## 概览
- 新增两个对外页面：`/ticket-search`（车票查询/详情）与`/ticket-order`（线上预定）
- 后端增加“线上预定/凭证”API，售票机支持“NEW/ONLINE”两模式，线上凭证在当天有效
- 页面样式统一沿用现有控制台风格（`web/style.css`），Minecraft 端所有文案保持英文

## 后端改动（Node/Express）
- 新增数据文件：`web/data/orders.json`（列表）、`web/data/order_index.json`（按码索引）
- 新增接口：
  - `POST /api/public/orders`：创建线上预定，入参：`start`、`terminal`、`train_type`、`trips`、`ride_date`（YYYY-MM-DD）。生成唯一5位字母数字凭证 `code`，返回 `{ ok:true, code, price }`。价格通过现有票价数据计算（普通/特急），并按当前促销折扣应用（与`/api/public/config`一致）。
  - `GET /api/public/orders/:code`：查询凭证详情，返回 `{ code, start, terminal, train_type, trips, ride_date, price, created_ts, consumed:false }`。
  - `POST /api/public/orders/:code/consume`：兑票后标记消费（幂等），防重复使用；返回 `{ ok:true }`。
  - `GET /api/public/popular`：根据现有售票事件日志（`web/data/ticket_events.jsonl` 的 `type:'sale'`）统计近7天或全部的热门站点与热门路线，返回 `{ topStations: [{name, code, count}], topRoutes: [{from,to,count}] }`。
- 路由与静态文件：
  - 追加服务器路由以兼容直链：`GET /ticket-search/:id` 与 `GET /ticket-search` 均返回同一静态页面文件（`web/ticket-search.html`）；页面端通过 `location.pathname` 识别 `:id` 并拉取数据。
  - 同理为 `GET /ticket-order` 返回 `web/ticket-order.html`。
- 复用/对齐：价格计算逻辑沿用控制台已有数据源与折扣（`server.js` 已持久化 `stations/lines/fares` 与 `promotion`）。

## 前端页面
- `web/ticket-search.html` + `web/ticket-search.js`：
  - 顶部搜索栏（支持模糊匹配，匹配票号、起点、终点、站点编号）。调用 `GET /api/public/tickets?q=...` 渲染列表，点击进入详情。
  - 详情视图：支持直链 `ticket.fse-media.group/ticket-search/XXXX`，通过 `GET /api/public/tickets/:id` 展示“概览+事件时间线”。
  - 辅助区块：热门站点与热门路线，调用 `GET /api/public/popular`，以卡片或列表展示。
  - 样式统一使用 `web/style.css` 的卡片/列表/按钮体系。
- `web/ticket-order.html` + `web/ticket-order.js`：
  - 流程：选择起点/终点（下拉+模糊搜索）、车型（普通/特急）、乘次、乘车日期（日期选择器）。右侧实时显示价格（调用 `GET /api/public/fares/query` 并应用折扣）。
  - 点击“生成凭证”后调用 `POST /api/public/orders`，展示返回的 5 位字母数字码及说明：“到游戏内售票机付款”。
  - 提供“复制凭证码”、“查看我的凭证详情”按钮（跳到 `/ticket-search/:code`）。

## 售票机 Lua 改动
- 首页两按钮：`NEW`（线下购买）与 `ONLINE`（线上兑票）。位置/大小与现有 `Start` 等同（修改 `showHome`），其余流程保持英文。
- 新增页面：线上凭证输入与验证
  - 屏幕键盘（A–Z、0–9、Backspace、Clear、OK），输入 5 位码；UI使用既有 `addButton`/`waitButtons` 组件。
  - 验证逻辑：
    - `GET http://ticket.fse-media.group/api/public/orders/:code`（或由 `config.json` 的 `api_base` 指到生产域），若返回存在且 `ride_date == os.date('%Y-%m-%d')` 且未 `consumed`，则有效。
    - 有效则将 `state.departure`、`state.terminal`、`state.trainType`、`state.trips`、`state.cost` 赋值并跳转到订单确认/支付页；支付逻辑与线下一致。
    - 无效弹英文提示：`Invalid voucher`。
  - 兑票完成后调用 `POST /api/public/orders/:code/consume` 标记已使用。
- 变更点位置参考：
  - 首页按钮处：在 `Lua/TicketMachine/startup.lua:805..822` 的 `showHome`，替换 `Start` 为 `NEW` 与 `ONLINE` 两个 `addButton`，分别进入现有线下流程与新逻辑。
  - 新增函数如 `showOnlineVoucher()`：与 `showOrderAndAudio()` 类似结构，添加键盘与HTTP校验后复用订单页。
  - HTTP 使用已存在封装：`fetchHTTP()` 与 `jsonDecode()`（`startup.lua:191..198`, `186..189`）。

## 域名与HTTP访问
- 站点部署到 `ticket.fse-media.group`，售票机需允许该域名的HTTP访问：
  - Minecraft 1.13+：在 `serverconfig/computercraft-server.toml` 中移除或调整 `[[http.rules]] host = "$private" action = "deny"`（参见 Tweaked CC 文档）。
  - 老版本或单机不同路径，参考提供的文档片段。
- 售票机 `config.json` 或 `API_ENDPOINT.txt` 指向生产 API 基础地址，`startup.lua` 中 `API_BASE` 也将改为生产地址。

## 文案与样式约定
- Web 前端：中文页面与控件；
- CC:T 端所有提示、按钮、错误信息统一英文（现有实现已如此，新增页面按该规范）。
- 整体风格沿用 `web/style.css` 控制台样式（深色/浅色模式兼容）。

## 交付物
- 新增/修改的文件：
  - `web/ticket-search.html`, `web/ticket-search.js`
  - `web/ticket-order.html`, `web/ticket-order.js`
  - `web/server.js`（新增`orders`/`popular`接口与静态路由）
  - `web/data/orders.json`, `web/data/order_index.json`
  - `Lua/TicketMachine/startup.lua`（首页按钮与线上凭证流程）

## 验证
- 后端：本地启动后用 `GET /api/public/popular`、`POST /api/public/orders`、`GET /api/public/orders/:code` 联调；
- 前端：打开 `/ticket-order` 生成凭证 → 复制码直链查看 `/ticket-search/:code`；
- 售票机：触发 `ONLINE` 流程，输入码验证跨天逻辑与支付流程，兑票后标记消费成功。

## 兼容/风险控制
- 所有新增逻辑均以新文件/新接口为主，避免破坏现有控制台；
- 前端/后端的模糊搜索初期采用子串匹配，后续可替换为更精细的打分；
- 若 HTTP 被禁，售票机将回退到线下流程，界面提示网络不可用。