diff --git a/README.md b/README.md index 877305d..c193b2b 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,15 @@ Mobile H5, admin, and API workspace for the WonderQ MiniAPP project. - `npm run dev:admin` starts the admin app. - `npm run dev:api` starts the API service. - `npm run build:all` builds all workspaces. + +## 后端内容发布 + +商品数据的正式源文件是 `src/generated-products.json`。后端部署时请从仓库根目录执行内容发布流程,不要把商品数据重新打进 H5: + +```bash +npm run db:content:release -- validate +npm run db:content:release -- plan +npm run db:content:release -- apply +``` + +`apply` 会把商品、首页配置、活动和媒体引用写入生产数据库,并在 `artifacts/` 生成可回滚快照。需要回滚时,使用发布命令输出的 `rollback` 命令。 diff --git a/src/App.tsx b/src/App.tsx index 8a4e035..e524df9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,7 +47,6 @@ import { type PublicSearchPageConfig, type PublicSiteConfig, } from "./api"; -import fallbackProductsJson from "./generated-products.json"; import { bottomCtas as fallbackBottomCtas, dealCards, @@ -255,54 +254,6 @@ type SwipeState = { axis: "horizontal" | "vertical" | null; }; -type SourceProduct = { - id: number; - title: string; - price: string; - priceUnit?: string | null; - pricingTiers?: ProductPricingTier[] | null; - tags: string[]; - image: string; - images?: Array<{ url: string; alt?: string | null; sortOrder?: number }>; - summary?: string | null; - destinationName?: string | null; - durationDays?: number | null; - durationNights?: number | null; - departureDates?: string[]; - remainingSpots?: number | null; - recommendation?: string | null; - keyFacts?: ProductKeyFact[] | null; - contentBlocks?: ProductContentBlock[] | null; - detailSections?: ProductDetailSection[] | null; - status?: string; - sortWeight?: number; -}; - -function normalizeFallbackProduct(product: SourceProduct): Product { - return { - id: product.id, - title: product.title, - price: product.price, - priceUnit: product.priceUnit, - pricingTiers: product.pricingTiers, - tags: product.tags, - image: product.image, - summary: product.summary, - destinationName: product.destinationName, - durationDays: product.durationDays, - durationNights: product.durationNights, - departureDates: product.departureDates, - remainingSpots: product.remainingSpots, - recommendation: product.recommendation, - images: product.images?.map((image, index) => ({ ...image, sortOrder: image.sortOrder ?? index })), - keyFacts: product.keyFacts, - contentBlocks: product.contentBlocks, - detailSections: product.detailSections, - status: product.status, - sortWeight: product.sortWeight, - }; -} - function fallbackNumberFromApiId(apiId: string, index: number) { const digits = apiId.match(/\d+/)?.[0]; return digits ? Number(digits) : 10000 + index; @@ -470,7 +421,7 @@ const fallbackData: AppData = { destinations: fallbackDestinations, themeCards: fallbackThemeCards, bottomCtas: fallbackBottomCtas, - products: (fallbackProductsJson as SourceProduct[]).map(normalizeFallbackProduct), + products: [], searchPage: fallbackSearchPageConfig, }; @@ -481,12 +432,7 @@ function useAppData() { } function getProducts(products: Product[]) { - return products.length ? products : fallbackData.products; -} - -function getProductAt(products: Product[], index: number) { - const list = getProducts(products); - return list[index] ?? list[0]; + return products; } const keywordAliases: Record = { 贵州: ["贵州", "贵阳", "黄果树", "荔波", "西江", "梵净山", "万峰林"], @@ -2515,7 +2461,7 @@ function SearchResultsPage({ {contentLoadState !== "ready" ? (
) : null} @@ -3686,14 +3632,14 @@ export default function App() { if (cancelled) return; setAppData({ ...normalizeSiteConfig(siteConfig, fallbackData), - products: products.length ? products.map(normalizeApiProduct) : fallbackData.products, + products: products.map(normalizeApiProduct), }); setContentLoadState("ready"); }) .catch((error) => { if (cancelled) return; setContentLoadState("offline"); - console.info("Public API unavailable, using bundled H5 content.", error); + console.info("Public API unavailable; product content remains empty until the API is available.", error); }); return () => {