diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5d2a7c5 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +VITE_API_BASE_URL="http://localhost:4000" diff --git a/.gitignore b/.gitignore index 2309cc8..b6acada 100644 --- a/.gitignore +++ b/.gitignore @@ -1,138 +1,7 @@ -# ---> Node -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +node_modules/ +dist/ .env -.env.development.local -.env.test.local -.env.production.local .env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - +*.log +coverage/ +.vite/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..582850b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,140 @@ +# WonderQ-Admin-UI Agent 工作说明 + +## 项目定位 + +WonderQ-Admin-UI 是独立的 WonderQ 后台管理前端,用于维护小程序/H5 前台的首页结构、目的地、线路商品、活动入口和客户需求线索。 + +当前项目是一个轻量单页后台应用,技术栈为 Vite + React + TypeScript + 普通 CSS。前端通过 `/api/admin/...` 调用后端管理接口,本地开发时由 Vite 代理到后端服务。 + +## 目录结构 + +```text +WonderQ-Admin-UI/ +├─ AGENTS.md # Agent 项目规则 +├─ README.md # 项目启动说明 +├─ docs/ +│ ├─ README.md # 文档索引 +│ └─ admin-backend-plan.md # 后台管理规划文档 +├─ public/ +│ └─ assets/ +│ ├─ guizhou/ # 贵州目的地展示图 +│ └─ source/ # 原始/通用静态素材与 manifest +├─ src/ +│ ├─ App.tsx # 后台主界面、页面状态、表单和业务 UI +│ ├─ api.ts # Admin API 类型、Token 管理和请求封装 +│ ├─ main.tsx # React 挂载入口 +│ ├─ styles.css # 全局样式与响应式布局 +│ └─ vite-env.d.ts # Vite 类型声明 +├─ index.html # Vite HTML 入口 +├─ package.json # 脚本与依赖 +├─ tsconfig.json # TypeScript 严格模式配置 +├─ vite.config.ts # Vite/React/本地 API 代理配置 +└─ yarn.lock # Yarn 依赖锁文件 +``` + +## 模块分工 + +- `src/App.tsx`:集中实现登录页、侧边导航、结构维护、首页/目的地维护、商品维护、线索跟进、Toast 提示等后台 UI。 +- `src/api.ts`:定义 `Product`、`Destination`、`Lead`、`SiteConfig` 等接口类型,封装登录、商品、目的地、站点配置、线索、发布和重置接口。 +- `src/styles.css`:全局布局、后台工作台、表格/表单、商品编辑器、移动端适配等样式。 +- `public/assets/`:前台/后台预览用静态图片资源,代码中保留 `/assets/...` 路径引用。 +- `docs/`:后台规划与后续协作边界,涉及后端 API 和数据模型时先参考这里,再看后端仓库文档。 + +## 启动与构建 + +依赖安装: + +```bash +yarn install +``` + +本地开发: + +```bash +yarn dev +``` + +默认访问地址: + +```text +http://localhost:5601 +``` + +本地联调要求: + +- 后端 Admin API 默认运行在 `http://localhost:4000`。 +- `vite.config.ts` 将 `/api` 代理到本地后端。 +- 如需改 API 地址,优先使用环境变量 `VITE_API_BASE_URL`,不要硬编码生产地址。 + +类型检查与生产构建: + +```bash +yarn build +``` + +构建产物预览: + +```bash +yarn preview +``` + +默认预览端口为 `5602`。 + +## 测试流程 + +当前 `package.json` 未配置独立测试脚本。提交前至少执行: + +```bash +yarn build +``` + +该命令会先运行 `tsc --noEmit`,再执行 Vite 生产构建。涉及 UI 布局、交互或接口行为时,还需本地启动页面并手动验证关键流程。 + +## 部署规范 + +- 生产发布使用 `yarn build` 生成 `dist/`。 +- `dist/` 可交给 Nginx、CDN 或静态托管服务部署。 +- 生产环境 API 地址必须通过部署环境变量配置,不要提交 `.env`、`.env.local` 或任何包含密钥的配置。 +- 后台接口、数据库、权限、发布/回滚能力属于后端仓库边界,本仓库只维护管理前端。 + +## 代码风格 + +- 使用 TypeScript 严格模式,新增代码必须有明确类型,避免 `any`。 +- React 采用函数组件和 Hooks,不引入 class component。 +- 遵循现有单文件轻量结构;未获授权前不要主动拆分大型组件或引入状态管理库。 +- UI 图标优先复用 `lucide-react`。 +- 样式优先沿用 `src/styles.css` 的类名、色彩、间距和 6-8px 圆角习惯。 +- API 访问统一走 `src/api.ts` 的 `request` 封装,不在组件里重复拼接鉴权逻辑。 +- 中文文案文件按 UTF-8 处理;修改前确认编辑器编码,避免造成乱码。 + +## 命名规范 + +- 组件、类型使用 PascalCase,例如 `ProductManager`、`SiteConfig`。 +- 函数、变量使用 camelCase,例如 `getProducts`、`selectedId`。 +- 联合类型字面量使用小写英文,例如 `"draft"`、`"published"`、`"leads"`。 +- CSS class 沿用 kebab-case,例如 `.admin-shell`、`.product-table`。 +- API 类型与后端字段保持同名,避免在前端私自改字段语义。 + +## 开发准则 + +- 默认先读相关文件和现有模式,再修改。 +- 只处理用户明确要求的范围,不额外加功能。 +- 修改前确认是否涉及接口契约、登录鉴权、发布操作或静态资源路径。 +- 不展示、不输出、不提交 Token、密码、密钥、真实客户手机号等敏感信息。 +- 不新增依赖,除非用户明确授权并说明理由。 +- 不提交 `node_modules/`、`dist/`、`.env`、`.env.local`、日志或覆盖率文件。 +- 涉及删除、批量移动、重命名资源文件时必须先征得用户明确授权。 + +## 锁定文件与高风险区域 + +未经用户明确授权,不得改动: + +- `.env`、`.env.local`、任何生产环境变量或密钥配置。 +- `vite.config.ts` 中的代理、端口和构建配置。 +- `tsconfig.json` 的严格类型配置。 +- `package.json`、`yarn.lock` 的依赖与脚本。 +- `src/api.ts` 的接口路径、Token 存储键、鉴权头和类型契约。 +- `public/assets/` 下已被页面引用的图片资源和 `manifest.json`。 +- `docs/admin-backend-plan.md` 中的后端规划,除非任务明确要求更新文档。 + +本仓库没有数据库配置文件;数据库、Prisma、权限和生产后端配置在后端仓库维护。 diff --git a/README.md b/README.md index e5c014a..bd82927 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -# WonderQ-Admin-UI +# WonderQ-Admin-UI +独立的 WonderQ 后台管理前端,基于 Vite、React、TypeScript 和普通 CSS。 + +## 本地启动 + +1. 安装依赖: + +```bash +yarn install +``` + +2. 确认后端 API 运行在 `http://localhost:4000`。 + +3. 启动后台 UI: + +```bash +yarn dev +``` + +访问:http://localhost:5601 + +默认后台账号由 `WonderQ-Admin` 提供:`admin@example.com / ChangeMe123!`。 + +## 目录说明 + +- `src/`:后台管理 UI 和 API 客户端。 +- `public/assets/`:后台预览所需静态资源,保留 `/assets/...` 引用方式。 +- `vite.config.ts`:开发环境将 `/api` 代理到 `http://localhost:4000`。 + +本仓库已经独立,不再依赖 `WonderQ-MiniAPP` 的相对路径。 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..383d67d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,7 @@ +# WonderQ-Admin-UI 文档 + +本目录存放后台管理前端相关文档。 + +- `admin-backend-plan.md`:从原 MiniAPP 迁出的后台管理规划,保留管理模块、页面能力和与后端 API 的协作边界。 + +后端 API 文档位于 `D:\www\znkj\WonderQ-Admin\docs`。 \ No newline at end of file diff --git a/docs/admin-backend-plan.md b/docs/admin-backend-plan.md new file mode 100644 index 0000000..6fe523d --- /dev/null +++ b/docs/admin-backend-plan.md @@ -0,0 +1,404 @@ +# H5 后台管理系统规划 + +## 目标 + +为当前鸿鹄逸游 H5 项目建设一套可真实上线维护的后台系统,让运营人员可以管理首页内容、目的地、线路产品、活动专题、客户需求、订单线索、消息和客服配置,而不是继续依赖改代码发布。 + +当前 H5 主要是一个静态 Vite + React 应用: + +- 首页配置来自 `src/content.ts`:轮播、目的地、主题卡、线路分区、底部 CTA。 +- 线路产品来自 `src/generated-products.json`:48 条产品卡片。 +- 目的地搜索、活动页、需求表单、消息、订单、客服经理等流程目前集中写在 `src/App.tsx`。 +- 图片资源在 `public/assets/source`,约 115 个文件。 + +后台建设的核心不是简单加一个管理页面,而是把这些静态内容拆成稳定的数据模型,并提供发布、审核、回滚、权限、日志和运维能力。 + +## 推荐架构 + +### 代码组织 + +建议从单一前端项目演进为 TypeScript monorepo: + +```text +apps/ + h5/ # 当前 Vite React H5,可从现有 src/ 迁移 + admin/ # 后台管理端,React + Ant Design Pro 或 shadcn/ui + api/ # 后端 API,NestJS 或 Fastify +packages/ + shared/ # 共享类型、校验 schema、接口 DTO + config/ # eslint/tsconfig 等公共配置 +``` + +保守可落地方案: + +- 前台 H5:继续 Vite + React,先只改数据来源。 +- 后台 Admin:React + Ant Design Pro。原因是后台表格、筛选、表单、权限、布局成熟,开发快。 +- API:NestJS + Prisma。原因是 TypeScript 体系一致,权限、模块化、OpenAPI、测试更规整。 +- 数据库:PostgreSQL。 +- 缓存/队列:Redis,先用于发布缓存、短信/通知任务、操作频控。 +- 文件存储:阿里 OSS、腾讯 COS 或 S3 兼容对象存储。 +- 部署:Docker + Nginx + CDN,后续可上云托管或 Kubernetes。 + +### 服务边界 + +```mermaid +flowchart LR + H5["H5 前台"] --> API["Public API"] + Admin["后台管理端"] --> AdminAPI["Admin API"] + API --> DB[("PostgreSQL")] + AdminAPI --> DB + AdminAPI --> OSS["对象存储"] + AdminAPI --> Redis[("Redis")] + CDN["CDN"] --> H5 + CDN --> OSS +``` + +## 后台功能模块 + +### 1. 工作台 + +- 今日新增需求、待跟进需求、待确认订单、上架线路数。 +- 最近发布记录、失败任务、异常日志。 +- 快捷入口:新建线路、新建活动、发布首页、导出需求。 + +### 2. 首页装修 + +对应当前 `src/content.ts`。 + +- 轮播管理:图片、标题、副标题、跳转类型、排序、上下架。 +- 锚点分类:探索世界、主题甄选、精选线路、一生一次、鸿鹄稀缺。 +- 目的地宫格:名称、图片、跳转关键词、排序、是否推荐。 +- 主题卡片:主题名、封面、关联线路或活动专题。 +- 线路分区:分区标题图、分区名称、展示线路、排序规则。 +- 底部 CTA:权益卡、公众号、目的地搜索、需求表单等入口。 +- 页脚与品牌图:图片、服务承诺、备案信息。 +- 预览与发布:草稿预览、定时发布、回滚上一版。 + +### 3. 线路产品管理 + +对应当前 `src/generated-products.json` 和产品详情页。 + +- 基础信息:标题、副标题、目的地、产品类型、价格、起价单位、标签。 +- 封面与图集:列表图、详情头图、行程图片。 +- 详情内容:特色、玩法、美食、行程日程、费用包含/不含、预订须知。 +- 售卖配置:上架状态、推荐权重、活动归属、库存/席位说明。 +- 搜索配置:关键词、别名、目的地关联、主题标签。 +- SEO/分享:分享标题、描述、海报图。 +- 批量能力:导入、导出、批量上下架、批量改标签。 + +### 4. 目的地管理 + +对应当前首页目的地、目的地搜索页和搜索别名逻辑。 + +- 国家/城市/地区层级。 +- 热门目的地、出境/国内分类。 +- 搜索别名:例如 `马代 -> 马尔代夫`、`内蒙 -> 内蒙古`。 +- 目的地封面图、推荐线路、活动专题绑定。 +- 排序和上下架。 + +### 5. 活动专题管理 + +对应当前端午甄选、早鸟尊享、鸿鹄逸游等活动页。 + +- 专题标题、封面、介绍文案、活动时间。 +- 关联线路列表。 +- CTA:提交需求、联系客服、跳转线路。 +- 发布状态:草稿、待审核、已发布、已下线。 +- 合规文案:例如活动免责声明。 + +### 6. 客户需求管理 + +对应当前 `DemandPage`,上线后这是最重要的转化入口。 + +- 表单字段:目的地、手机号、出行时间、预算、人数、备注、来源页面、来源线路。 +- 线索状态:新建、已分配、已联系、方案中、已成交、无效。 +- 客户经理分配:手动分配、自动轮询分配。 +- 跟进记录:电话、微信、备注、下次提醒。 +- 防刷:手机号频控、验证码、IP 风控、黑名单。 +- 导出:Excel/CSV,按时间、状态、顾问筛选。 + +### 7. 客户与订单管理 + +对应当前消息、订单中心的真实业务化。 + +- 客户档案:手机号、姓名、微信、偏好、历史需求。 +- 订单/方案:关联客户、线路、顾问、出行时间、人数、报价、状态。 +- 状态流转:需求单 -> 方案 -> 合同/支付 -> 出行中 -> 已完成/售后。 +- 附件:合同、方案 PDF、发票、签证材料。 +- 订单备注和操作日志。 + +### 8. 消息与客服配置 + +- 后台通知:需求分配、订单状态变化、发布失败。 +- 前台消息:活动通知、顾问消息、订单提醒。 +- 客服入口:在线联系、电话、服务时间、企业微信/IM 配置。 +- 短信:验证码、需求提交确认、顾问跟进提醒。 + +### 9. 媒体库 + +- 图片上传、裁剪、压缩、WebP 转换。 +- 图片分组:轮播、目的地、线路、活动、品牌。 +- 文件引用关系:防止删除正在使用的图片。 +- CDN URL、缩略图、多尺寸版本。 + +### 10. 权限与审计 + +- 角色:超级管理员、运营、产品、客服主管、客户经理、只读审计。 +- 权限粒度:菜单权限、按钮权限、数据范围权限。 +- 审核流:重要内容先提交审核再发布。 +- 操作日志:谁在什么时候改了什么,从什么值改到什么值。 +- 登录安全:强密码、二次验证可选、登录 IP 记录。 + +## 核心数据模型 + +第一期建议覆盖这些表: + +| 表 | 用途 | +| --- | --- | +| `admin_users` | 后台用户 | +| `roles` / `permissions` | RBAC 权限 | +| `media_assets` | 图片和文件资源 | +| `site_versions` | 首页/站点配置发布版本 | +| `home_sections` | 首页模块配置 | +| `hero_slides` | 首页轮播 | +| `destinations` | 目的地 | +| `destination_aliases` | 搜索别名 | +| `themes` | 主题甄选 | +| `products` | 线路产品 | +| `product_images` | 产品图片 | +| `product_itineraries` | 行程日程 | +| `product_fee_items` | 费用说明 | +| `campaigns` | 活动专题 | +| `campaign_products` | 专题与线路关联 | +| `leads` | 客户需求/线索 | +| `lead_followups` | 跟进记录 | +| `customers` | 客户档案 | +| `orders` | 订单/方案 | +| `messages` | 前台消息 | +| `audit_logs` | 操作审计 | + +### 示例:产品表 + +```sql +products ( + id uuid primary key, + title text not null, + subtitle text, + destination_id uuid, + price_amount integer, + price_unit varchar(20), + tags text[], + cover_asset_id uuid, + summary text, + status varchar(20), -- draft, published, archived + sort_weight integer default 0, + published_at timestamptz, + created_at timestamptz not null, + updated_at timestamptz not null +) +``` + +### 示例:线索表 + +```sql +leads ( + id uuid primary key, + destination text, + phone varchar(30) not null, + travel_date date, + people_count integer, + budget_min integer, + budget_max integer, + source_page text, + source_product_id uuid, + status varchar(30) not null, + assigned_user_id uuid, + created_at timestamptz not null, + updated_at timestamptz not null +) +``` + +## API 设计 + +### Public API 给 H5 使用 + +- `GET /api/public/site-config`:首页配置、模块排序、底部导航。 +- `GET /api/public/products`:线路列表,支持目的地、主题、活动、关键词筛选。 +- `GET /api/public/products/:id`:线路详情。 +- `GET /api/public/destinations`:目的地和热门分类。 +- `GET /api/public/campaigns/:slug`:活动专题详情。 +- `POST /api/public/leads`:提交出行需求。 +- `POST /api/public/sms/send-code`:发送验证码。 + +### Admin API 给后台使用 + +- `POST /api/admin/auth/login` +- `GET /api/admin/dashboard` +- `CRUD /api/admin/products` +- `CRUD /api/admin/destinations` +- `CRUD /api/admin/campaigns` +- `CRUD /api/admin/home-config` +- `CRUD /api/admin/media-assets` +- `GET /api/admin/leads` +- `PATCH /api/admin/leads/:id/status` +- `POST /api/admin/leads/:id/followups` +- `CRUD /api/admin/orders` +- `GET /api/admin/audit-logs` +- `POST /api/admin/publish` +- `POST /api/admin/rollback` + +## 前台改造路径 + +### 第一步:数据层抽离 + +先不大改 UI,把硬编码内容替换成数据适配层。 + +- 新建 `src/api/client.ts` 和 `src/api/types.ts`。 +- 新建 `src/adapters/siteConfig.ts`,把接口数据转成当前组件需要的结构。 +- 保留本地 JSON fallback,便于本地开发和接口故障降级。 +- 把 `heroSlides`、`destinations`、`themeCards`、`sectionHeaders`、`bottomCtas` 从静态 import 改成接口加载。 + +### 第二步:产品和目的地接口化 + +- `generated-products.json` 改为接口 seed 数据。 +- 搜索逻辑从前端 `keywordAliases` 迁移到后端。 +- 产品详情页从 `getDetailMeta` 这类本地推断改为真实详情字段。 + +### 第三步:需求表单真实提交 + +- `DemandPage` 接入 `POST /api/public/leads`。 +- 增加手机号校验、验证码、提交成功页。 +- 后台线索列表能看到来源页面和来源产品。 + +### 第四步:消息与订单真实化 + +- 登录态、客户身份、订单列表需要独立规划。 +- 如果短期不做会员体系,可先保留“提交需求后的查询链接/手机号验证码查询”。 + +## 发布与运维 + +### 发布策略 + +- 内容发布与代码发布分离。 +- 运营修改内容后生成草稿版本。 +- 审核通过后写入 `site_versions`,Public API 默认返回当前发布版本。 +- CDN 缓存使用短 TTL 或发布后主动刷新。 +- 每次发布可回滚到上一版本。 + +### 环境 + +| 环境 | 用途 | +| --- | --- | +| local | 本地开发 | +| dev | 联调环境 | +| staging | 预发布,连接准生产数据或脱敏数据 | +| production | 正式环境 | + +### 监控与备份 + +- API 错误率、响应时间、数据库连接数。 +- 需求提交成功率、短信发送成功率。 +- 每日数据库自动备份,至少保留 14 到 30 天。 +- 对象存储开启版本控制或回收站。 +- 关键操作日志不可被普通管理员删除。 + +### 安全 + +- 后台必须 HTTPS。 +- 管理端登录限流。 +- 所有 Admin API 校验权限。 +- Public API 做参数校验和频控。 +- 手机号等隐私信息后台脱敏展示,导出需要高权限。 +- 防止任意文件上传:限制 MIME、大小、后缀,图片重新编码。 + +## 分阶段交付计划 + +### Phase 1:后台 MVP,约 2 到 3 周 + +目标:运营可以维护首页和线路,H5 可以读取接口。 + +- 搭建 monorepo、API、Admin、数据库。 +- 建立媒体库、产品、目的地、首页配置数据表。 +- 写 seed 脚本,把当前 `content.ts` 和 `generated-products.json` 导入数据库。 +- 后台完成登录、产品列表/编辑、首页配置、图片上传。 +- H5 接入 `site-config` 和 `products`,保留本地 fallback。 + +### Phase 2:线索闭环,约 2 周 + +目标:出行需求可以真实提交、分配、跟进。 + +- 需求表单接口。 +- 后台线索列表、状态流转、跟进记录。 +- 客户经理分配。 +- 短信验证码和提交通知。 +- 导出和基础统计。 + +### Phase 3:活动和发布体系,约 2 到 3 周 + +目标:专题页和首页可以安全发布。 + +- 活动专题管理。 +- 草稿/审核/发布/回滚。 +- 发布日志和操作审计。 +- CDN 刷新。 +- 预发布环境验收。 + +### Phase 4:订单、客户和消息,约 3 到 5 周 + +目标:从线索进一步进入服务履约。 + +- 客户档案。 +- 订单/方案管理。 +- 前台消息和订单查询。 +- 附件上传。 +- 顾问工作台。 + +### Phase 5:生产加固,持续迭代 + +目标:稳定上线维护。 + +- 监控告警、备份恢复演练。 +- 权限细化、数据脱敏。 +- 性能优化和缓存。 +- 自动化测试、CI/CD。 +- 数据分析看板。 + +## MVP 优先级 + +必须先做: + +1. 后台登录与权限。 +2. 媒体库。 +3. 线路产品管理。 +4. 首页配置管理。 +5. H5 读取接口并保留 fallback。 +6. 需求表单真实提交。 +7. 线索后台跟进。 +8. 发布、回滚、操作日志。 + +可以后做: + +1. 完整会员登录。 +2. 真实支付。 +3. 复杂订单履约。 +4. 企业微信深度集成。 +5. 多语言/多品牌站点。 + +## 近期可执行清单 + +1. 建立 `apps/h5`、`apps/admin`、`apps/api` 结构。 +2. 设计 Prisma schema,并先覆盖产品、首页、媒体、目的地、线索。 +3. 编写 seed 脚本,把现有静态内容导入数据库。 +4. 做 Public API,先返回与当前 H5 兼容的数据。 +5. 改造 H5 数据加载层,保证 UI 不变。 +6. 做 Admin 的产品管理和首页装修。 +7. 接通需求表单和后台线索列表。 +8. 补充发布版本、回滚、审计日志。 + +## 风险与建议 + +- 不建议一开始就做完整电商订单和支付,旅游定制业务更适合先做线索和顾问跟进闭环。 +- 不建议让后台直接编辑任意 JSON,短期快但长期容易把数据结构搞乱。 +- 不建议 H5 只依赖实时接口,首页配置应支持发布版本和缓存,避免后台故障影响前台展示。 +- 不建议图片继续只放在代码仓库里,真实上线后要进入对象存储和 CDN。 +- 如果预算有限,第一期可以先用单台云服务器 + PostgreSQL + 对象存储;但备份、HTTPS、日志和权限不能省。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..aef4f7d --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + 万趣小程序管理端 + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..dfff1a8 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "wonderq-admin-ui", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 5601", + "build": "tsc --noEmit && vite build", + "preview": "vite preview --host 0.0.0.0 --port 5602" + }, + "dependencies": { + "@vitejs/plugin-react": "^6.0.3", + "lucide-react": "^1.21.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "vite": "^8.1.0" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "typescript": "^6.0.3" + } +} + diff --git a/public/assets/guizhou/baili-azalea.jpg b/public/assets/guizhou/baili-azalea.jpg new file mode 100644 index 0000000..331e131 Binary files /dev/null and b/public/assets/guizhou/baili-azalea.jpg differ diff --git a/public/assets/guizhou/bailian-hot-spring.jpg b/public/assets/guizhou/bailian-hot-spring.jpg new file mode 100644 index 0000000..0d8f004 Binary files /dev/null and b/public/assets/guizhou/bailian-hot-spring.jpg differ diff --git a/public/assets/guizhou/cave-sunbeam.jpg b/public/assets/guizhou/cave-sunbeam.jpg new file mode 100644 index 0000000..f9c4bd3 Binary files /dev/null and b/public/assets/guizhou/cave-sunbeam.jpg differ diff --git a/public/assets/guizhou/chishui-danxia.jpg b/public/assets/guizhou/chishui-danxia.jpg new file mode 100644 index 0000000..00e4f95 Binary files /dev/null and b/public/assets/guizhou/chishui-danxia.jpg differ diff --git a/public/assets/guizhou/dong-village.jpg b/public/assets/guizhou/dong-village.jpg new file mode 100644 index 0000000..5e11592 Binary files /dev/null and b/public/assets/guizhou/dong-village.jpg differ diff --git a/public/assets/guizhou/fanjing-mountain.jpg b/public/assets/guizhou/fanjing-mountain.jpg new file mode 100644 index 0000000..ddfc76a Binary files /dev/null and b/public/assets/guizhou/fanjing-mountain.jpg differ diff --git a/public/assets/guizhou/gorge-paddle.jpg b/public/assets/guizhou/gorge-paddle.jpg new file mode 100644 index 0000000..aa6563b Binary files /dev/null and b/public/assets/guizhou/gorge-paddle.jpg differ diff --git a/public/assets/guizhou/huangguoshu-waterfall.jpg b/public/assets/guizhou/huangguoshu-waterfall.jpg new file mode 100644 index 0000000..1d6d035 Binary files /dev/null and b/public/assets/guizhou/huangguoshu-waterfall.jpg differ diff --git a/public/assets/guizhou/intangible-masks.jpg b/public/assets/guizhou/intangible-masks.jpg new file mode 100644 index 0000000..3e24a5d Binary files /dev/null and b/public/assets/guizhou/intangible-masks.jpg differ diff --git a/public/assets/guizhou/jiabang-terrace.jpg b/public/assets/guizhou/jiabang-terrace.jpg new file mode 100644 index 0000000..37c8614 Binary files /dev/null and b/public/assets/guizhou/jiabang-terrace.jpg differ diff --git a/public/assets/guizhou/jianhe-hot-spring.jpg b/public/assets/guizhou/jianhe-hot-spring.jpg new file mode 100644 index 0000000..994c1b5 Binary files /dev/null and b/public/assets/guizhou/jianhe-hot-spring.jpg differ diff --git a/public/assets/guizhou/jiaxiu-tower.jpg b/public/assets/guizhou/jiaxiu-tower.jpg new file mode 100644 index 0000000..cd14edb Binary files /dev/null and b/public/assets/guizhou/jiaxiu-tower.jpg differ diff --git a/public/assets/guizhou/karst-cave-explorer.jpg b/public/assets/guizhou/karst-cave-explorer.jpg new file mode 100644 index 0000000..1d890e1 Binary files /dev/null and b/public/assets/guizhou/karst-cave-explorer.jpg differ diff --git a/public/assets/guizhou/libo-water-forest.jpg b/public/assets/guizhou/libo-water-forest.jpg new file mode 100644 index 0000000..2263388 Binary files /dev/null and b/public/assets/guizhou/libo-water-forest.jpg differ diff --git a/public/assets/guizhou/libo-xiaoqikong.jpg b/public/assets/guizhou/libo-xiaoqikong.jpg new file mode 100644 index 0000000..5ba347f Binary files /dev/null and b/public/assets/guizhou/libo-xiaoqikong.jpg differ diff --git a/public/assets/guizhou/miao-costume.jpg b/public/assets/guizhou/miao-costume.jpg new file mode 100644 index 0000000..7c52841 Binary files /dev/null and b/public/assets/guizhou/miao-costume.jpg differ diff --git a/public/assets/guizhou/miao-dragon-boat.jpg b/public/assets/guizhou/miao-dragon-boat.jpg new file mode 100644 index 0000000..4924d6e Binary files /dev/null and b/public/assets/guizhou/miao-dragon-boat.jpg differ diff --git a/public/assets/guizhou/miao-lusheng-dance.jpg b/public/assets/guizhou/miao-lusheng-dance.jpg new file mode 100644 index 0000000..369f1ab Binary files /dev/null and b/public/assets/guizhou/miao-lusheng-dance.jpg differ diff --git a/public/assets/guizhou/miao-village-street.jpg b/public/assets/guizhou/miao-village-street.jpg new file mode 100644 index 0000000..9079bd1 Binary files /dev/null and b/public/assets/guizhou/miao-village-street.jpg differ diff --git a/public/assets/guizhou/mountain-resort.jpg b/public/assets/guizhou/mountain-resort.jpg new file mode 100644 index 0000000..f185093 Binary files /dev/null and b/public/assets/guizhou/mountain-resort.jpg differ diff --git a/public/assets/guizhou/shuichunhe-rafting.jpg b/public/assets/guizhou/shuichunhe-rafting.jpg new file mode 100644 index 0000000..68b61b7 Binary files /dev/null and b/public/assets/guizhou/shuichunhe-rafting.jpg differ diff --git a/public/assets/guizhou/tiankeng-pool.jpg b/public/assets/guizhou/tiankeng-pool.jpg new file mode 100644 index 0000000..ed1383d Binary files /dev/null and b/public/assets/guizhou/tiankeng-pool.jpg differ diff --git a/public/assets/guizhou/tiankeng-sunbeam-group.jpg b/public/assets/guizhou/tiankeng-sunbeam-group.jpg new file mode 100644 index 0000000..823f4d7 Binary files /dev/null and b/public/assets/guizhou/tiankeng-sunbeam-group.jpg differ diff --git a/public/assets/guizhou/wanfenglin.jpg b/public/assets/guizhou/wanfenglin.jpg new file mode 100644 index 0000000..01e4e76 Binary files /dev/null and b/public/assets/guizhou/wanfenglin.jpg differ diff --git a/public/assets/guizhou/wumeng-grassland.jpg b/public/assets/guizhou/wumeng-grassland.jpg new file mode 100644 index 0000000..fcbd0ea Binary files /dev/null and b/public/assets/guizhou/wumeng-grassland.jpg differ diff --git a/public/assets/guizhou/xijiang-miao-village.jpg b/public/assets/guizhou/xijiang-miao-village.jpg new file mode 100644 index 0000000..f398b01 Binary files /dev/null and b/public/assets/guizhou/xijiang-miao-village.jpg differ diff --git a/public/assets/guizhou/zhenyuan-ancient-town.jpg b/public/assets/guizhou/zhenyuan-ancient-town.jpg new file mode 100644 index 0000000..bd8bada Binary files /dev/null and b/public/assets/guizhou/zhenyuan-ancient-town.jpg differ diff --git a/public/assets/guizhou/zhijin-cave-pool.jpg b/public/assets/guizhou/zhijin-cave-pool.jpg new file mode 100644 index 0000000..f8aac84 Binary files /dev/null and b/public/assets/guizhou/zhijin-cave-pool.jpg differ diff --git a/public/assets/guizhou/zhijin-cave.jpg b/public/assets/guizhou/zhijin-cave.jpg new file mode 100644 index 0000000..9676ef7 Binary files /dev/null and b/public/assets/guizhou/zhijin-cave.jpg differ diff --git a/public/assets/source/asset-000-6130f5faf6.jpg b/public/assets/source/asset-000-6130f5faf6.jpg new file mode 100644 index 0000000..77849f0 Binary files /dev/null and b/public/assets/source/asset-000-6130f5faf6.jpg differ diff --git a/public/assets/source/asset-001-e3ccdf0858.jpg b/public/assets/source/asset-001-e3ccdf0858.jpg new file mode 100644 index 0000000..dbd71e3 Binary files /dev/null and b/public/assets/source/asset-001-e3ccdf0858.jpg differ diff --git a/public/assets/source/asset-002-60f1781bad.jpg b/public/assets/source/asset-002-60f1781bad.jpg new file mode 100644 index 0000000..cca44c8 Binary files /dev/null and b/public/assets/source/asset-002-60f1781bad.jpg differ diff --git a/public/assets/source/asset-003-67c94551ff.jpg b/public/assets/source/asset-003-67c94551ff.jpg new file mode 100644 index 0000000..be3443c Binary files /dev/null and b/public/assets/source/asset-003-67c94551ff.jpg differ diff --git a/public/assets/source/asset-004-74801a9861.jpg b/public/assets/source/asset-004-74801a9861.jpg new file mode 100644 index 0000000..e16e9de Binary files /dev/null and b/public/assets/source/asset-004-74801a9861.jpg differ diff --git a/public/assets/source/asset-005-5f88616344.jpg b/public/assets/source/asset-005-5f88616344.jpg new file mode 100644 index 0000000..765a3c5 Binary files /dev/null and b/public/assets/source/asset-005-5f88616344.jpg differ diff --git a/public/assets/source/asset-006-065f65e89a.png b/public/assets/source/asset-006-065f65e89a.png new file mode 100644 index 0000000..a7562ed Binary files /dev/null and b/public/assets/source/asset-006-065f65e89a.png differ diff --git a/public/assets/source/asset-007-bfd23e4423.png b/public/assets/source/asset-007-bfd23e4423.png new file mode 100644 index 0000000..66f3278 Binary files /dev/null and b/public/assets/source/asset-007-bfd23e4423.png differ diff --git a/public/assets/source/asset-008-b69a2ebdf1.png b/public/assets/source/asset-008-b69a2ebdf1.png new file mode 100644 index 0000000..f334075 Binary files /dev/null and b/public/assets/source/asset-008-b69a2ebdf1.png differ diff --git a/public/assets/source/asset-009-7c60387849.png b/public/assets/source/asset-009-7c60387849.png new file mode 100644 index 0000000..1a0bfd7 Binary files /dev/null and b/public/assets/source/asset-009-7c60387849.png differ diff --git a/public/assets/source/asset-010-8614b34e80.png b/public/assets/source/asset-010-8614b34e80.png new file mode 100644 index 0000000..4a9d9dc Binary files /dev/null and b/public/assets/source/asset-010-8614b34e80.png differ diff --git a/public/assets/source/asset-011-398553c792.png b/public/assets/source/asset-011-398553c792.png new file mode 100644 index 0000000..13b8cf7 Binary files /dev/null and b/public/assets/source/asset-011-398553c792.png differ diff --git a/public/assets/source/asset-012-486ac8f1a1.png b/public/assets/source/asset-012-486ac8f1a1.png new file mode 100644 index 0000000..b373d8a Binary files /dev/null and b/public/assets/source/asset-012-486ac8f1a1.png differ diff --git a/public/assets/source/asset-013-babb337ba2.png b/public/assets/source/asset-013-babb337ba2.png new file mode 100644 index 0000000..b9b6be6 Binary files /dev/null and b/public/assets/source/asset-013-babb337ba2.png differ diff --git a/public/assets/source/asset-014-fd57beac6b.png b/public/assets/source/asset-014-fd57beac6b.png new file mode 100644 index 0000000..603116d Binary files /dev/null and b/public/assets/source/asset-014-fd57beac6b.png differ diff --git a/public/assets/source/asset-015-94835fc601.png b/public/assets/source/asset-015-94835fc601.png new file mode 100644 index 0000000..e35cdda Binary files /dev/null and b/public/assets/source/asset-015-94835fc601.png differ diff --git a/public/assets/source/asset-016-306236a4af.png b/public/assets/source/asset-016-306236a4af.png new file mode 100644 index 0000000..e4d9a48 Binary files /dev/null and b/public/assets/source/asset-016-306236a4af.png differ diff --git a/public/assets/source/asset-017-e4a0e59bd4.png b/public/assets/source/asset-017-e4a0e59bd4.png new file mode 100644 index 0000000..0f69007 Binary files /dev/null and b/public/assets/source/asset-017-e4a0e59bd4.png differ diff --git a/public/assets/source/asset-018-30bc451049.jpg b/public/assets/source/asset-018-30bc451049.jpg new file mode 100644 index 0000000..f8951f5 Binary files /dev/null and b/public/assets/source/asset-018-30bc451049.jpg differ diff --git a/public/assets/source/asset-019-8b7f71737e.jpg b/public/assets/source/asset-019-8b7f71737e.jpg new file mode 100644 index 0000000..9ead9f3 Binary files /dev/null and b/public/assets/source/asset-019-8b7f71737e.jpg differ diff --git a/public/assets/source/asset-020-5207a45c09.png b/public/assets/source/asset-020-5207a45c09.png new file mode 100644 index 0000000..958e115 Binary files /dev/null and b/public/assets/source/asset-020-5207a45c09.png differ diff --git a/public/assets/source/asset-021-267958c0d6.png b/public/assets/source/asset-021-267958c0d6.png new file mode 100644 index 0000000..3ca51d1 Binary files /dev/null and b/public/assets/source/asset-021-267958c0d6.png differ diff --git a/public/assets/source/asset-022-f982dcf65e.png b/public/assets/source/asset-022-f982dcf65e.png new file mode 100644 index 0000000..b867dd0 Binary files /dev/null and b/public/assets/source/asset-022-f982dcf65e.png differ diff --git a/public/assets/source/asset-023-20ebf544be.png b/public/assets/source/asset-023-20ebf544be.png new file mode 100644 index 0000000..87d2fdd Binary files /dev/null and b/public/assets/source/asset-023-20ebf544be.png differ diff --git a/public/assets/source/asset-024-6e6ee3998c.png b/public/assets/source/asset-024-6e6ee3998c.png new file mode 100644 index 0000000..9ae6b9b Binary files /dev/null and b/public/assets/source/asset-024-6e6ee3998c.png differ diff --git a/public/assets/source/asset-025-980c7ac168.png b/public/assets/source/asset-025-980c7ac168.png new file mode 100644 index 0000000..5d34183 Binary files /dev/null and b/public/assets/source/asset-025-980c7ac168.png differ diff --git a/public/assets/source/asset-026-6e4d686b97.png b/public/assets/source/asset-026-6e4d686b97.png new file mode 100644 index 0000000..500a64e Binary files /dev/null and b/public/assets/source/asset-026-6e4d686b97.png differ diff --git a/public/assets/source/asset-027-22aef1d83b.png b/public/assets/source/asset-027-22aef1d83b.png new file mode 100644 index 0000000..4326d0b Binary files /dev/null and b/public/assets/source/asset-027-22aef1d83b.png differ diff --git a/public/assets/source/asset-028-912019b131.png b/public/assets/source/asset-028-912019b131.png new file mode 100644 index 0000000..26818f3 Binary files /dev/null and b/public/assets/source/asset-028-912019b131.png differ diff --git a/public/assets/source/asset-029-9eccbb7131.png b/public/assets/source/asset-029-9eccbb7131.png new file mode 100644 index 0000000..9a84338 Binary files /dev/null and b/public/assets/source/asset-029-9eccbb7131.png differ diff --git a/public/assets/source/asset-030-a77d1ef23a.jpg b/public/assets/source/asset-030-a77d1ef23a.jpg new file mode 100644 index 0000000..5234932 Binary files /dev/null and b/public/assets/source/asset-030-a77d1ef23a.jpg differ diff --git a/public/assets/source/asset-031-802555e02b.png b/public/assets/source/asset-031-802555e02b.png new file mode 100644 index 0000000..2c2b817 Binary files /dev/null and b/public/assets/source/asset-031-802555e02b.png differ diff --git a/public/assets/source/asset-032-1257cf8865.jpg b/public/assets/source/asset-032-1257cf8865.jpg new file mode 100644 index 0000000..7a283e0 Binary files /dev/null and b/public/assets/source/asset-032-1257cf8865.jpg differ diff --git a/public/assets/source/asset-033-4f39bbaba6.jpg b/public/assets/source/asset-033-4f39bbaba6.jpg new file mode 100644 index 0000000..585c94b Binary files /dev/null and b/public/assets/source/asset-033-4f39bbaba6.jpg differ diff --git a/public/assets/source/asset-034-393944f522.png b/public/assets/source/asset-034-393944f522.png new file mode 100644 index 0000000..2cf1d79 Binary files /dev/null and b/public/assets/source/asset-034-393944f522.png differ diff --git a/public/assets/source/asset-035-9d6bac22b3.jpg b/public/assets/source/asset-035-9d6bac22b3.jpg new file mode 100644 index 0000000..ae2d64a Binary files /dev/null and b/public/assets/source/asset-035-9d6bac22b3.jpg differ diff --git a/public/assets/source/asset-036-60d6309202.jpg b/public/assets/source/asset-036-60d6309202.jpg new file mode 100644 index 0000000..10ac4a2 Binary files /dev/null and b/public/assets/source/asset-036-60d6309202.jpg differ diff --git a/public/assets/source/asset-037-b6dee37086.jpg b/public/assets/source/asset-037-b6dee37086.jpg new file mode 100644 index 0000000..7c006a9 Binary files /dev/null and b/public/assets/source/asset-037-b6dee37086.jpg differ diff --git a/public/assets/source/asset-038-90f983ea37.jpg b/public/assets/source/asset-038-90f983ea37.jpg new file mode 100644 index 0000000..a89fb16 Binary files /dev/null and b/public/assets/source/asset-038-90f983ea37.jpg differ diff --git a/public/assets/source/asset-039-a02a731cec.jpg b/public/assets/source/asset-039-a02a731cec.jpg new file mode 100644 index 0000000..d99ef2d Binary files /dev/null and b/public/assets/source/asset-039-a02a731cec.jpg differ diff --git a/public/assets/source/asset-040-10c33d1558.jpg b/public/assets/source/asset-040-10c33d1558.jpg new file mode 100644 index 0000000..ec4b01d Binary files /dev/null and b/public/assets/source/asset-040-10c33d1558.jpg differ diff --git a/public/assets/source/asset-041-c5a7b61b99.jpg b/public/assets/source/asset-041-c5a7b61b99.jpg new file mode 100644 index 0000000..c3eb5c8 Binary files /dev/null and b/public/assets/source/asset-041-c5a7b61b99.jpg differ diff --git a/public/assets/source/asset-042-263c1d8958.jpg b/public/assets/source/asset-042-263c1d8958.jpg new file mode 100644 index 0000000..4f1e158 Binary files /dev/null and b/public/assets/source/asset-042-263c1d8958.jpg differ diff --git a/public/assets/source/asset-043-a1bbc0007e.jpg b/public/assets/source/asset-043-a1bbc0007e.jpg new file mode 100644 index 0000000..cd41590 Binary files /dev/null and b/public/assets/source/asset-043-a1bbc0007e.jpg differ diff --git a/public/assets/source/asset-044-80ccc16363.jpg b/public/assets/source/asset-044-80ccc16363.jpg new file mode 100644 index 0000000..9fe4396 Binary files /dev/null and b/public/assets/source/asset-044-80ccc16363.jpg differ diff --git a/public/assets/source/asset-045-014a557086.png b/public/assets/source/asset-045-014a557086.png new file mode 100644 index 0000000..ea44d60 Binary files /dev/null and b/public/assets/source/asset-045-014a557086.png differ diff --git a/public/assets/source/asset-046-5b4ca8173b.png b/public/assets/source/asset-046-5b4ca8173b.png new file mode 100644 index 0000000..63a6db0 Binary files /dev/null and b/public/assets/source/asset-046-5b4ca8173b.png differ diff --git a/public/assets/source/asset-047-14415ef235.jpg b/public/assets/source/asset-047-14415ef235.jpg new file mode 100644 index 0000000..46e987b Binary files /dev/null and b/public/assets/source/asset-047-14415ef235.jpg differ diff --git a/public/assets/source/asset-048-cdb636ea72.jpg b/public/assets/source/asset-048-cdb636ea72.jpg new file mode 100644 index 0000000..5ce3511 Binary files /dev/null and b/public/assets/source/asset-048-cdb636ea72.jpg differ diff --git a/public/assets/source/asset-049-662d6cd220.jpg b/public/assets/source/asset-049-662d6cd220.jpg new file mode 100644 index 0000000..d30baa8 Binary files /dev/null and b/public/assets/source/asset-049-662d6cd220.jpg differ diff --git a/public/assets/source/asset-050-b1dcc1ad8e.jpg b/public/assets/source/asset-050-b1dcc1ad8e.jpg new file mode 100644 index 0000000..d0fec59 Binary files /dev/null and b/public/assets/source/asset-050-b1dcc1ad8e.jpg differ diff --git a/public/assets/source/asset-051-5873b802e1.jpg b/public/assets/source/asset-051-5873b802e1.jpg new file mode 100644 index 0000000..a7b9051 Binary files /dev/null and b/public/assets/source/asset-051-5873b802e1.jpg differ diff --git a/public/assets/source/asset-052-8454c19a8f.jpg b/public/assets/source/asset-052-8454c19a8f.jpg new file mode 100644 index 0000000..0ea69d9 Binary files /dev/null and b/public/assets/source/asset-052-8454c19a8f.jpg differ diff --git a/public/assets/source/asset-053-9298156002.jpg b/public/assets/source/asset-053-9298156002.jpg new file mode 100644 index 0000000..8407efc Binary files /dev/null and b/public/assets/source/asset-053-9298156002.jpg differ diff --git a/public/assets/source/asset-054-c936b44502.jpg b/public/assets/source/asset-054-c936b44502.jpg new file mode 100644 index 0000000..f1cdd81 Binary files /dev/null and b/public/assets/source/asset-054-c936b44502.jpg differ diff --git a/public/assets/source/asset-055-0cd651d9e1.jpg b/public/assets/source/asset-055-0cd651d9e1.jpg new file mode 100644 index 0000000..71e9e8e Binary files /dev/null and b/public/assets/source/asset-055-0cd651d9e1.jpg differ diff --git a/public/assets/source/asset-056-209a8a139f.jpg b/public/assets/source/asset-056-209a8a139f.jpg new file mode 100644 index 0000000..674dab7 Binary files /dev/null and b/public/assets/source/asset-056-209a8a139f.jpg differ diff --git a/public/assets/source/asset-057-7c54fd675c.jpg b/public/assets/source/asset-057-7c54fd675c.jpg new file mode 100644 index 0000000..6eaf2f5 Binary files /dev/null and b/public/assets/source/asset-057-7c54fd675c.jpg differ diff --git a/public/assets/source/asset-058-77303eee5e.jpg b/public/assets/source/asset-058-77303eee5e.jpg new file mode 100644 index 0000000..39b5b2b Binary files /dev/null and b/public/assets/source/asset-058-77303eee5e.jpg differ diff --git a/public/assets/source/asset-059-a8a6b2886f.jpg b/public/assets/source/asset-059-a8a6b2886f.jpg new file mode 100644 index 0000000..7157652 Binary files /dev/null and b/public/assets/source/asset-059-a8a6b2886f.jpg differ diff --git a/public/assets/source/asset-060-4906700f3d.jpg b/public/assets/source/asset-060-4906700f3d.jpg new file mode 100644 index 0000000..d5cc59c Binary files /dev/null and b/public/assets/source/asset-060-4906700f3d.jpg differ diff --git a/public/assets/source/asset-061-34916cd0eb.jpg b/public/assets/source/asset-061-34916cd0eb.jpg new file mode 100644 index 0000000..b387f42 Binary files /dev/null and b/public/assets/source/asset-061-34916cd0eb.jpg differ diff --git a/public/assets/source/asset-062-d0571e4731.jpg b/public/assets/source/asset-062-d0571e4731.jpg new file mode 100644 index 0000000..96c360a Binary files /dev/null and b/public/assets/source/asset-062-d0571e4731.jpg differ diff --git a/public/assets/source/asset-063-b8f12a7d43.jpg b/public/assets/source/asset-063-b8f12a7d43.jpg new file mode 100644 index 0000000..754dcf9 Binary files /dev/null and b/public/assets/source/asset-063-b8f12a7d43.jpg differ diff --git a/public/assets/source/asset-064-3f85c3a4e7.jpg b/public/assets/source/asset-064-3f85c3a4e7.jpg new file mode 100644 index 0000000..9798891 Binary files /dev/null and b/public/assets/source/asset-064-3f85c3a4e7.jpg differ diff --git a/public/assets/source/asset-065-48df4407bc.jpg b/public/assets/source/asset-065-48df4407bc.jpg new file mode 100644 index 0000000..0b5ee57 Binary files /dev/null and b/public/assets/source/asset-065-48df4407bc.jpg differ diff --git a/public/assets/source/asset-066-a3f2f1272a.jpg b/public/assets/source/asset-066-a3f2f1272a.jpg new file mode 100644 index 0000000..7b5ce3f Binary files /dev/null and b/public/assets/source/asset-066-a3f2f1272a.jpg differ diff --git a/public/assets/source/asset-067-447266ec79.jpg b/public/assets/source/asset-067-447266ec79.jpg new file mode 100644 index 0000000..5dd12c9 Binary files /dev/null and b/public/assets/source/asset-067-447266ec79.jpg differ diff --git a/public/assets/source/asset-068-3d774150b9.jpg b/public/assets/source/asset-068-3d774150b9.jpg new file mode 100644 index 0000000..c8b0347 Binary files /dev/null and b/public/assets/source/asset-068-3d774150b9.jpg differ diff --git a/public/assets/source/asset-069-90ae10dc55.jpg b/public/assets/source/asset-069-90ae10dc55.jpg new file mode 100644 index 0000000..035c350 Binary files /dev/null and b/public/assets/source/asset-069-90ae10dc55.jpg differ diff --git a/public/assets/source/asset-070-2d9f8846d1.jpg b/public/assets/source/asset-070-2d9f8846d1.jpg new file mode 100644 index 0000000..eea2692 Binary files /dev/null and b/public/assets/source/asset-070-2d9f8846d1.jpg differ diff --git a/public/assets/source/asset-071-1cd6938258.jpg b/public/assets/source/asset-071-1cd6938258.jpg new file mode 100644 index 0000000..feafa05 Binary files /dev/null and b/public/assets/source/asset-071-1cd6938258.jpg differ diff --git a/public/assets/source/asset-072-65f094370f.jpg b/public/assets/source/asset-072-65f094370f.jpg new file mode 100644 index 0000000..0e55c60 Binary files /dev/null and b/public/assets/source/asset-072-65f094370f.jpg differ diff --git a/public/assets/source/asset-073-50718eaa04.jpg b/public/assets/source/asset-073-50718eaa04.jpg new file mode 100644 index 0000000..1a98783 Binary files /dev/null and b/public/assets/source/asset-073-50718eaa04.jpg differ diff --git a/public/assets/source/asset-074-27e8eeb978.jpg b/public/assets/source/asset-074-27e8eeb978.jpg new file mode 100644 index 0000000..55d20e2 Binary files /dev/null and b/public/assets/source/asset-074-27e8eeb978.jpg differ diff --git a/public/assets/source/asset-075-21da2203d7.jpg b/public/assets/source/asset-075-21da2203d7.jpg new file mode 100644 index 0000000..566891c Binary files /dev/null and b/public/assets/source/asset-075-21da2203d7.jpg differ diff --git a/public/assets/source/asset-076-df483f164e.jpg b/public/assets/source/asset-076-df483f164e.jpg new file mode 100644 index 0000000..c260204 Binary files /dev/null and b/public/assets/source/asset-076-df483f164e.jpg differ diff --git a/public/assets/source/asset-077-10074b62a0.jpg b/public/assets/source/asset-077-10074b62a0.jpg new file mode 100644 index 0000000..c96de1b Binary files /dev/null and b/public/assets/source/asset-077-10074b62a0.jpg differ diff --git a/public/assets/source/asset-078-5a8b1d3faf.jpg b/public/assets/source/asset-078-5a8b1d3faf.jpg new file mode 100644 index 0000000..9d25bb3 Binary files /dev/null and b/public/assets/source/asset-078-5a8b1d3faf.jpg differ diff --git a/public/assets/source/asset-079-b87c2aebbc.jpg b/public/assets/source/asset-079-b87c2aebbc.jpg new file mode 100644 index 0000000..c91e3bb Binary files /dev/null and b/public/assets/source/asset-079-b87c2aebbc.jpg differ diff --git a/public/assets/source/asset-080-e26f5fd1af.jpg b/public/assets/source/asset-080-e26f5fd1af.jpg new file mode 100644 index 0000000..cce74cb Binary files /dev/null and b/public/assets/source/asset-080-e26f5fd1af.jpg differ diff --git a/public/assets/source/asset-081-f8ed32e871.jpg b/public/assets/source/asset-081-f8ed32e871.jpg new file mode 100644 index 0000000..306a25a Binary files /dev/null and b/public/assets/source/asset-081-f8ed32e871.jpg differ diff --git a/public/assets/source/asset-082-7265272fc7.jpg b/public/assets/source/asset-082-7265272fc7.jpg new file mode 100644 index 0000000..9ed410a Binary files /dev/null and b/public/assets/source/asset-082-7265272fc7.jpg differ diff --git a/public/assets/source/asset-083-5a645cf860.jpg b/public/assets/source/asset-083-5a645cf860.jpg new file mode 100644 index 0000000..88ec008 Binary files /dev/null and b/public/assets/source/asset-083-5a645cf860.jpg differ diff --git a/public/assets/source/asset-084-e9e0000a3f.jpg b/public/assets/source/asset-084-e9e0000a3f.jpg new file mode 100644 index 0000000..4415295 Binary files /dev/null and b/public/assets/source/asset-084-e9e0000a3f.jpg differ diff --git a/public/assets/source/asset-085-bb1263df56.jpg b/public/assets/source/asset-085-bb1263df56.jpg new file mode 100644 index 0000000..a04f228 Binary files /dev/null and b/public/assets/source/asset-085-bb1263df56.jpg differ diff --git a/public/assets/source/asset-086-8a8d225934.jpg b/public/assets/source/asset-086-8a8d225934.jpg new file mode 100644 index 0000000..c3d4fb6 Binary files /dev/null and b/public/assets/source/asset-086-8a8d225934.jpg differ diff --git a/public/assets/source/asset-087-b290f942f6.jpg b/public/assets/source/asset-087-b290f942f6.jpg new file mode 100644 index 0000000..505e6f7 Binary files /dev/null and b/public/assets/source/asset-087-b290f942f6.jpg differ diff --git a/public/assets/source/asset-088-4836acf6df.jpg b/public/assets/source/asset-088-4836acf6df.jpg new file mode 100644 index 0000000..9807b06 Binary files /dev/null and b/public/assets/source/asset-088-4836acf6df.jpg differ diff --git a/public/assets/source/asset-089-970bccb170.jpg b/public/assets/source/asset-089-970bccb170.jpg new file mode 100644 index 0000000..6f50363 Binary files /dev/null and b/public/assets/source/asset-089-970bccb170.jpg differ diff --git a/public/assets/source/asset-090-94e7f5f26f.png b/public/assets/source/asset-090-94e7f5f26f.png new file mode 100644 index 0000000..5185131 Binary files /dev/null and b/public/assets/source/asset-090-94e7f5f26f.png differ diff --git a/public/assets/source/asset-091-9dcce2d1f9.jpg b/public/assets/source/asset-091-9dcce2d1f9.jpg new file mode 100644 index 0000000..7bfe00b Binary files /dev/null and b/public/assets/source/asset-091-9dcce2d1f9.jpg differ diff --git a/public/assets/source/asset-092-6d447dfa4e.jpg b/public/assets/source/asset-092-6d447dfa4e.jpg new file mode 100644 index 0000000..69ac7ce Binary files /dev/null and b/public/assets/source/asset-092-6d447dfa4e.jpg differ diff --git a/public/assets/source/asset-093-1d8b7d743b.jpg b/public/assets/source/asset-093-1d8b7d743b.jpg new file mode 100644 index 0000000..77d8755 Binary files /dev/null and b/public/assets/source/asset-093-1d8b7d743b.jpg differ diff --git a/public/assets/source/asset-094-506564ecb7.jpg b/public/assets/source/asset-094-506564ecb7.jpg new file mode 100644 index 0000000..32149f3 Binary files /dev/null and b/public/assets/source/asset-094-506564ecb7.jpg differ diff --git a/public/assets/source/asset-095-bd37c3754d.jpg b/public/assets/source/asset-095-bd37c3754d.jpg new file mode 100644 index 0000000..b6efae8 Binary files /dev/null and b/public/assets/source/asset-095-bd37c3754d.jpg differ diff --git a/public/assets/source/manifest.json b/public/assets/source/manifest.json new file mode 100644 index 0000000..7f6140c --- /dev/null +++ b/public/assets/source/manifest.json @@ -0,0 +1,869 @@ +{ + "generatedAt": "2026-06-25T03:21:05.867Z", + "assets": [ + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301k12000smvj7fr86BF_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 170388, + "filename": "asset-000-6130f5faf6.jpg", + "publicPath": "/assets/source/asset-000-6130f5faf6.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305t12000sb9yawgA680_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 212456, + "filename": "asset-001-e3ccdf0858.jpg", + "publicPath": "/assets/source/asset-001-e3ccdf0858.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0304812000slb8alx3F3F_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 91178, + "filename": "asset-002-60f1781bad.jpg", + "publicPath": "/assets/source/asset-002-60f1781bad.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0304p12000smeitomC5EE_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 284254, + "filename": "asset-003-67c94551ff.jpg", + "publicPath": "/assets/source/asset-003-67c94551ff.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303l12000smeikth375E_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 271294, + "filename": "asset-004-74801a9861.jpg", + "publicPath": "/assets/source/asset-004-74801a9861.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306v12000smeini5EBB8_Q80.jpg?proc=format%2Ff_webp%3Bcorp%2Ft163", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 241168, + "filename": "asset-005-5f88616344.jpg", + "publicPath": "/assets/source/asset-005-5f88616344.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300l12000izkd2vhADDB_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 3098, + "filename": "asset-006-065f65e89a.png", + "publicPath": "/assets/source/asset-006-065f65e89a.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300y12000izkd78l8AD7_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 1638, + "filename": "asset-007-bfd23e4423.png", + "publicPath": "/assets/source/asset-007-bfd23e4423.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300412000izkedzdBEE1_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 2026, + "filename": "asset-008-b69a2ebdf1.png", + "publicPath": "/assets/source/asset-008-b69a2ebdf1.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305k12000j0ka6gt8771_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 882, + "filename": "asset-009-7c60387849.png", + "publicPath": "/assets/source/asset-009-7c60387849.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303712000izkddq5FD05_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 1938, + "filename": "asset-010-8614b34e80.png", + "publicPath": "/assets/source/asset-010-8614b34e80.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305h12000sorwqyf8CF6_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 8248, + "filename": "asset-011-398553c792.png", + "publicPath": "/assets/source/asset-011-398553c792.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301412000sorwcsjFC42_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 7998, + "filename": "asset-012-486ac8f1a1.png", + "publicPath": "/assets/source/asset-012-486ac8f1a1.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0304n12000sorx2ot95E2_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 10282, + "filename": "asset-013-babb337ba2.png", + "publicPath": "/assets/source/asset-013-babb337ba2.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301g12000sorxk921BDB_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 9266, + "filename": "asset-014-fd57beac6b.png", + "publicPath": "/assets/source/asset-014-fd57beac6b.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0302y12000sorxrncD367_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 10524, + "filename": "asset-015-94835fc601.png", + "publicPath": "/assets/source/asset-015-94835fc601.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0304s12000soryjcp2123_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 6178, + "filename": "asset-016-306236a4af.png", + "publicPath": "/assets/source/asset-016-306236a4af.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306h12000sory9nl55CA_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 10098, + "filename": "asset-017-e4a0e59bd4.png", + "publicPath": "/assets/source/asset-017-e4a0e59bd4.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303p12000f9vpbs2E0CC_Q80.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 13620, + "filename": "asset-018-30bc451049.jpg", + "publicPath": "/assets/source/asset-018-30bc451049.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306e12000kwyklve849A_Q80.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 12854, + "filename": "asset-019-8b7f71737e.jpg", + "publicPath": "/assets/source/asset-019-8b7f71737e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301c12000j0jomub6A63_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 11474, + "filename": "asset-020-5207a45c09.png", + "publicPath": "/assets/source/asset-020-5207a45c09.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300i12000j0jowt4F1F0_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 12382, + "filename": "asset-021-267958c0d6.png", + "publicPath": "/assets/source/asset-021-267958c0d6.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303t12000irw2m6tBEB9_Q80.png?proc=format%2Ff_webp%3Bresize%2Fm_c%2Cw_1125%2Ch_858%3Bcrop%2Fm_c%2Cw_1125%2Ch_450%2Cx_0%2Cy_0%2CF1B8", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 30698, + "filename": "asset-022-f982dcf65e.png", + "publicPath": "/assets/source/asset-022-f982dcf65e.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303t12000irw2m6tBEB9_Q80.png?proc=format%2Ff_webp%3Bresize%2Fm_c%2Cw_1125%2Ch_858%3Bcrop%2Fm_c%2Cw_1125%2Ch_450%2Cx_0%2Cy_450%2CE74F", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 18064, + "filename": "asset-023-20ebf544be.png", + "publicPath": "/assets/source/asset-023-20ebf544be.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/1tw2s12000ii40grfF6DE.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 1471, + "filename": "asset-024-6e6ee3998c.png", + "publicPath": "/assets/source/asset-024-6e6ee3998c.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/1tw6x12000ii41qst9B8C.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 1139, + "filename": "asset-025-980c7ac168.png", + "publicPath": "/assets/source/asset-025-980c7ac168.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/1tw1d12000ii3zremDD90.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 497, + "filename": "asset-026-6e4d686b97.png", + "publicPath": "/assets/source/asset-026-6e4d686b97.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/1tw2q12000ii3z57n9203.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 703, + "filename": "asset-027-22aef1d83b.png", + "publicPath": "/assets/source/asset-027-22aef1d83b.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300512000irztxfa30DB_Q80.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 68584, + "filename": "asset-028-912019b131.png", + "publicPath": "/assets/source/asset-028-912019b131.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306y12000irytaffD93C_Q80.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 67709, + "filename": "asset-029-9eccbb7131.png", + "publicPath": "/assets/source/asset-029-9eccbb7131.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306v12000s0usa7gF64D_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 12465, + "filename": "asset-030-a77d1ef23a.jpg", + "publicPath": "/assets/source/asset-030-a77d1ef23a.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300p12000q01uwt3ECA7_C_265_265_Q80.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 161758, + "filename": "asset-031-802555e02b.png", + "publicPath": "/assets/source/asset-031-802555e02b.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305a12000s38bp9m41BB_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 16056, + "filename": "asset-032-1257cf8865.jpg", + "publicPath": "/assets/source/asset-032-1257cf8865.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306412000smvvyxs06F1_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 13094, + "filename": "asset-033-4f39bbaba6.jpg", + "publicPath": "/assets/source/asset-033-4f39bbaba6.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305712000irzq2lgA6B7_C_265_265_Q80.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 152439, + "filename": "asset-034-393944f522.png", + "publicPath": "/assets/source/asset-034-393944f522.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301612000s0uuuet873F_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 13779, + "filename": "asset-035-9d6bac22b3.jpg", + "publicPath": "/assets/source/asset-035-9d6bac22b3.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301712000rqs98cfB2AD_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 11943, + "filename": "asset-036-60d6309202.jpg", + "publicPath": "/assets/source/asset-036-60d6309202.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305h12000rqs8te62765_C_265_265.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 14173, + "filename": "asset-037-b6dee37086.jpg", + "publicPath": "/assets/source/asset-037-b6dee37086.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301412000krauoilC5D4.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 16470, + "filename": "asset-038-90f983ea37.jpg", + "publicPath": "/assets/source/asset-038-90f983ea37.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0307412000qv8ly1g77F7.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 11538, + "filename": "asset-039-a02a731cec.jpg", + "publicPath": "/assets/source/asset-039-a02a731cec.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305112000krauo6x3C1E.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 13756, + "filename": "asset-040-10c33d1558.jpg", + "publicPath": "/assets/source/asset-040-10c33d1558.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306q12000slbab6t7D10.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 11232, + "filename": "asset-041-c5a7b61b99.jpg", + "publicPath": "/assets/source/asset-041-c5a7b61b99.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0302h12000s6djf1bB21E.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 12106, + "filename": "asset-042-263c1d8958.jpg", + "publicPath": "/assets/source/asset-042-263c1d8958.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300q12000qfa9e5t4957.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 13112, + "filename": "asset-043-a1bbc0007e.jpg", + "publicPath": "/assets/source/asset-043-a1bbc0007e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0306312000kraurrg4784.jpg?proc=format%2Ff_webp%3Bresize%2Fm_w%2Cw_258%2Ch_541%2CFE25", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 17192, + "filename": "asset-044-80ccc16363.jpg", + "publicPath": "/assets/source/asset-044-80ccc16363.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303g12000irmavs698BD_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 38990, + "filename": "asset-045-014a557086.png", + "publicPath": "/assets/source/asset-045-014a557086.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305512000j0hhbvn7E21_Q80.png?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 766, + "filename": "asset-046-5b4ca8173b.png", + "publicPath": "/assets/source/asset-046-5b4ca8173b.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C071e000001fmkft5B48_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 90922, + "filename": "asset-047-14415ef235.jpg", + "publicPath": "/assets/source/asset-047-14415ef235.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C5r12000jchfwqqCBA0_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 27202, + "filename": "asset-048-cdb636ea72.jpg", + "publicPath": "/assets/source/asset-048-cdb636ea72.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0612000sb7jwk12CFB_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 31806, + "filename": "asset-049-662d6cd220.jpg", + "publicPath": "/assets/source/asset-049-662d6cd220.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C4d12000cs3gt370743_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 34978, + "filename": "asset-050-b1dcc1ad8e.jpg", + "publicPath": "/assets/source/asset-050-b1dcc1ad8e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C0k1e000001fakda86E2_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 36024, + "filename": "asset-051-5873b802e1.jpg", + "publicPath": "/assets/source/asset-051-5873b802e1.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6j12000bttek05089C_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 14534, + "filename": "asset-052-8454c19a8f.jpg", + "publicPath": "/assets/source/asset-052-8454c19a8f.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0k12000a65nz928183_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 27006, + "filename": "asset-053-9298156002.jpg", + "publicPath": "/assets/source/asset-053-9298156002.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6w12000s96wqaf365D_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 49792, + "filename": "asset-054-c936b44502.jpg", + "publicPath": "/assets/source/asset-054-c936b44502.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C5n12000rxi0cdaF5A6_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 24212, + "filename": "asset-055-0cd651d9e1.jpg", + "publicPath": "/assets/source/asset-055-0cd651d9e1.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0712000ctfu55q089F_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 32388, + "filename": "asset-056-209a8a139f.jpg", + "publicPath": "/assets/source/asset-056-209a8a139f.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3812000rf8djcf4023_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 15626, + "filename": "asset-057-7c54fd675c.jpg", + "publicPath": "/assets/source/asset-057-7c54fd675c.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6z120009v8609gE0D1_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 15670, + "filename": "asset-058-77303eee5e.jpg", + "publicPath": "/assets/source/asset-058-77303eee5e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1j12000resqsm3DE93_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 51726, + "filename": "asset-059-a8a6b2886f.jpg", + "publicPath": "/assets/source/asset-059-a8a6b2886f.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6s12000s7x1adoA25F_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 51990, + "filename": "asset-060-4906700f3d.jpg", + "publicPath": "/assets/source/asset-060-4906700f3d.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C0w1e000001f33gd0E57_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 33882, + "filename": "asset-061-34916cd0eb.jpg", + "publicPath": "/assets/source/asset-061-34916cd0eb.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0s120009mut1bo4227_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 18032, + "filename": "asset-062-d0571e4731.jpg", + "publicPath": "/assets/source/asset-062-d0571e4731.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0u12000rt3ga88866B_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 50592, + "filename": "asset-063-b8f12a7d43.jpg", + "publicPath": "/assets/source/asset-063-b8f12a7d43.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6512000pjf6rgp9B46_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 73024, + "filename": "asset-064-3f85c3a4e7.jpg", + "publicPath": "/assets/source/asset-064-3f85c3a4e7.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C44120009np9rh540C0_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 24688, + "filename": "asset-065-48df4407bc.jpg", + "publicPath": "/assets/source/asset-065-48df4407bc.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0z12000sg0gi4u734E_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 29600, + "filename": "asset-066-a3f2f1272a.jpg", + "publicPath": "/assets/source/asset-066-a3f2f1272a.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3p12000sjhokqgE5A4_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 23620, + "filename": "asset-067-447266ec79.jpg", + "publicPath": "/assets/source/asset-067-447266ec79.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1112000rzwt90j3652_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 45030, + "filename": "asset-068-3d774150b9.jpg", + "publicPath": "/assets/source/asset-068-3d774150b9.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3912000r7u3e6d7AFC_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 30012, + "filename": "asset-069-90ae10dc55.jpg", + "publicPath": "/assets/source/asset-069-90ae10dc55.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C4v12000sny387lAC23_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 64618, + "filename": "asset-070-2d9f8846d1.jpg", + "publicPath": "/assets/source/asset-070-2d9f8846d1.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C2012000f6dg8h09EDD_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 17094, + "filename": "asset-071-1cd6938258.jpg", + "publicPath": "/assets/source/asset-071-1cd6938258.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1x12000rx4uno998FF_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 42246, + "filename": "asset-072-65f094370f.jpg", + "publicPath": "/assets/source/asset-072-65f094370f.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C4s12000espf8b9A9F7_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 48174, + "filename": "asset-073-50718eaa04.jpg", + "publicPath": "/assets/source/asset-073-50718eaa04.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C2n12000bk6ypzh1B0A_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 36482, + "filename": "asset-074-27e8eeb978.jpg", + "publicPath": "/assets/source/asset-074-27e8eeb978.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1w12000pkzp4vrE479_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 16582, + "filename": "asset-075-21da2203d7.jpg", + "publicPath": "/assets/source/asset-075-21da2203d7.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3112000cl7p7d8965C_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 20602, + "filename": "asset-076-df483f164e.jpg", + "publicPath": "/assets/source/asset-076-df483f164e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C0d1e000001ewqeaAD63_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 27194, + "filename": "asset-077-10074b62a0.jpg", + "publicPath": "/assets/source/asset-077-10074b62a0.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C5g12000dn1gi668F11_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 13926, + "filename": "asset-078-5a8b1d3faf.jpg", + "publicPath": "/assets/source/asset-078-5a8b1d3faf.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3h12000hm73r7kF70A_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 22920, + "filename": "asset-079-b87c2aebbc.jpg", + "publicPath": "/assets/source/asset-079-b87c2aebbc.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1812000hzlez253E06_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 41918, + "filename": "asset-080-e26f5fd1af.jpg", + "publicPath": "/assets/source/asset-080-e26f5fd1af.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C6p12000f1qtq7l0D78_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 41134, + "filename": "asset-081-f8ed32e871.jpg", + "publicPath": "/assets/source/asset-081-f8ed32e871.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C0z12000rwkb013F312_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 30154, + "filename": "asset-082-7265272fc7.jpg", + "publicPath": "/assets/source/asset-082-7265272fc7.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C3o12000atd38jj839B_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 33950, + "filename": "asset-083-5a645cf860.jpg", + "publicPath": "/assets/source/asset-083-5a645cf860.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C5d12000rbyq0eeA5CB_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 38516, + "filename": "asset-084-e9e0000a3f.jpg", + "publicPath": "/assets/source/asset-084-e9e0000a3f.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C5q12000qysig6h1197_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 27044, + "filename": "asset-085-bb1263df56.jpg", + "publicPath": "/assets/source/asset-085-bb1263df56.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C0j1e000001exo9f0BDE_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 27422, + "filename": "asset-086-8a8d225934.jpg", + "publicPath": "/assets/source/asset-086-8a8d225934.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C1212000seh6r0k668B_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 39556, + "filename": "asset-087-b290f942f6.jpg", + "publicPath": "/assets/source/asset-087-b290f942f6.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/03C2r12000azwjkm83256_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 32184, + "filename": "asset-088-4836acf6df.jpg", + "publicPath": "/assets/source/asset-088-4836acf6df.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/3C091e000001f085j4CB6_C_548_348.jpg?proc=format%2Ff_webp", + "ok": true, + "status": 200, + "contentType": "image/webp", + "bytes": 34382, + "filename": "asset-089-970bccb170.jpg", + "publicPath": "/assets/source/asset-089-970bccb170.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0301512000iru41stFB96_Q80.png", + "ok": true, + "status": 200, + "contentType": "image/png", + "bytes": 679975, + "filename": "asset-090-94e7f5f26f.png", + "publicPath": "/assets/source/asset-090-94e7f5f26f.png" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0300d12000jwawcibEF59.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 154367, + "filename": "asset-091-9dcce2d1f9.jpg", + "publicPath": "/assets/source/asset-091-9dcce2d1f9.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0304c12000j1294oeB9BC.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 143987, + "filename": "asset-092-6d447dfa4e.jpg", + "publicPath": "/assets/source/asset-092-6d447dfa4e.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303p12000j12afin35D8.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 179667, + "filename": "asset-093-1d8b7d743b.jpg", + "publicPath": "/assets/source/asset-093-1d8b7d743b.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0303e12000j2nt1i62B74.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 169045, + "filename": "asset-094-506564ecb7.jpg", + "publicPath": "/assets/source/asset-094-506564ecb7.jpg" + }, + { + "originalUrl": "https://dimg04.c-ctrip.com/images/0305h12000msz4nbxC853.jpg", + "ok": true, + "status": 200, + "contentType": "image/jpeg", + "bytes": 424627, + "filename": "asset-095-bd37c3754d.jpg", + "publicPath": "/assets/source/asset-095-bd37c3754d.jpg" + } + ] +} \ No newline at end of file diff --git a/public/assets/source/secondary-bali-2de0f08eaa.jpg b/public/assets/source/secondary-bali-2de0f08eaa.jpg new file mode 100644 index 0000000..1d51bde Binary files /dev/null and b/public/assets/source/secondary-bali-2de0f08eaa.jpg differ diff --git a/public/assets/source/secondary-domestic-icon-a263c9ffe7.png b/public/assets/source/secondary-domestic-icon-a263c9ffe7.png new file mode 100644 index 0000000..e9ccb4b Binary files /dev/null and b/public/assets/source/secondary-domestic-icon-a263c9ffe7.png differ diff --git a/public/assets/source/secondary-fiji-b742a38ceb.jpg b/public/assets/source/secondary-fiji-b742a38ceb.jpg new file mode 100644 index 0000000..d5e5848 Binary files /dev/null and b/public/assets/source/secondary-fiji-b742a38ceb.jpg differ diff --git a/public/assets/source/secondary-france-41bf852d5e.jpg b/public/assets/source/secondary-france-41bf852d5e.jpg new file mode 100644 index 0000000..071bd22 Binary files /dev/null and b/public/assets/source/secondary-france-41bf852d5e.jpg differ diff --git a/public/assets/source/secondary-gansu-0a92435395.jpg b/public/assets/source/secondary-gansu-0a92435395.jpg new file mode 100644 index 0000000..d8f0589 Binary files /dev/null and b/public/assets/source/secondary-gansu-0a92435395.jpg differ diff --git a/public/assets/source/secondary-kenya-1c2c61d13a.jpg b/public/assets/source/secondary-kenya-1c2c61d13a.jpg new file mode 100644 index 0000000..1da8380 Binary files /dev/null and b/public/assets/source/secondary-kenya-1c2c61d13a.jpg differ diff --git a/public/assets/source/secondary-maldives-283b20785e.jpg b/public/assets/source/secondary-maldives-283b20785e.jpg new file mode 100644 index 0000000..47e4afd Binary files /dev/null and b/public/assets/source/secondary-maldives-283b20785e.jpg differ diff --git a/public/assets/source/secondary-norway-524e1598a1.jpg b/public/assets/source/secondary-norway-524e1598a1.jpg new file mode 100644 index 0000000..c180850 Binary files /dev/null and b/public/assets/source/secondary-norway-524e1598a1.jpg differ diff --git a/public/assets/source/secondary-outbound-icon-560ad58d3c.png b/public/assets/source/secondary-outbound-icon-560ad58d3c.png new file mode 100644 index 0000000..b787f84 Binary files /dev/null and b/public/assets/source/secondary-outbound-icon-560ad58d3c.png differ diff --git a/public/assets/source/secondary-saudi-1b47ef583a.jpg b/public/assets/source/secondary-saudi-1b47ef583a.jpg new file mode 100644 index 0000000..cc6682d Binary files /dev/null and b/public/assets/source/secondary-saudi-1b47ef583a.jpg differ diff --git a/public/assets/source/secondary-singapore-bcda6d9f5f.jpg b/public/assets/source/secondary-singapore-bcda6d9f5f.jpg new file mode 100644 index 0000000..2f51332 Binary files /dev/null and b/public/assets/source/secondary-singapore-bcda6d9f5f.jpg differ diff --git a/public/assets/source/secondary-srilanka-659c7fa697.jpg b/public/assets/source/secondary-srilanka-659c7fa697.jpg new file mode 100644 index 0000000..e668ea3 Binary files /dev/null and b/public/assets/source/secondary-srilanka-659c7fa697.jpg differ diff --git a/public/assets/source/secondary-tanzania-d607ec8990.jpg b/public/assets/source/secondary-tanzania-d607ec8990.jpg new file mode 100644 index 0000000..66ffbd0 Binary files /dev/null and b/public/assets/source/secondary-tanzania-d607ec8990.jpg differ diff --git a/public/assets/source/secondary-thailand-e9aa2cae4e.jpg b/public/assets/source/secondary-thailand-e9aa2cae4e.jpg new file mode 100644 index 0000000..b312ab7 Binary files /dev/null and b/public/assets/source/secondary-thailand-e9aa2cae4e.jpg differ diff --git a/public/assets/source/secondary-uk-37d1647a7d.jpg b/public/assets/source/secondary-uk-37d1647a7d.jpg new file mode 100644 index 0000000..b04b18f Binary files /dev/null and b/public/assets/source/secondary-uk-37d1647a7d.jpg differ diff --git a/public/assets/source/secondary-usa-fa4e4594e1.jpg b/public/assets/source/secondary-usa-fa4e4594e1.jpg new file mode 100644 index 0000000..ba012fc Binary files /dev/null and b/public/assets/source/secondary-usa-fa4e4594e1.jpg differ diff --git a/public/assets/source/secondary-xinjiang-7bdff060a5.jpg b/public/assets/source/secondary-xinjiang-7bdff060a5.jpg new file mode 100644 index 0000000..487e564 Binary files /dev/null and b/public/assets/source/secondary-xinjiang-7bdff060a5.jpg differ diff --git a/public/assets/source/secondary-yunnan-cce4700549.png b/public/assets/source/secondary-yunnan-cce4700549.png new file mode 100644 index 0000000..9c27c88 Binary files /dev/null and b/public/assets/source/secondary-yunnan-cce4700549.png differ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..2982312 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,1353 @@ +import { FormEvent, ReactNode, useEffect, useMemo, useState } from "react"; +import { + AlertTriangle, + ArrowLeft, + Blocks, + ChevronRight, + CheckCircle2, + Eye, + FileText, + Home, + Image as ImageIcon, + LogOut, + MapPin, + Package, + Plus, + RefreshCcw, + Save, + Send, + Trash2, + X, + UserRoundCheck, +} from "lucide-react"; +import { + clearToken, + createProduct, + Destination, + getDestinations, + getLeads, + getProducts, + getSiteConfig, + getToken, + Lead, + login, + Product, + ProductDetailBlock, + ProductDetailSection, + ProductInput, + publishSite, + resetGuizhouContent, + setToken, + SiteConfig, + SiteItemPatch, + SiteModule, + updateLeadStatus, + updateProduct, + updateSiteConfigItem, +} from "./api"; + +type Tab = "structure" | "home" | "destinations" | "products" | "leads"; +type PageId = "home" | "destination" | "detail" | "campaign" | "demand"; +type ModuleId = SiteModule | "routeProducts" | "campaignProducts" | "leadFlow"; +type Toast = { id: string; tone: "success" | "warning" | "danger"; title: string; message: string }; + +type EditableSiteItem = SiteConfig["heroSlides"][number] | SiteConfig["destinations"][number] | SiteConfig["themes"][number] | SiteConfig["ctaBanners"][number]; + +const sidebarGroups: { id: string; label: string; items: { id: Tab; label: string; icon: typeof Home; description: string }[] }[] = [ + { + id: "workspace", + label: "工作台", + items: [{ id: "structure", label: "维护地图", icon: Blocks, description: "前台结构总览" }], + }, + { + id: "frontend", + label: "前台页面", + items: [ + { id: "home", label: "小程序首页", icon: Home, description: "轮播、主题、CTA" }, + { id: "destinations", label: "目的地页", icon: MapPin, description: "字典、搜索、商品" }, + { id: "products", label: "商品维护", icon: Package, description: "列表、卡片、详情" }, + ], + }, + { + id: "operations", + label: "业务处理", + items: [{ id: "leads", label: "需求线索", icon: UserRoundCheck, description: "表单和跟进" }], + }, +]; + +const tabs = sidebarGroups.flatMap((group) => group.items); + +const leadStatusLabels: Record = { + new: "新线索", + assigned: "已分配", + contacted: "已联系", + planning: "方案中", + won: "已成交", + invalid: "无效", +}; + +const pageSpecs: { + id: PageId; + title: string; + subtitle: string; + frontPath: string; + modules: { id: ModuleId; label: string; hint: string; frontPosition: string }[]; +}[] = [ + { + id: "home", + title: "万趣首页", + subtitle: "维护用户进入小程序后第一屏到页尾的内容顺序。", + frontPath: "前台:首页 / bottom tab「万趣」", + modules: [ + { id: "heroSlides", label: "顶部轮播", hint: "控制首屏大图、活动入口和主视觉顺序。", frontPosition: "首页第一屏" }, + { id: "destinations", label: "探索贵州", hint: "控制省内目的地宫格、热门标记和搜索入口。", frontPosition: "首页「探索贵州」" }, + { id: "themes", label: "主题甄选", hint: "控制横向主题卡和搜索跳转。", frontPosition: "首页「主题甄选」" }, + { id: "routeProducts", label: "精选线路", hint: "从商品库选择上架线路,决定首页线路池。", frontPosition: "首页精选线路" }, + { id: "ctaBanners", label: "底部运营入口", hint: "控制权益卡、服务管家入口、目的地搜索、提交需求入口。", frontPosition: "首页页尾 CTA" }, + ], + }, + { + id: "destination", + title: "目的地页", + subtitle: "维护目的地、别名和与商品的关联,影响搜索和目的地 tab。", + frontPath: "前台:bottom tab「目的地」/ 搜索弹层", + modules: [ + { id: "destinations", label: "目的地字典", hint: "维护名称、封面、热门状态和启停。", frontPosition: "目的地首页和搜索列表" }, + { id: "routeProducts", label: "目的地商品", hint: "商品绑定目的地后会进入对应结果页。", frontPosition: "搜索结果页产品列表" }, + ], + }, + { + id: "detail", + title: "商品维护", + subtitle: "维护用户点击商品卡后的详情页内容和售卖状态。", + frontPath: "前台:产品卡点击后详情页", + modules: [{ id: "routeProducts", label: "商品库", hint: "编辑标题、价格、封面、标签、详情模块和上下架。", frontPosition: "详情页、搜索页、首页商品卡" }], + }, + { + id: "campaign", + title: "活动专题", + subtitle: "维护轮播、主题卡和 CTA 指向的活动内容。", + frontPath: "前台:轮播 / 主题卡 / CTA 跳转", + modules: [ + { id: "heroSlides", label: "轮播活动入口", hint: "首屏主视觉上的活动入口。", frontPosition: "首页轮播" }, + { id: "themes", label: "主题活动入口", hint: "主题卡片进入对应活动专题。", frontPosition: "主题甄选横滑区" }, + { id: "ctaBanners", label: "营销 CTA", hint: "页尾活动和需求入口。", frontPosition: "首页底部运营区" }, + { id: "campaignProducts", label: "活动商品池", hint: "活动专题内展示的商品来自商品库。", frontPosition: "活动专题产品列表" }, + ], + }, + { + id: "demand", + title: "需求线索", + subtitle: "维护用户提交出行需求后的分配和跟进。", + frontPath: "前台:提交出行需求 / 立即咨询", + modules: [{ id: "leadFlow", label: "线索跟进", hint: "查看联系方式、目的地、来源商品并推进状态。", frontPosition: "需求表单提交后" }], + }, +]; + +function formatDate(value: string) { + return new Intl.DateTimeFormat("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(value)); +} + +function toProductInput(product?: Product | null): ProductInput { + const summary = product?.summary || product?.tags?.join(" · ") || "万趣贵州小包团定制体验"; + return { + title: product?.title ?? "", + subtitle: product?.subtitle ?? "", + destinationId: product?.destinationId ?? product?.destination?.id ?? null, + priceAmount: product?.priceAmount ?? null, + priceUnit: product?.priceUnit ?? "起/人", + tags: product?.tags ?? [], + coverImage: product?.coverImage ?? "", + summary: product?.summary ?? "", + images: product?.images?.map((image, index) => ({ + url: image.url, + alt: image.alt ?? "", + sortOrder: image.sortOrder ?? index, + })) ?? [], + detailSections: product?.detailSections?.length + ? product.detailSections + : defaultAdminDetailSections(summary, product?.destination?.name ?? "贵州省内定制"), + status: product?.status ?? "draft", + sortWeight: product?.sortWeight ?? 0, + }; +} + +function defaultAdminDetailSections(summary: string, location: string): ProductDetailSection[] { + return [ + { key: "overview", label: "行程概述", title: "小包团专属概览", blocks: [{ type: "text", text: `${summary}。万趣会按同行人、预算、酒店偏好和体力强度重排细节,保留小车小团、错峰入园与在地向导服务。` }] }, + { key: "itinerary", label: "每日行程", title: `${location} 弹性安排`, blocks: [{ type: "text", text: "默认按抵达接站、核心景点游览、特色体验、酒店休整和返程送站安排每日节奏;具体天数、停留时长和餐食可在行前由服务管家二次确认。" }] }, + { key: "service", label: "包含/不含服务", title: "费用边界清晰", blocks: [{ type: "text", text: "通常包含当地用车、行程内住宿、列明门票/体验、必要讲解和服务管家跟进;大交通、个人消费、未列明餐食和自选项目以最终方案为准。" }] }, + { key: "notice", label: "出行须知", title: "贵州山地旅行提示", blocks: [{ type: "text", text: "贵州多山多雨,建议准备防滑鞋、轻便雨具和薄外套;溶洞、漂流、徒步等体验会按天气和同行人体力调整。" }] }, + { key: "price", label: "价格区间", title: "按人数、酒店和季节报价", blocks: [{ type: "text", text: "页面价格为参考起价,节假日、旺季房态、用车车型和体验资源会影响最终报价;提交需求后由服务管家给出可执行方案。" }] }, + { key: "manager", label: "服务管家", title: "直接添加服务管家", blocks: [{ type: "text", text: "点击底部“服务管家”或拨打 18786174929,可直接添加服务管家沟通出行人数、日期、酒店偏好和预算。" }] }, + ]; +} + +const productStatusLabels: Record = { + draft: "草稿", + published: "已上架", + archived: "已归档", +}; + +const detailSectionTemplates: Pick[] = [ + { key: "overview", label: "行程概述", title: "小包团专属概览" }, + { key: "itinerary", label: "每日行程", title: "弹性安排" }, + { key: "service", label: "包含/不含服务", title: "费用边界清晰" }, + { key: "notice", label: "出行须知", title: "贵州山地旅行提示" }, + { key: "price", label: "价格区间", title: "按人数、酒店和季节报价" }, + { key: "manager", label: "服务管家", title: "直接添加服务管家" }, +]; + +function createEmptyDetailSection(index: number): ProductDetailSection { + const template = detailSectionTemplates[index % detailSectionTemplates.length]; + return { + key: template.key, + label: template.label, + title: template.title, + blocks: [{ type: "text", text: "" }], + }; +} + +function createDetailBlock(type: ProductDetailBlock["type"]): ProductDetailBlock { + return type === "image" ? { type: "image", url: "", alt: "" } : { type: "text", text: "" }; +} + +function compactProductPayload(draft: ProductInput): ProductInput { + return { + ...draft, + title: draft.title.trim(), + subtitle: draft.subtitle?.trim() || "", + priceUnit: draft.priceUnit?.trim() || "起/人", + tags: draft.tags.map((tag) => tag.trim()).filter(Boolean), + images: (draft.images ?? []) + .map((image, index) => ({ + url: image.url.trim(), + alt: image.alt?.trim() || null, + sortOrder: index, + })) + .filter((image) => image.url.length > 0), + detailSections: (draft.detailSections ?? []) + .map((section) => ({ + key: section.key.trim(), + label: section.label.trim(), + title: section.title?.trim() || null, + blocks: section.blocks + .map((block) => + block.type === "image" + ? { type: "image" as const, url: block.url.trim(), alt: block.alt?.trim() || null } + : { type: "text" as const, text: block.text.trim() }, + ) + .filter((block) => (block.type === "image" ? block.url.length > 0 : block.text.length > 0)), + })) + .filter((section) => section.key.length > 0 && section.label.length > 0 && section.blocks.length > 0), + priceAmount: draft.priceAmount === null ? undefined : Number(draft.priceAmount), + destinationId: draft.destinationId || null, + }; +} + +function itemName(item: EditableSiteItem) { + if ("title" in item) return item.title; + if ("name" in item) return item.name; + if ("label" in item) return item.label; + return item.alt; +} + +function itemMeta(item: EditableSiteItem) { + if ("kicker" in item) return item.kicker || "轮播"; + if ("aliases" in item) return item.aliases?.map((alias) => alias.alias).join(" / ") || "目的地"; + if ("targetType" in item) return item.targetType || "跳转未配置"; + return ""; +} + +function itemImage(item: EditableSiteItem) { + return item.image || ""; +} + +function moduleItems(config: SiteConfig, moduleId: SiteModule): EditableSiteItem[] { + return config[moduleId] as EditableSiteItem[]; +} + +function moduleEditable(moduleId: ModuleId): moduleId is SiteModule { + return moduleId === "heroSlides" || moduleId === "destinations" || moduleId === "themes" || moduleId === "ctaBanners"; +} + +function AdminDisclosure({ + title, + description, + meta, + children, + defaultOpen = false, +}: { + title: string; + description: string; + meta: string; + children: ReactNode; + defaultOpen?: boolean; +}) { + return ( +
+ + + {title} + {description} + + + {meta} + + + +
{children}
+
+ ); +} + +function ToastStack({ toasts, onClose }: { toasts: Toast[]; onClose: (id: string) => void }) { + if (!toasts.length) return null; + return ( +
+ {toasts.map((toast) => ( +
+ {toast.tone === "success" ? : } + + {toast.title} + {toast.message} + + +
+ ))} +
+ ); +} + +function LoginScreen({ onDone }: { onDone: () => void }) { + const [email, setEmail] = useState("admin@example.com"); + const [password, setPassword] = useState("ChangeMe123!"); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const submit = async (event: FormEvent) => { + event.preventDefault(); + setLoading(true); + setError(""); + try { + const result = await login(email, password); + setToken(result.token); + onDone(); + } catch (err) { + setError(err instanceof Error ? err.message : "登录失败"); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ 万趣运营后台 +

万趣小程序管理端

+

按前台页面结构维护内容、商品和线索。

+ + + {error ?

{error}

: null} + +
+
+ ); +} + +function StructureWorkbench({ + fixedPage, + onJump, + onDirtyChange, + notify, +}: { + fixedPage?: PageId; + onJump: (tab: Tab) => void; + onDirtyChange: (dirty: boolean) => void; + notify: (toast: Omit) => void; +}) { + const [config, setConfig] = useState(null); + const [products, setProducts] = useState([]); + const [pageId, setPageId] = useState(fixedPage ?? "home"); + const [moduleId, setModuleId] = useState("heroSlides"); + const [selectedId, setSelectedId] = useState(""); + const [draft, setDraft] = useState({}); + const [message, setMessage] = useState(""); + + const activePage = pageSpecs.find((page) => page.id === (fixedPage ?? pageId)) ?? pageSpecs[0]; + const activeModule = activePage.modules.find((module) => module.id === moduleId) ?? activePage.modules[0]; + + const load = async () => { + const [site, productResult] = await Promise.all([getSiteConfig(), getProducts()]); + setConfig(site); + setProducts(productResult.items); + const firstModule = activePage.modules[0]?.id ?? "heroSlides"; + setModuleId((current) => (activePage.modules.some((module) => module.id === current) ? current : firstModule)); + if (moduleEditable(firstModule)) { + const first = moduleItems(site, firstModule)[0]; + if (first && !selectedId) { + setSelectedId(first.id); + setDraftFromItem(first); + } + } + }; + + useEffect(() => { + load().catch((err) => setMessage(err.message)); + }, []); + + useEffect(() => { + if (!fixedPage) return; + setPageId(fixedPage); + const next = pageSpecs.find((page) => page.id === fixedPage)?.modules[0]?.id ?? "heroSlides"; + setModuleId(next); + setSelectedId(""); + }, [fixedPage]); + + useEffect(() => { + if (!config || !moduleEditable(moduleId)) return; + const first = moduleItems(config, moduleId)[0]; + if (!first) return; + setSelectedId(first.id); + setDraftFromItem(first); + }, [moduleId, config]); + + const setDraftFromItem = (item: EditableSiteItem) => { + setDraft({ + title: "title" in item ? item.title : undefined, + kicker: "kicker" in item ? item.kicker || "" : undefined, + name: "name" in item ? item.name : undefined, + label: "label" in item ? item.label : undefined, + alt: "alt" in item ? item.alt : undefined, + image: "image" in item ? item.image || "" : "", + targetType: "targetType" in item ? item.targetType || "" : undefined, + targetValue: "targetValue" in item ? item.targetValue || "" : undefined, + isActive: item.isActive, + }); + }; + + const selectPage = (next: PageId) => { + setPageId(next); + const page = pageSpecs.find((item) => item.id === next); + setModuleId(page?.modules[0]?.id ?? "heroSlides"); + setSelectedId(""); + setMessage(""); + onDirtyChange(false); + }; + + const selectItem = (item: EditableSiteItem) => { + setSelectedId(item.id); + setDraftFromItem(item); + setMessage(""); + onDirtyChange(false); + }; + + const save = async () => { + if (!config || !moduleEditable(moduleId) || !selectedId) return; + await updateSiteConfigItem(moduleId, selectedId, draft); + setMessage("已保存,前台对应模块会使用新配置。"); + onDirtyChange(false); + notify({ tone: "success", title: "内容已保存", message: `${activeModule.label} 已同步到维护数据。` }); + await load(); + }; + + const publish = async () => { + const result = await publishSite(); + setMessage(`已生成发布版本:${result.title}`); + notify({ tone: "success", title: "已生成发布版本", message: result.title }); + }; + + const resetToGuizhou = async () => { + const result = await resetGuizhouContent(); + setMessage(`已重置贵州内容:${result.products} 条商品、${result.destinations} 个目的地。`); + notify({ tone: "success", title: "贵州内容已重置", message: "后台配置、商品库和素材引用已切换为贵州省内定制游。" }); + setSelectedId(""); + setDraft({}); + onDirtyChange(false); + await load(); + }; + + const editableItems = config && moduleEditable(moduleId) ? moduleItems(config, moduleId) : []; + const selectedItem = editableItems.find((item) => item.id === selectedId) ?? editableItems[0]; + + if (!config) return ; + + return ( +
+
+
+

{fixedPage ? activePage.title : "小程序维护地图"}

+

{fixedPage ? activePage.subtitle : "按用户看到的前台页面组织后台,逐层展开到可维护内容。"}

+
+
+ + + +
+
+ {message ?

{message}

: null} +
+ {!fixedPage ? ( + + ) : ( +
+ 当前维护页面 +

{activePage.title}

+

{activePage.frontPath}

+ {activePage.subtitle} +
+ )} +
+
+ + + {activePage.frontPath} + {activePage.subtitle} + +
+

页面模块

+ {activePage.modules.map((module, index) => ( + + ))} +
+
+

{activeModule.label}

+

{activeModule.hint}

+ {moduleEditable(moduleId) ? ( +
+ {editableItems.map((item) => ( + + ))} +
+ ) : ( + + )} +
+
+ {moduleEditable(moduleId) && selectedItem ? ( + { + setDraft(nextDraft); + onDirtyChange(true); + }} + onSave={save} + /> + ) : ( + + )} +
+
+
+ ); +} + +function ReferencePanel({ moduleId, products, onJump }: { moduleId: ModuleId; products: Product[]; onJump: (tab: Tab) => void }) { + if (moduleId === "leadFlow") { + return ( +
+

这个模块由客户需求列表维护,重点是状态流转、服务管家跟进和来源追踪。

+ +
+ ); + } + + return ( +
+

这个模块引用商品库。编辑商品后,首页商品卡、搜索结果和详情页会同步使用同一份数据。

+
+ {products.slice(0, 4).map((product) => ( + + + {product.title} + + ))} +
+ +
+ ); +} + +function SiteItemEditor({ + moduleId, + draft, + onDraft, + onSave, +}: { + moduleId: SiteModule; + draft: SiteItemPatch; + onDraft: (draft: SiteItemPatch) => void; + onSave: () => void; +}) { + const primaryKey = moduleId === "heroSlides" ? "title" : moduleId === "destinations" ? "name" : moduleId === "themes" ? "label" : "alt"; + const primaryLabel = moduleId === "heroSlides" ? "轮播标题" : moduleId === "destinations" ? "目的地名称" : moduleId === "themes" ? "主题名称" : "入口文案"; + + return ( +
+
+ +

编辑当前内容

+ 保存后会影响对应前台模块 +
+ +
+
+ + + {moduleId === "heroSlides" ? ( + + ) : null} + + + + {moduleId !== "destinations" ? ( +
+ + +
+ ) : null} +
+ {draft.image ? : } +
+
+ + + +
+
+ ); +} + +function FrontPreview({ config, moduleId, products }: { config: SiteConfig; moduleId: ModuleId; products: Product[] }) { + return ( +
+
万趣贵州
+
+ + {config.heroSlides[0]?.title} +
+
+ 探索贵州 + 主题甄选 + 精选线路 +
+
+ {config.destinations.slice(0, 6).map((destination) => ( + {destination.name} + ))} +
+
+ {products.slice(0, 2).map((product) => ( +
+ + {product.subtitle || product.title} +
+ ))} +
+
+ ); +} + +function ProductManager({ + destinations, + onDirtyChange, + notify, +}: { + destinations: Destination[]; + onDirtyChange: (dirty: boolean) => void; + notify: (toast: Omit) => void; +}) { + const [products, setProducts] = useState([]); + const [selected, setSelected] = useState(null); + const [draft, setDraft] = useState(toProductInput()); + const [keyword, setKeyword] = useState(""); + const [message, setMessage] = useState(""); + const [loading, setLoading] = useState(false); + const [detailOpen, setDetailOpen] = useState(false); + + const load = async () => { + setLoading(true); + setMessage(""); + try { + const result = await getProducts(keyword); + setProducts(result.items); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + load().catch((err) => setMessage(err.message)); + }, []); + + const select = (product: Product) => { + setSelected(product); + setDraft(toProductInput(product)); + setMessage(""); + setDetailOpen(true); + onDirtyChange(false); + }; + + const save = async () => { + if (!draft.title.trim()) { + setMessage("请先填写商品标题。"); + notify({ tone: "warning", title: "商品未保存", message: "商品标题不能为空。" }); + return; + } + + const payload = compactProductPayload(draft); + const saved = selected ? await updateProduct(selected.id, payload) : await createProduct(payload); + setSelected(saved); + setDraft(toProductInput(saved)); + setMessage("商品已保存,对应前台商品卡、搜索结果和详情页会使用这份数据。"); + onDirtyChange(false); + notify({ tone: "success", title: "商品已保存", message: "产品卡、搜索结果和详情页会使用最新内容。" }); + await load(); + }; + + const createNew = () => { + setSelected(null); + setDraft(toProductInput()); + setDetailOpen(true); + setMessage("正在新建商品"); + onDirtyChange(true); + }; + + const updateDraft = (nextDraft: ProductInput) => { + setDraft(nextDraft); + onDirtyChange(true); + }; + + const closeDetail = () => { + setDetailOpen(false); + setSelected(null); + setMessage(""); + onDirtyChange(false); + }; + + if (!detailOpen) { + const publishedCount = products.filter((product) => product.status === "published").length; + const draftCount = products.filter((product) => product.status === "draft").length; + + return ( +
+
+
+
+

商品维护

+

先管理商品列表,再点击单个商品进入详情维护。

+
+ +
+ {message ?

{message}

: null} +
+ + {products.length} + 当前列表 + + + {publishedCount} + 已上架 + + + {draftCount} + 草稿 + +
+
{ + event.preventDefault(); + load().catch((err) => setMessage(err.message)); + }} + > + setKeyword(event.target.value)} placeholder="搜索商品标题、短标题或标签" /> + +
+
+ {products.map((product) => ( + + ))} + {!products.length ?

暂无商品,点击右上角新建商品。

: null} +
+
+
+ ); + } + + return ( +
+
+
+
+

{selected ? "商品详情维护" : "新建商品"}

+

把列表卡片、销售信息、详情图文拆成可单独维护的模块。

+
+
+ + +
+
+ {message ?

{message}

: null} +
+ +
+
+ +
+