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 (
+
+
+
+ );
+}
+
+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 (
+
+
+ {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 (
+
+
+
+ 编辑当前内容
+ 保存后会影响对应前台模块
+
+
+
+
+
+ );
+}
+
+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}
+ 草稿
+
+
+
+
+ {products.map((product) => (
+
+ ))}
+ {!products.length ?
暂无商品,点击右上角新建商品。
: null}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {message ?
{message}
: null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ updateDraft({ ...draft, tags })} />
+
+
+
+
+
+ updateDraft({ ...draft, images })} />
+
+
+ updateDraft({ ...draft, detailSections })} />
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function TagEditor({ tags, onChange }: { tags: string[]; onChange: (tags: string[]) => void }) {
+ const updateTag = (index: number, value: string) => {
+ onChange(tags.map((tag, tagIndex) => (tagIndex === index ? value : tag)));
+ };
+
+ const removeTag = (index: number) => {
+ onChange(tags.filter((_, tagIndex) => tagIndex !== index));
+ };
+
+ return (
+
+ {tags.map((tag, index) => (
+
+ ))}
+ {!tags.length ?
暂无标签,可点击添加后逐个维护。
: null}
+
+
+ );
+}
+
+function ProductImagesEditor({
+ images,
+ onChange,
+}: {
+ images: NonNullable;
+ onChange: (images: NonNullable) => void;
+}) {
+ const updateImage = (index: number, patch: Partial[number]>) => {
+ onChange(images.map((image, imageIndex) => (imageIndex === index ? { ...image, ...patch } : image)));
+ };
+
+ const removeImage = (index: number) => {
+ onChange(images.filter((_, imageIndex) => imageIndex !== index));
+ };
+
+ return (
+
+ {images.map((image, index) => (
+
+
+ {image.url ?

:
}
+
+
+
+
+
+
+
+ ))}
+ {!images.length ?
暂无详情图片。封面图会继续用于商品卡,详情模块中也可以单独添加图片块。
: null}
+
+
+ );
+}
+
+function DetailSectionsEditor({
+ sections,
+ onChange,
+}: {
+ sections: ProductDetailSection[];
+ onChange: (sections: ProductDetailSection[]) => void;
+}) {
+ const updateSection = (sectionIndex: number, patch: Partial) => {
+ onChange(sections.map((section, index) => (index === sectionIndex ? { ...section, ...patch } : section)));
+ };
+
+ const removeSection = (sectionIndex: number) => {
+ onChange(sections.filter((_, index) => index !== sectionIndex));
+ };
+
+ const updateBlock = (sectionIndex: number, blockIndex: number, nextBlock: ProductDetailBlock) => {
+ const section = sections[sectionIndex];
+ if (!section) return;
+ updateSection(sectionIndex, {
+ blocks: section.blocks.map((block, index) => (index === blockIndex ? nextBlock : block)),
+ });
+ };
+
+ const addBlock = (sectionIndex: number, type: ProductDetailBlock["type"]) => {
+ const section = sections[sectionIndex];
+ if (!section) return;
+ updateSection(sectionIndex, { blocks: [...section.blocks, createDetailBlock(type)] });
+ };
+
+ const removeBlock = (sectionIndex: number, blockIndex: number) => {
+ const section = sections[sectionIndex];
+ if (!section) return;
+ updateSection(sectionIndex, { blocks: section.blocks.filter((_, index) => index !== blockIndex) });
+ };
+
+ return (
+
+ {sections.map((section, sectionIndex) => (
+
+
+ {sectionIndex + 1}
+
+ {section.label || "未命名模块"}
+ {section.blocks.length} 个内容块
+
+
+
+
+
+
+
+
+
+ {section.blocks.map((block, blockIndex) => (
+
+
+
+ {block.type === "image" ? : }
+ {block.type === "image" ? "图片内容" : "文字内容"} {blockIndex + 1}
+
+
+
+ {block.type === "image" ? (
+
+
+ {block.url ?

:
}
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+ ))}
+ {!section.blocks.length ?
这个模块暂无内容块,可添加文字或图片。
: null}
+
+
+
+
+
+
+ ))}
+ {!sections.length ?
暂无详情模块。可以从常用模块开始添加,再逐块维护标题、文字和图片。
: null}
+
+
+ );
+}
+
+function LeadManager({ notify }: { notify: (toast: Omit) => void }) {
+ const [leads, setLeads] = useState([]);
+ const [message, setMessage] = useState("");
+
+ const load = async () => {
+ const result = await getLeads();
+ setLeads(result.items);
+ };
+
+ useEffect(() => {
+ load().catch((err) => setMessage(err.message));
+ }, []);
+
+ const changeStatus = async (lead: Lead, status: Lead["status"]) => {
+ await updateLeadStatus(lead.id, status);
+ setMessage("线索状态已更新");
+ notify({ tone: "success", title: "线索已更新", message: `${lead.phone} 已标记为${leadStatusLabels[status]}。` });
+ await load();
+ };
+
+ return (
+
+
+
+
需求线索跟进
+
对应前台「提交出行需求」和详情页「立即咨询」。
+
+
+
+ {message ? {message}
: null}
+
+
+ 客户
+ 需求
+ 来源
+ 状态
+
+ {leads.map((lead) => (
+
+
+ {lead.phone}
+ {formatDate(lead.createdAt)}
+
+ {lead.destination || lead.note || "未填写"}
+ {lead.sourceProduct?.title || lead.sourcePage || "H5 表单"}
+
+
+ ))}
+ {!leads.length ?
暂无客户需求。
: null}
+
+
+ );
+}
+
+function EmptyState({ text }: { text: string }) {
+ return {text}
;
+}
+
+export default function App() {
+ const [authed, setAuthed] = useState(Boolean(getToken()));
+ const [activeTab, setActiveTab] = useState("structure");
+ const [destinations, setDestinations] = useState([]);
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
+ const [toasts, setToasts] = useState([]);
+
+ useEffect(() => {
+ if (!authed) return;
+ getDestinations().then((result) => setDestinations(result.items)).catch(() => setDestinations([]));
+ }, [authed, activeTab]);
+
+ useEffect(() => {
+ setHasUnsavedChanges(false);
+ }, [activeTab]);
+
+ const activeMeta = useMemo(() => tabs.find((tab) => tab.id === activeTab), [activeTab]);
+ const activeLabel = activeMeta?.label ?? "";
+ const activeDescription = activeMeta?.description ?? "";
+
+ const notify = (toast: Omit) => {
+ const id = `toast-${Date.now()}-${Math.random().toString(16).slice(2)}`;
+ setToasts((items) => [...items, { ...toast, id }]);
+ window.setTimeout(() => {
+ setToasts((items) => items.filter((item) => item.id !== id));
+ }, 4200);
+ };
+
+ if (!authed) return setAuthed(true)} />;
+
+ return (
+
+
+
+
+
+ {activeLabel}
+ {activeDescription} · 结构化维护 · 内存开发模式
+
+
+
+ {hasUnsavedChanges ? "有未提交修改" : "内容已同步"}
+
+
+
+ {activeTab === "structure" ? (
+
+ ) : activeTab === "home" ? (
+
+ ) : activeTab === "destinations" ? (
+
+ ) : activeTab === "products" ? (
+
+ ) : (
+
+ )}
+
+ setToasts((items) => items.filter((item) => item.id !== id))} />
+
+ );
+}
diff --git a/src/api.ts b/src/api.ts
new file mode 100644
index 0000000..c8f458b
--- /dev/null
+++ b/src/api.ts
@@ -0,0 +1,206 @@
+export type Dashboard = {
+ stats: {
+ productCount: number;
+ publishedProductCount: number;
+ destinationCount: number;
+ newLeadCount: number;
+ leadCount: number;
+ campaignCount: number;
+ };
+ recentLeads: Lead[];
+};
+
+export type Destination = {
+ id: string;
+ name: string;
+ slug: string;
+ region?: string | null;
+ image?: string | null;
+ isHot: boolean;
+ isActive: boolean;
+ sortOrder: number;
+ aliases?: { id: string; alias: string }[];
+ _count?: { products: number };
+};
+
+export type Product = {
+ id: string;
+ sourceId?: number | null;
+ title: string;
+ subtitle?: string | null;
+ priceAmount?: number | null;
+ priceUnit: string;
+ tags: string[];
+ coverImage?: string | null;
+ summary?: string | null;
+ images?: Array<{ id?: string; url: string; alt?: string | null; sortOrder: number }>;
+ detailSections?: ProductDetailSection[] | null;
+ status: "draft" | "published" | "archived";
+ sortWeight: number;
+ updatedAt: string;
+ destination?: Destination | null;
+ destinationId?: string | null;
+};
+
+export type ProductDetailBlock =
+ | { type: "text"; text: string }
+ | { type: "image"; url: string; alt?: string | null };
+
+export type ProductDetailSection = {
+ key: string;
+ label: string;
+ title?: string | null;
+ blocks: ProductDetailBlock[];
+};
+
+export type Lead = {
+ id: string;
+ destination?: string | null;
+ phone: string;
+ note?: string | null;
+ sourcePage?: string | null;
+ status: "new" | "assigned" | "contacted" | "planning" | "won" | "invalid";
+ createdAt: string;
+ sourceProduct?: { id: string; title: string } | null;
+ assignedUser?: { id: string; name: string } | null;
+};
+
+export type SiteConfig = {
+ heroSlides: Array<{ id: string; title: string; kicker?: string | null; image: string; targetType?: string | null; targetValue?: string | null; isActive: boolean }>;
+ destinations: Destination[];
+ themes: Array<{ id: string; label: string; image: string; targetType?: string | null; targetValue?: string | null; isActive: boolean }>;
+ ctaBanners: Array<{ id: string; alt: string; image: string; targetType: string; targetValue?: string | null; isActive: boolean }>;
+};
+
+export type SiteModule = "heroSlides" | "destinations" | "themes" | "ctaBanners";
+
+export type SiteItemPatch = {
+ title?: string;
+ kicker?: string;
+ name?: string;
+ label?: string;
+ alt?: string;
+ image?: string | null;
+ targetType?: string | null;
+ targetValue?: string | null;
+ isActive?: boolean;
+};
+
+export type ProductInput = {
+ title: string;
+ subtitle?: string;
+ destinationId?: string | null;
+ priceAmount?: number | null;
+ priceUnit?: string;
+ tags: string[];
+ coverImage?: string | null;
+ summary?: string | null;
+ images?: Array<{ url: string; alt?: string | null; sortOrder: number }>;
+ detailSections?: ProductDetailSection[];
+ status: "draft" | "published" | "archived";
+ sortWeight: number;
+};
+
+const API_BASE = import.meta.env.VITE_API_BASE_URL ?? "";
+const TOKEN_KEY = "miniapp_admin_token";
+
+export function getToken() {
+ return window.localStorage.getItem(TOKEN_KEY);
+}
+
+export function setToken(token: string) {
+ window.localStorage.setItem(TOKEN_KEY, token);
+}
+
+export function clearToken() {
+ window.localStorage.removeItem(TOKEN_KEY);
+}
+
+async function request(path: string, options: RequestInit = {}): Promise {
+ const headers = new Headers(options.headers);
+ headers.set("Content-Type", "application/json");
+ const token = getToken();
+ if (token) headers.set("Authorization", `Bearer ${token}`);
+
+ const response = await fetch(`${API_BASE}${path}`, {
+ ...options,
+ headers,
+ });
+
+ if (!response.ok) {
+ const body = await response.json().catch(() => ({}));
+ throw new Error(body.message ?? `请求失败:${response.status}`);
+ }
+
+ return response.json() as Promise;
+}
+
+export async function login(email: string, password: string) {
+ return request<{ token: string; user: { name: string; email: string; role: string } }>("/api/admin/auth/login", {
+ method: "POST",
+ body: JSON.stringify({ email, password }),
+ });
+}
+
+export async function getDashboard() {
+ return request("/api/admin/dashboard");
+}
+
+export async function getProducts(keyword = "") {
+ const search = keyword ? `?keyword=${encodeURIComponent(keyword)}` : "";
+ return request<{ items: Product[] }>(`/api/admin/products${search}`);
+}
+
+export async function createProduct(input: ProductInput) {
+ return request("/api/admin/products", {
+ method: "POST",
+ body: JSON.stringify(input),
+ });
+}
+
+export async function updateProduct(id: string, input: Partial) {
+ return request(`/api/admin/products/${id}`, {
+ method: "PATCH",
+ body: JSON.stringify(input),
+ });
+}
+
+export async function getDestinations() {
+ return request<{ items: Destination[] }>("/api/admin/destinations");
+}
+
+export async function getSiteConfig() {
+ return request("/api/admin/site-config");
+}
+
+export async function updateSiteConfigItem(module: SiteModule, id: string, input: SiteItemPatch) {
+ return request(`/api/admin/site-config/${module}/${id}`, {
+ method: "PATCH",
+ body: JSON.stringify(input),
+ });
+}
+
+export async function getLeads() {
+ return request<{ items: Lead[] }>("/api/admin/leads");
+}
+
+export async function updateLeadStatus(id: string, status: Lead["status"]) {
+ return request(`/api/admin/leads/${id}/status`, {
+ method: "PATCH",
+ body: JSON.stringify({ status }),
+ });
+}
+
+export async function publishSite() {
+ return request<{ id: string; title: string; publishedAt: string }>("/api/admin/publish", {
+ method: "POST",
+ body: JSON.stringify({}),
+ });
+}
+
+export async function resetGuizhouContent() {
+ return request<{ heroSlides: number; destinations: number; themes: number; ctaBanners: number; products: number }>("/api/admin/reset-guizhou-content", {
+ method: "POST",
+ body: JSON.stringify({}),
+ });
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..693ac2e
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+import "./styles.css";
+
+createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..0ce8bf3
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,1448 @@
+:root {
+ font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
+ color: #1c2329;
+ background: #eef1f4;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+
+button {
+ cursor: pointer;
+}
+
+.login-shell {
+ min-height: 100vh;
+ display: grid;
+ place-items: center;
+ background: #edf1f2;
+}
+
+.login-panel {
+ width: min(390px, calc(100vw - 32px));
+ display: grid;
+ gap: 18px;
+ padding: 28px;
+ background: #fff;
+ border: 1px solid #d9dee4;
+ border-radius: 8px;
+}
+
+.login-kicker {
+ color: #287d79;
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.login-panel h1 {
+ margin: 0 0 8px;
+ font-size: 26px;
+}
+
+.login-note {
+ margin: -8px 0 4px;
+ color: #66727b;
+ font-size: 14px;
+}
+
+label {
+ display: grid;
+ gap: 8px;
+ color: #5c6670;
+ font-size: 13px;
+}
+
+input,
+select,
+textarea {
+ width: 100%;
+ border: 1px solid #cfd6dc;
+ border-radius: 6px;
+ padding: 10px 11px;
+ color: #1c2329;
+ background: #fff;
+}
+
+textarea {
+ resize: vertical;
+}
+
+.primary-action,
+.action-row button,
+.toolbar button,
+.section-head > button,
+.lead-table select,
+.editor-head button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+}
+
+.primary-action {
+ border: 0;
+ border-radius: 6px;
+ padding: 11px 16px;
+ color: #fff;
+ background: #176c68;
+ font-weight: 700;
+}
+
+.primary-action.compact {
+ padding: 9px 13px;
+}
+
+.form-error,
+.notice {
+ margin: 0;
+ padding: 10px 12px;
+ border-radius: 6px;
+ background: #fff3df;
+ color: #7c4200;
+}
+
+.admin-shell {
+ min-height: 100vh;
+ display: grid;
+ grid-template-columns: 236px minmax(0, 1fr);
+}
+
+.sidebar {
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ padding: 18px;
+ background: #151b1f;
+ color: #fff;
+}
+
+.brand-block {
+ display: grid;
+ gap: 4px;
+ padding: 8px 8px 20px;
+}
+
+.brand-block b {
+ font-size: 22px;
+}
+
+.brand-block span {
+ color: #96a3ad;
+ font-size: 13px;
+}
+
+.sidebar nav,
+.sidebar-group {
+ display: grid;
+ align-content: start;
+}
+
+.sidebar nav {
+ gap: 18px;
+}
+
+.sidebar-group {
+ gap: 7px;
+}
+
+.sidebar-group > span {
+ padding: 0 8px;
+ color: #7f8b94;
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0;
+}
+
+.sidebar button {
+ min-height: 42px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ border: 0;
+ border-radius: 6px;
+ padding: 0 12px;
+ color: #c9d0d6;
+ background: transparent;
+ text-align: left;
+}
+
+.sidebar button > span {
+ min-width: 0;
+ display: grid;
+ gap: 1px;
+}
+
+.sidebar button b {
+ color: inherit;
+ font-size: 14px;
+}
+
+.sidebar button small {
+ overflow: hidden;
+ color: #7f8b94;
+ font-size: 11px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sidebar button.active {
+ color: #fff;
+ background: #1f6f6b;
+ box-shadow: inset 3px 0 0 #7dd6d0;
+}
+
+.sidebar button.active small {
+ color: #b8d7d5;
+}
+
+.logout-button {
+ border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
+ border-radius: 0 !important;
+}
+
+.main-panel {
+ min-width: 0;
+ display: grid;
+ grid-template-rows: 56px minmax(0, 1fr);
+}
+
+.topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 0 24px;
+ background: #fff;
+ border-bottom: 1px solid #dde3e8;
+}
+
+.topbar span {
+ display: block;
+ font-weight: 800;
+}
+
+.topbar small {
+ color: #75808a;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.admin-dirty-chip {
+ display: inline-flex !important;
+ align-items: center;
+ min-height: 30px;
+ padding: 0 10px;
+ border: 1px solid #cfd8de;
+ border-radius: 999px;
+ color: #4e5b65;
+ background: #f7f9fa;
+ font-size: 12px;
+ font-weight: 800;
+ white-space: nowrap;
+}
+
+.admin-dirty-chip.is-dirty {
+ border-color: #dfb154;
+ color: #7c4200;
+ background: #fff5dc;
+}
+
+.work-area {
+ min-width: 0;
+ padding: 24px;
+}
+
+.section-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 18px;
+}
+
+.section-head h2,
+.editor-head h3,
+.data-panel h3 {
+ margin: 0;
+}
+
+.section-head p {
+ margin: 6px 0 0;
+ color: #68747d;
+}
+
+.section-head button,
+.toolbar button,
+.action-row button,
+.icon-action {
+ border: 1px solid #cbd4da;
+ border-radius: 6px;
+ padding: 9px 12px;
+ color: #263038;
+ background: #fff;
+}
+
+.action-row {
+ display: flex;
+ gap: 10px;
+}
+
+.metric-grid {
+ display: grid;
+ grid-template-columns: repeat(6, minmax(120px, 1fr));
+ gap: 12px;
+}
+
+.metric-card,
+.data-panel,
+.editor-pane,
+.list-pane {
+ background: #fff;
+ border: 1px solid #dce2e7;
+ border-radius: 8px;
+}
+
+.metric-card {
+ display: grid;
+ gap: 8px;
+ padding: 18px;
+}
+
+.metric-card span,
+.dense-list small,
+.destination-row small,
+.product-table small,
+.lead-row small {
+ color: #6d7882;
+}
+
+.metric-card strong {
+ font-size: 28px;
+}
+
+.data-panel {
+ margin-top: 16px;
+ padding: 18px;
+}
+
+.dense-list {
+ display: grid;
+ gap: 8px;
+ margin-top: 14px;
+}
+
+.dense-list span {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto auto;
+ gap: 12px;
+ align-items: center;
+ padding: 10px 0;
+ border-bottom: 1px solid #edf0f2;
+}
+
+.dense-list b,
+.product-table b,
+.destination-row strong {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.dense-list em {
+ color: #176c68;
+ font-style: normal;
+}
+
+.muted,
+.empty-state {
+ color: #717d86;
+}
+
+.empty-state {
+ display: grid;
+ place-items: center;
+ min-height: 260px;
+}
+
+.config-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+}
+
+.structure-workbench {
+ height: calc(100vh - 56px);
+ display: grid;
+ grid-template-rows: auto auto minmax(0, 1fr);
+}
+
+.maintenance-grid {
+ grid-row: 3;
+ min-height: 0;
+ display: grid;
+ grid-template-columns: 260px 300px minmax(320px, 0.95fr) minmax(360px, 1.05fr);
+ gap: 14px;
+}
+
+.maintenance-grid.focused-page-grid {
+ grid-template-columns: 260px 300px minmax(330px, 0.95fr) minmax(380px, 1.05fr);
+}
+
+.page-rail,
+.page-focus-card,
+.module-rail,
+.item-rail,
+.edit-rail {
+ min-height: 0;
+ overflow: auto;
+ padding: 16px;
+ background: #fff;
+ border: 1px solid #dce2e7;
+ border-radius: 8px;
+}
+
+.page-focus-card {
+ align-content: start;
+ display: grid;
+ gap: 10px;
+ color: #263038;
+ background:
+ linear-gradient(180deg, rgba(36, 125, 121, 0.08), rgba(255, 255, 255, 0) 62%),
+ #fff;
+}
+
+.page-focus-card .focus-kicker {
+ width: fit-content;
+ padding: 5px 8px;
+ border-radius: 5px;
+ color: #176c68;
+ background: #e6f3f2;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.page-focus-card h3 {
+ margin: 4px 0 0;
+ font-size: 22px;
+}
+
+.page-focus-card p {
+ margin: 0;
+ color: #1f2a31;
+ font-weight: 800;
+ line-height: 1.55;
+}
+
+.page-focus-card small {
+ color: #68747d;
+ line-height: 1.65;
+}
+
+.page-rail h3,
+.module-rail h3,
+.item-rail h3 {
+ margin: 0 0 12px;
+ font-size: 15px;
+}
+
+.page-rail,
+.module-rail {
+ display: grid;
+ align-content: start;
+ gap: 8px;
+}
+
+.page-rail button,
+.module-rail button,
+.mapped-list button {
+ width: 100%;
+ min-width: 0;
+ border: 1px solid #e0e6eb;
+ border-radius: 7px;
+ padding: 10px;
+ background: #fff;
+ color: #1e272e;
+ text-align: left;
+}
+
+.page-rail button,
+.module-rail button {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 8px;
+ align-items: center;
+}
+
+.module-rail button {
+ grid-template-columns: 30px minmax(0, 1fr);
+}
+
+.page-rail button.active,
+.module-rail button.active,
+.mapped-list button.selected {
+ border-color: #247d79;
+ background: #eff9f8;
+}
+
+.page-rail button:disabled {
+ opacity: 1;
+}
+
+.page-rail b,
+.module-rail b,
+.mapped-list b {
+ overflow: hidden;
+ display: block;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.page-rail small,
+.module-rail small,
+.mapped-list small,
+.editor-head small {
+ color: #68747d;
+}
+
+.module-rail em {
+ display: grid;
+ place-items: center;
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ color: #176c68;
+ background: #e8f4f3;
+ font-style: normal;
+ font-weight: 800;
+}
+
+.front-context {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 10px;
+ align-items: start;
+ padding: 12px;
+ color: #263038;
+ background: #f5f7f8;
+ border: 1px solid #e0e6eb;
+ border-radius: 7px;
+}
+
+.front-context b,
+.front-context small {
+ display: block;
+}
+
+.item-rail > p {
+ margin: -4px 0 14px;
+ color: #68747d;
+ line-height: 1.55;
+}
+
+.mapped-list,
+.mapped-editor,
+.reference-panel {
+ display: grid;
+ gap: 10px;
+}
+
+.mapped-list button {
+ display: grid;
+ grid-template-columns: 64px minmax(0, 1fr);
+ gap: 10px;
+ align-items: center;
+}
+
+.mapped-list img,
+.mini-product-stack img {
+ width: 64px;
+ height: 48px;
+ object-fit: cover;
+ border-radius: 5px;
+ background: #d7dee3;
+}
+
+.mapped-editor {
+ align-content: start;
+}
+
+.admin-disclosure-stack {
+ display: grid;
+ gap: 10px;
+}
+
+.admin-disclosure {
+ overflow: hidden;
+ border: 1px solid #dce3e8;
+ border-radius: 8px;
+ background: #fff;
+}
+
+.admin-disclosure[open] {
+ border-color: #b7cbc9;
+ box-shadow: 0 10px 24px rgba(23, 108, 104, 0.08);
+}
+
+.admin-disclosure summary {
+ min-height: 58px;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 12px;
+ align-items: center;
+ padding: 12px 14px;
+ cursor: pointer;
+ list-style: none;
+}
+
+.admin-disclosure summary::-webkit-details-marker {
+ display: none;
+}
+
+.admin-disclosure-copy {
+ min-width: 0;
+ display: grid;
+ gap: 3px;
+}
+
+.admin-disclosure-copy strong {
+ overflow: hidden;
+ color: #1f2a31;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.admin-disclosure-copy small {
+ color: #68747d;
+ line-height: 1.45;
+}
+
+.admin-disclosure-meta {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+}
+
+.admin-disclosure-meta em {
+ padding: 4px 7px;
+ border-radius: 999px;
+ color: #176c68;
+ background: #edf7f6;
+ font-size: 11px;
+ font-style: normal;
+ font-weight: 800;
+}
+
+.admin-disclosure-meta svg {
+ transition: transform 160ms ease;
+}
+
+.admin-disclosure[open] .admin-disclosure-meta svg {
+ transform: rotate(90deg);
+}
+
+.admin-disclosure-body {
+ display: grid;
+ gap: 12px;
+ padding: 0 14px 14px;
+}
+
+.mapped-editor .asset-preview {
+ display: grid;
+ place-items: center;
+ min-height: 190px;
+ overflow: hidden;
+ border: 1px solid #dce2e7;
+ border-radius: 7px;
+ background: #f5f7f8;
+}
+
+.mapped-editor .asset-preview img {
+ width: 100%;
+ height: 220px;
+ object-fit: cover;
+}
+
+.switch-line {
+ display: flex;
+ grid-template-columns: none;
+ align-items: center;
+ gap: 8px;
+}
+
+.switch-line input {
+ width: 16px;
+}
+
+.compact-grid {
+ overflow: visible;
+}
+
+.reference-panel {
+ color: #46525c;
+ line-height: 1.6;
+}
+
+.mini-product-stack {
+ display: grid;
+ gap: 8px;
+}
+
+.mini-product-stack span {
+ min-width: 0;
+ display: grid;
+ grid-template-columns: 64px minmax(0, 1fr);
+ gap: 10px;
+ align-items: center;
+ padding: 8px;
+ background: #f7f9fa;
+ border-radius: 7px;
+}
+
+.mini-product-stack b {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.phone-preview {
+ width: min(100%, 330px);
+ margin: 0 auto;
+ overflow: hidden;
+ border: 1px solid #d4dce2;
+ border-radius: 18px;
+ background: #fff;
+ box-shadow: 0 18px 45px rgba(26, 35, 42, 0.12);
+}
+
+.admin-toast-stack {
+ position: fixed;
+ right: 18px;
+ bottom: 18px;
+ z-index: 40;
+ display: grid;
+ gap: 10px;
+ width: min(360px, calc(100vw - 36px));
+}
+
+.admin-toast {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: start;
+ padding: 12px;
+ border: 1px solid #d5dde3;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 18px 46px rgba(24, 34, 42, 0.16);
+}
+
+.admin-toast.success svg {
+ color: #176c68;
+}
+
+.admin-toast.warning svg,
+.admin-toast.danger svg {
+ color: #b66b00;
+}
+
+.admin-toast span {
+ display: grid;
+ gap: 3px;
+}
+
+.admin-toast strong {
+ font-size: 13px;
+}
+
+.admin-toast small {
+ color: #66727b;
+ line-height: 1.4;
+}
+
+.admin-toast button {
+ width: 24px;
+ height: 24px;
+ border: 0;
+ border-radius: 5px;
+ background: #f1f4f6;
+ color: #66727b;
+}
+
+.phone-preview header {
+ height: 42px;
+ display: grid;
+ place-items: center;
+ color: #fff;
+ background: #111;
+ font-weight: 800;
+}
+
+.preview-hero {
+ position: relative;
+ height: 190px;
+}
+
+.preview-hero img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.preview-hero span {
+ position: absolute;
+ left: 16px;
+ bottom: 16px;
+ color: #fff;
+ font-weight: 800;
+ text-shadow: 0 1px 8px rgba(0, 0, 0, 0.45);
+}
+
+.preview-tabs {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ border-bottom: 1px solid #edf0f2;
+}
+
+.preview-tabs b {
+ padding: 11px 4px;
+ color: #68747d;
+ font-size: 12px;
+ text-align: center;
+}
+
+.preview-tabs b.active {
+ color: #176c68;
+}
+
+.preview-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 6px;
+ padding: 12px;
+}
+
+.preview-grid span {
+ padding: 8px 4px;
+ color: #176c68;
+ background: #ecf6f5;
+ border-radius: 5px;
+ font-size: 12px;
+ text-align: center;
+}
+
+.preview-products {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+ padding: 0 12px 14px;
+}
+
+.preview-products article {
+ min-width: 0;
+ display: grid;
+ gap: 6px;
+}
+
+.preview-products img {
+ width: 100%;
+ height: 72px;
+ object-fit: cover;
+ border-radius: 6px;
+}
+
+.preview-products b {
+ overflow: hidden;
+ display: -webkit-box;
+ color: #263038;
+ font-size: 12px;
+ line-height: 1.35;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+}
+
+.product-workbench {
+ height: calc(100vh - 56px);
+ overflow: auto;
+}
+
+.product-list-page,
+.product-detail-page {
+ min-height: 100%;
+ display: grid;
+ align-content: start;
+ gap: 14px;
+ padding: 18px;
+ background: #fff;
+ border: 1px solid #dce2e7;
+ border-radius: 8px;
+}
+
+.product-list-page {
+ grid-template-rows: auto auto auto minmax(0, 1fr);
+}
+
+.product-detail-page {
+ grid-template-rows: auto auto minmax(0, 1fr);
+}
+
+.product-list-summary {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(120px, 1fr));
+ gap: 10px;
+}
+
+.product-list-summary span {
+ display: grid;
+ gap: 3px;
+ padding: 12px;
+ border: 1px solid #e2e8ec;
+ border-radius: 7px;
+ background: #f7f9fa;
+}
+
+.product-list-summary b {
+ font-size: 24px;
+}
+
+.product-list-summary small {
+ color: #68747d;
+ font-size: 12px;
+}
+
+.toolbar {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.product-table {
+ overflow: auto;
+ display: grid;
+ align-content: start;
+ gap: 8px;
+}
+
+.product-table button {
+ min-width: 0;
+ display: grid;
+ grid-template-columns: 72px minmax(0, 1fr) auto 120px auto;
+ align-items: center;
+ gap: 12px;
+ border: 1px solid #dbe1e6;
+ border-radius: 6px;
+ padding: 8px;
+ background: #fff;
+ text-align: left;
+}
+
+.product-table button.selected {
+ border-color: #247d79;
+ background: #f0faf9;
+}
+
+.product-table img,
+.destination-row img,
+.image-placeholder {
+ width: 64px;
+ height: 48px;
+ object-fit: cover;
+ border-radius: 5px;
+ background: #d7dee3;
+}
+
+.product-table span,
+.destination-row div,
+.lead-row span {
+ min-width: 0;
+ display: grid;
+ gap: 4px;
+}
+
+.product-table em,
+.destination-row em {
+ color: #176c68;
+ font-style: normal;
+ font-weight: 800;
+}
+
+.product-table-main b {
+ max-width: 100%;
+}
+
+.status-chip,
+.row-link {
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ white-space: nowrap;
+}
+
+.status-chip {
+ min-width: 68px;
+ min-height: 28px;
+ padding: 0 9px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 800;
+}
+
+.status-chip.published {
+ color: #176c68;
+ background: #e7f5f3;
+}
+
+.status-chip.draft {
+ color: #765018;
+ background: #fff0d2;
+}
+
+.status-chip.archived {
+ color: #67717b;
+ background: #eceff2;
+}
+
+.row-link {
+ color: #176c68;
+ font-size: 13px;
+ font-weight: 800;
+}
+
+.product-editor-layout {
+ min-height: 0;
+ display: grid;
+ grid-template-columns: minmax(260px, 0.34fr) minmax(520px, 0.66fr);
+ gap: 16px;
+}
+
+.product-editor-sidebar {
+ position: sticky;
+ top: 0;
+ align-self: start;
+ display: grid;
+ gap: 12px;
+ padding: 14px;
+ border: 1px solid #dce3e8;
+ border-radius: 8px;
+ background: #f8fafb;
+}
+
+.product-cover-preview,
+.image-editor-preview {
+ display: grid;
+ place-items: center;
+ overflow: hidden;
+ border: 1px solid #dce3e8;
+ border-radius: 7px;
+ background: #eef2f4;
+ color: #7a858e;
+}
+
+.product-cover-preview {
+ aspect-ratio: 4 / 3;
+}
+
+.product-cover-preview img,
+.image-editor-preview img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.product-editor-sidebar h3,
+.product-editor-sidebar p {
+ margin: 0;
+}
+
+.product-editor-sidebar h3 {
+ font-size: 18px;
+ line-height: 1.45;
+}
+
+.product-editor-sidebar p {
+ color: #68747d;
+ font-size: 13px;
+ line-height: 1.55;
+}
+
+.product-side-meta {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+}
+
+.product-side-meta span {
+ display: grid;
+ gap: 3px;
+ padding: 10px;
+ border-radius: 7px;
+ background: #fff;
+ border: 1px solid #e2e8ec;
+}
+
+.product-side-meta b {
+ color: #1f2a31;
+ font-size: 14px;
+}
+
+.product-side-meta small {
+ color: #68747d;
+ font-size: 11px;
+}
+
+.product-editor-main {
+ min-width: 0;
+}
+
+.product-editor-footer {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 12px;
+}
+
+.inline-action {
+ width: fit-content;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ border: 1px solid #cbd4da;
+ border-radius: 6px;
+ padding: 8px 11px;
+ color: #263038;
+ background: #fff;
+ font-weight: 700;
+}
+
+.inline-action.strong {
+ border-color: #176c68;
+ color: #176c68;
+ background: #eff8f7;
+}
+
+.danger-icon {
+ color: #9c3d2f !important;
+}
+
+.icon-action.danger-icon {
+ width: 38px;
+ height: 38px;
+ padding: 0;
+}
+
+.tag-editor,
+.image-list-editor,
+.detail-section-editor,
+.detail-block-list {
+ display: grid;
+ gap: 10px;
+}
+
+.tag-input-row span {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 38px;
+ gap: 8px;
+}
+
+.image-editor-row {
+ display: grid;
+ grid-template-columns: 96px minmax(0, 1fr) 38px;
+ gap: 12px;
+ align-items: start;
+ padding: 10px;
+ border: 1px solid #e0e6eb;
+ border-radius: 8px;
+ background: #f9fbfc;
+}
+
+.image-editor-preview {
+ width: 96px;
+ height: 78px;
+}
+
+.image-editor-preview.large {
+ width: 132px;
+ height: 98px;
+}
+
+.detail-section-card {
+ display: grid;
+ gap: 12px;
+ padding: 12px;
+ border: 1px solid #dfe6eb;
+ border-radius: 8px;
+ background: #fbfcfd;
+}
+
+.detail-section-card-head,
+.detail-block-card header {
+ display: grid;
+ align-items: center;
+ gap: 10px;
+}
+
+.detail-section-card-head {
+ grid-template-columns: 34px minmax(0, 1fr) 38px;
+}
+
+.detail-section-card-head > span:not(.module-index) {
+ min-width: 0;
+ display: grid;
+ gap: 2px;
+}
+
+.detail-section-card-head b {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.detail-section-card-head small {
+ color: #68747d;
+}
+
+.module-index {
+ display: grid;
+ place-items: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 7px;
+ color: #176c68;
+ background: #e6f3f2;
+ font-weight: 800;
+}
+
+.detail-block-card {
+ display: grid;
+ gap: 10px;
+ padding: 10px;
+ border: 1px solid #e4e9ed;
+ border-radius: 7px;
+ background: #fff;
+}
+
+.detail-block-card header {
+ grid-template-columns: minmax(0, 1fr) 34px;
+}
+
+.detail-block-card header span {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ color: #34414a;
+}
+
+.detail-image-block {
+ display: grid;
+ grid-template-columns: 132px minmax(0, 1fr);
+ gap: 12px;
+}
+
+.section-action-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.editor-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 14px;
+}
+
+.form-grid {
+ overflow: auto;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.form-grid .wide {
+ grid-column: 1 / -1;
+}
+
+.field-help {
+ margin: -6px 0 0;
+ color: #6e7781;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.destination-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 12px;
+}
+
+.destination-row {
+ display: grid;
+ grid-template-columns: 64px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ background: #fff;
+ border: 1px solid #dce2e7;
+ border-radius: 8px;
+}
+
+.lead-table {
+ overflow: hidden;
+ background: #fff;
+ border: 1px solid #dce2e7;
+ border-radius: 8px;
+}
+
+.lead-table-head,
+.lead-row {
+ display: grid;
+ grid-template-columns: 180px minmax(160px, 1fr) minmax(220px, 1.2fr) 150px;
+ gap: 14px;
+ align-items: center;
+ padding: 12px 16px;
+}
+
+.lead-table-head {
+ color: #5e6972;
+ font-size: 13px;
+ font-weight: 800;
+ background: #f4f6f8;
+}
+
+.lead-row {
+ border-top: 1px solid #edf0f2;
+}
+
+.empty-row {
+ padding: 20px;
+}
+
+@media (max-width: 980px) {
+ .admin-shell {
+ grid-template-columns: 72px minmax(0, 1fr);
+ }
+
+ .sidebar {
+ padding: 12px 8px;
+ }
+
+ .brand-block {
+ place-items: center;
+ padding: 4px 0 14px;
+ }
+
+ .brand-block b {
+ font-size: 0;
+ }
+
+ .brand-block b::after {
+ content: "万";
+ display: grid;
+ place-items: center;
+ width: 40px;
+ height: 40px;
+ border-radius: 6px;
+ background: rgba(125, 214, 208, 0.14);
+ color: #fff;
+ font-size: 18px;
+ }
+
+ .brand-block span,
+ .sidebar button > span,
+ .sidebar-group > span {
+ display: none;
+ }
+
+ .sidebar button {
+ width: 48px;
+ justify-content: center;
+ padding: 0;
+ }
+
+ .logout-button {
+ font-size: 0;
+ }
+
+ .section-head {
+ display: grid;
+ }
+
+ .action-row {
+ width: 100%;
+ }
+
+ .action-row button {
+ flex: 1;
+ justify-content: center;
+ white-space: nowrap;
+ }
+
+ .config-grid,
+ .maintenance-grid,
+ .maintenance-grid.focused-page-grid,
+ .metric-grid,
+ .product-editor-layout,
+ .product-list-summary {
+ grid-template-columns: 1fr;
+ height: auto;
+ }
+
+ .product-workbench {
+ height: auto;
+ }
+
+ .product-editor-sidebar {
+ position: static;
+ }
+
+ .product-table button,
+ .image-editor-row,
+ .detail-image-block {
+ grid-template-columns: 1fr;
+ }
+
+ .product-table img,
+ .image-placeholder,
+ .image-editor-preview,
+ .image-editor-preview.large {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 4 / 3;
+ }
+
+ .row-link,
+ .status-chip {
+ justify-content: flex-start;
+ }
+
+ .structure-workbench {
+ height: auto;
+ }
+
+ .lead-table-head,
+ .lead-row {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..4053da4
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..df1b459
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,16 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5601,
+ proxy: {
+ "/api": {
+ target: "http://localhost:4000",
+ changeOrigin: true,
+ },
+ },
+ },
+});
+
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..638479e
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,347 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@emnapi/core@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz#b9e1064f3a6b1631e241e638eb48d736bfd372a6"
+ integrity sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==
+ dependencies:
+ "@emnapi/wasi-threads" "1.2.2"
+ tslib "^2.4.0"
+
+"@emnapi/runtime@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz#58f1f3d5d81a9b12f793ab688c96371901027c24"
+ integrity sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==
+ dependencies:
+ tslib "^2.4.0"
+
+"@emnapi/wasi-threads@1.2.2":
+ version "1.2.2"
+ resolved "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz#4c93becf5bfa3b13d1bbdcc06aee38321ad8139a"
+ integrity sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==
+ dependencies:
+ tslib "^2.4.0"
+
+"@napi-rs/wasm-runtime@^1.1.6":
+ version "1.1.6"
+ resolved "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz#ed33806d0f9be98dc76d0c3d4fd872fda701b5d5"
+ integrity sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==
+ dependencies:
+ "@tybys/wasm-util" "^0.10.3"
+
+"@oxc-project/types@=0.137.0":
+ version "0.137.0"
+ resolved "https://registry.npmmirror.com/@oxc-project/types/-/types-0.137.0.tgz#56e77f8bb221fa05f18b1cd34d73f94f0954a773"
+ integrity sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==
+
+"@rolldown/binding-android-arm64@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz#cc6153029c3d9afc9caaae2dc362d899ae94ac4f"
+ integrity sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==
+
+"@rolldown/binding-darwin-arm64@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz#3af681a5d7610340257b3ac7753353b23e884765"
+ integrity sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==
+
+"@rolldown/binding-darwin-x64@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz#80ada35e9f35efb7e48a887444ce2052f615d645"
+ integrity sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==
+
+"@rolldown/binding-freebsd-x64@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz#65b2bbb82f005f08aeeff0b6d81e19be68360201"
+ integrity sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==
+
+"@rolldown/binding-linux-arm-gnueabihf@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz#7e8d34ad0c7bcfd3baed268e9798571e3888ca71"
+ integrity sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==
+
+"@rolldown/binding-linux-arm64-gnu@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz#52bbf400ff219bda1e56c042160d96deb08bfecc"
+ integrity sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==
+
+"@rolldown/binding-linux-arm64-musl@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz#9e82899186f73329f3d8155fa1618ae2e86ffa2a"
+ integrity sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==
+
+"@rolldown/binding-linux-ppc64-gnu@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz#050520177316586ccad816eb466ea11015e17ba7"
+ integrity sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==
+
+"@rolldown/binding-linux-s390x-gnu@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz#50efa7b20219c6e31235fded0fd3427f36123e5a"
+ integrity sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==
+
+"@rolldown/binding-linux-x64-gnu@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz#c5973445113dff50d4077d0edaa4b8a69533dc6f"
+ integrity sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==
+
+"@rolldown/binding-linux-x64-musl@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz#ddb023f7fc98ccbb8c1b683545216ca7b4e7ebdd"
+ integrity sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==
+
+"@rolldown/binding-openharmony-arm64@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz#832a6da3472722427c73d178c75681858b76aeed"
+ integrity sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==
+
+"@rolldown/binding-wasm32-wasi@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz#256cdcc06ad9ada611606526f319642fc0830b0f"
+ integrity sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==
+ dependencies:
+ "@emnapi/core" "1.11.1"
+ "@emnapi/runtime" "1.11.1"
+ "@napi-rs/wasm-runtime" "^1.1.6"
+
+"@rolldown/binding-win32-arm64-msvc@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz#e735c7024a5e17ebaf13689112fa0bdfc6886c38"
+ integrity sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==
+
+"@rolldown/binding-win32-x64-msvc@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz#f06c09db5c8ad4b6904b4d406c9b6f17f392b5c6"
+ integrity sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==
+
+"@rolldown/pluginutils@^1.0.0", "@rolldown/pluginutils@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz#e3fcee093fbb5ce765e1ad088ff4de2889f6f9be"
+ integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==
+
+"@tybys/wasm-util@^0.10.3":
+ version "0.10.3"
+ resolved "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz#015cba9e9dd47ce14d03d2a8c5d547bfb169665d"
+ integrity sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==
+ dependencies:
+ tslib "^2.4.0"
+
+"@types/react-dom@^19.2.3":
+ version "19.2.3"
+ resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
+ integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
+
+"@types/react@^19.2.17":
+ version "19.2.17"
+ resolved "https://registry.npmmirror.com/@types/react/-/react-19.2.17.tgz#dccac365baa0f1734ec270ff4b51c89465e8dc7f"
+ integrity sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==
+ dependencies:
+ csstype "^3.2.2"
+
+"@vitejs/plugin-react@^6.0.3":
+ version "6.0.3"
+ resolved "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz#55f1d7f558534d10aef03c007dc208b7c3771ce4"
+ integrity sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==
+ dependencies:
+ "@rolldown/pluginutils" "^1.0.1"
+
+csstype@^3.2.2:
+ version "3.2.3"
+ resolved "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a"
+ integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
+
+detect-libc@^2.0.3:
+ version "2.1.2"
+ resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
+ integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+
+fdir@^6.5.0:
+ version "6.5.0"
+ resolved "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
+ integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+
+fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+lightningcss-android-arm64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968"
+ integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==
+
+lightningcss-darwin-arm64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5"
+ integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==
+
+lightningcss-darwin-x64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e"
+ integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==
+
+lightningcss-freebsd-x64@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575"
+ integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==
+
+lightningcss-linux-arm-gnueabihf@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d"
+ integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==
+
+lightningcss-linux-arm64-gnu@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335"
+ integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==
+
+lightningcss-linux-arm64-musl@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133"
+ integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==
+
+lightningcss-linux-x64-gnu@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6"
+ integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==
+
+lightningcss-linux-x64-musl@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b"
+ integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==
+
+lightningcss-win32-arm64-msvc@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38"
+ integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==
+
+lightningcss-win32-x64-msvc@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a"
+ integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==
+
+lightningcss@^1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9"
+ integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==
+ dependencies:
+ detect-libc "^2.0.3"
+ optionalDependencies:
+ lightningcss-android-arm64 "1.32.0"
+ lightningcss-darwin-arm64 "1.32.0"
+ lightningcss-darwin-x64 "1.32.0"
+ lightningcss-freebsd-x64 "1.32.0"
+ lightningcss-linux-arm-gnueabihf "1.32.0"
+ lightningcss-linux-arm64-gnu "1.32.0"
+ lightningcss-linux-arm64-musl "1.32.0"
+ lightningcss-linux-x64-gnu "1.32.0"
+ lightningcss-linux-x64-musl "1.32.0"
+ lightningcss-win32-arm64-msvc "1.32.0"
+ lightningcss-win32-x64-msvc "1.32.0"
+
+lucide-react@^1.21.0:
+ version "1.22.0"
+ resolved "https://registry.npmmirror.com/lucide-react/-/lucide-react-1.22.0.tgz#f2930b21a1d3941fbee8a1017a5973d06ec4860b"
+ integrity sha512-c9o3l0PiNcgOQDW4F31BEYHudE7kgxVt3o30qMl36ZPwTxXlGB4QnLilhERvVM4uh/pl5MDyY1/gzZSYcHDtBg==
+
+nanoid@^3.3.12:
+ version "3.3.15"
+ resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz#36c490fad8c6e86c824c940dfdde999b69ed4316"
+ integrity sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==
+
+picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
+ integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
+
+postcss@^8.5.15:
+ version "8.5.16"
+ resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.16.tgz#1230ce0b5df354c24c0ea45f99ce5f6a88279d28"
+ integrity sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==
+ dependencies:
+ nanoid "^3.3.12"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+react-dom@^19.2.7:
+ version "19.2.7"
+ resolved "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.7.tgz#0450dc9ae9ddbff76ef196401cd8b8c7fb466ccc"
+ integrity sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==
+ dependencies:
+ scheduler "^0.27.0"
+
+react@^19.2.7:
+ version "19.2.7"
+ resolved "https://registry.npmmirror.com/react/-/react-19.2.7.tgz#1f47a1bfc06f8ec885752c6f4af14369a9f8260b"
+ integrity sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==
+
+rolldown@~1.1.2:
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.3.tgz#87072bfd0d1bdd02a66076a261a62e8e49b3f0e2"
+ integrity sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==
+ dependencies:
+ "@oxc-project/types" "=0.137.0"
+ "@rolldown/pluginutils" "^1.0.0"
+ optionalDependencies:
+ "@rolldown/binding-android-arm64" "1.1.3"
+ "@rolldown/binding-darwin-arm64" "1.1.3"
+ "@rolldown/binding-darwin-x64" "1.1.3"
+ "@rolldown/binding-freebsd-x64" "1.1.3"
+ "@rolldown/binding-linux-arm-gnueabihf" "1.1.3"
+ "@rolldown/binding-linux-arm64-gnu" "1.1.3"
+ "@rolldown/binding-linux-arm64-musl" "1.1.3"
+ "@rolldown/binding-linux-ppc64-gnu" "1.1.3"
+ "@rolldown/binding-linux-s390x-gnu" "1.1.3"
+ "@rolldown/binding-linux-x64-gnu" "1.1.3"
+ "@rolldown/binding-linux-x64-musl" "1.1.3"
+ "@rolldown/binding-openharmony-arm64" "1.1.3"
+ "@rolldown/binding-wasm32-wasi" "1.1.3"
+ "@rolldown/binding-win32-arm64-msvc" "1.1.3"
+ "@rolldown/binding-win32-x64-msvc" "1.1.3"
+
+scheduler@^0.27.0:
+ version "0.27.0"
+ resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
+ integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
+
+source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+tinyglobby@^0.2.17:
+ version "0.2.17"
+ resolved "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631"
+ integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==
+ dependencies:
+ fdir "^6.5.0"
+ picomatch "^4.0.4"
+
+tslib@^2.4.0:
+ version "2.8.1"
+ resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+typescript@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21"
+ integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==
+
+vite@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.npmmirror.com/vite/-/vite-8.1.0.tgz#0734dc1a48faeb2bd5f5b16b66dcbfae484fec55"
+ integrity sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==
+ dependencies:
+ lightningcss "^1.32.0"
+ picomatch "^4.0.4"
+ postcss "^8.5.15"
+ rolldown "~1.1.2"
+ tinyglobby "^0.2.17"
+ optionalDependencies:
+ fsevents "~2.3.3"