From 977cc57aefcc1babef52dc2bc54cb33ba33d076b Mon Sep 17 00:00:00 2001 From: Council Date: Mon, 20 Apr 2026 07:24:57 +0800 Subject: [PATCH] docs: new vr_goods_config spec + Phase 2 v2.0 plan --- docs/DEVELOPMENT_LOG.md | 59 ++++++++++ docs/PHASE2_PLAN.md | 210 ++++++++++++++++++----------------- docs/VR_GOODS_CONFIG_SPEC.md | 190 +++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+), 99 deletions(-) create mode 100644 docs/VR_GOODS_CONFIG_SPEC.md diff --git a/docs/DEVELOPMENT_LOG.md b/docs/DEVELOPMENT_LOG.md index 078f3b2..605effd 100644 --- a/docs/DEVELOPMENT_LOG.md +++ b/docs/DEVELOPMENT_LOG.md @@ -445,3 +445,62 @@ dc63cff77 chore: clean up my_test_plugin residual hooks - `shopxo/test_ticket.php` → 移至 `_backup_20260420/test_ticket.php`(临时测试脚本,不入仓库) - `docs/14_TEMPLATE_RENDER_INVESTIGATION.md` → 重写修正版(删除错误信息,保留调查价值) - 核心代码(Goods.php / SeatSkuService.php / TicketService.php)→ 全部提交推送 + +--- + +## 十二、模板渲染修复 + JSON 格式升级(2026-04-20 白天) + +### 12.1 模板渲染修复(v2.0 路线 B) + +**问题**:ThinkTemplate 的 `{include file="public/head"}` 标签在 Linux 下因 `view_depr=/` 导致路径拼接错误,页面以纯文本输出。 + +**解决方案(路线 B)**: +1. `{include}` / `{:}` ThinkTemplate 标签 → `` 原生 PHP +2. `{$var|default='...'}` → `` +3. `{json_decode(...)|raw}` → `` +4. 复制 ShopXO `app/index/view/default/public/` → `plugins/vr_ticket/view/goods/public/` + +**提交记录**: +``` +349ec063c fix: 替换 ThinkTemplate 标签为 PHP ModuleInclude +c894e7018 fix: 复制 ShopXO public 模板 + 修复 footer_page 不存在问题 +1b0ac3276 fix: 替换为票务专用精简 footer(449行→53行) +``` + +**渲染结果**:✅ 商品详情页正常渲染,但场次为空(待适配新 JSON 格式)。 + +### 12.2 vr_goods_config JSON 格式重新设计(重大变更) + +**背景**:大头 + Gemini 重新设计了 vr_goods_config 规格,从依赖 `vr_seat_templates` 表实时查询,改为商品发布时快照模式。 + +**新格式核心**: +- `goods.vr_goods_config` 包含完整的 `rooms[]` 快照(座位图+sections+seats) +- 不再需要实时查 `vr_seat_templates` 表 +- `spec_base_id_map` 格式:`{room_id}_{row}_{colNum}` → `spec_base_id` + +**设计原则**: +- 商品发布时快照 → 已发布商品与 `vr_seat_templates` 解耦 +- 修改模板不影响已发布商品 → 绝对一致性 +- SKU 和 config 一起过时、一起更新 + +**新文档**: +- `docs/VR_GOODS_CONFIG_SPEC.md` — 新 JSON 格式完整规格说明(已确认) +- `docs/PHASE2_PLAN.md` v2.0 — 同步更新,下一步工作计划 + +### 12.3 当前 Git 状态 + +``` +1b0ac3276 fix: 替换为票务专用精简 footer ← HEAD +c894e7018 fix: 复制 ShopXO public 模板 +349ec063c fix: 替换 ThinkTemplate 标签为 PHP ModuleInclude +7bd896764 feat(Phase 2): 完成票务商品前端展示层 +``` + +### 12.4 接下来需要实现 + +| 任务 | 负责人 | 依赖 | +|------|--------|------| +| 重写 GetGoodsViewData() 适配新格式 | 待定 | VR_GOODS_CONFIG_SPEC.md 已确认 | +| 更新 ticket_detail.html JS(rooms[] 结构) | 待定 | GetGoodsViewData() 输出确定后 | +| AdminGoodsSaveHandle SKU 生成 | 待定 | 新格式已确认 | +| loadSoldSeats() 实现 | 待定 | vr_tickets 有数据后 | diff --git a/docs/PHASE2_PLAN.md b/docs/PHASE2_PLAN.md index a8bf379..cf575cb 100644 --- a/docs/PHASE2_PLAN.md +++ b/docs/PHASE2_PLAN.md @@ -1,146 +1,158 @@ # Phase 2 — 计划与当前状态 -> 版本:v1.0 | 日期:2026-04-20 | 状态:执行中 -> 关联提交:7bd896764 +> 版本:v2.0 | 日期:2026-04-20 | 状态:执行中 +> 关联提交:c894e7018(模板渲染已通)、1b0ac3276(精简 footer) +> 关联文档:`docs/VR_GOODS_CONFIG_SPEC.md`(新 JSON 格式规格) --- -## 一、Phase 2 完成情况 +## ⚠️ 重大更新(v2.0) + +**vr_goods_config JSON 格式已重新设计。** + +旧格式依赖 `vr_seat_templates` 表实时查询,新格式在商品发布时将 `rooms` 快照存入商品表,前端不再跨表查询。 + +详见 `docs/VR_GOODS_CONFIG_SPEC.md`。 + +--- + +## 一、Phase 2 当前状态 ### ✅ 已完成 -| 任务 | 文件 | 说明 | +| 任务 | 提交 | 说明 | |------|------|------| -| Goods.php 改法 | `app/index/controller/Goods.php` | item_type=ticket → ticket_detail.html + 数据注入 | -| GetGoodsViewData() | `SeatSkuService.php` | 为前端模板提供座位图+场次数据 | -| onOrderPaid() 修复 | `TicketService.php` | sxo_order_detail + JSON spec 解析 | -| 模板渲染调研 | `docs/14_TEMPLATE_RENDER_INVESTIGATION.md` | 已修正,记录完整调查过程 | +| 模板渲染 | c894e7018 | ThinkTemplate → PHP ModuleInclude,渲染正常 | +| 票务专用 footer | 1b0ac3276 | 精简 footer,移除 ShopXO 默认导航 | +| Goods.php 改法 | 7bd896764 | item_type=ticket → ticket_detail.html | +| onOrderPaid() 修复 | 7bd896764 | sxo_order_detail + JSON spec 解析 | -### ⚠️ 待验证(容器内) +### ⚠️ 进行中 -| 任务 | 优先级 | 说明 | -|------|--------|------| -| `{include}` 标签实测 | P0 | ticket_detail.html 中 `{include file="public/head"}` 是否能正确解析 | -| ModuleInclude 备选方案 | P1 | 若 `{include}` 失败,切换 `ModuleInclude()` | -| loadSoldSeats() | P1 | 查询已售座位,前端座位图需显示已售状态 | +| 任务 | 说明 | 依赖 | +|------|------|------| +| GetGoodsViewData() 重写 | 适配新 vr_goods_config JSON 格式 | VR_GOODS_CONFIG_SPEC.md 已确认 | +| 前端 JS 更新 | 适配 rooms[] 结构渲染 | GetGoodsViewData() 输出格式确定后 | +| loadSoldSeats() 实现 | 查询 vr_tickets,标记已售座位 | vr_tickets 表有数据后 | ### ❌ 未开始 | 任务 | 说明 | |------|------| -| vr_ticket Hook.php 钩子补充 | 缺失 `plugins_service_goods_spec_data` 处理 | -| 后台座位模板管理 | admin/controller/SeatTemplate.php(已生成,未调试) | -| 后台电子票列表 | admin/controller/Ticket.php(已生成,未调试) | -| 后台核销员管理 | admin/controller/Verifier.php(已生成,未调试) | -| 后台核销记录 | admin/controller/Verification.php(已生成,未调试) | -| 核销 API | B 端扫码核销 REST 接口 | +| AdminGoodsSaveHandle SKU 生成 | 根据 selected_rooms 生成 goods_spec_base 条目 | +| 后台 4 控制器联调 | SeatTemplate/Ticket/Verifier/Verification | +| 核销 API | POST /api/vr_ticket/verify | --- -## 二、模板渲染问题现状 +## 二、vr_goods_config 新格式(已确认) -### 问题 - -票务商品详情页 ThinkTemplate 标签未解析(`{$...}` / `{include}` / `{if}` 以原文输出)。 - -### 根因 - -Goods.php 原来用 `MyView()` 加载主题模板,票务商品需要加载插件独立模板 `ticket_detail.html`。 - -### 解决路径(Goods.php 绝对路径方案) - -``` -Goods::Index() - → $goods['item_type'] === 'ticket' - → SeatSkuService::GetGoodsViewData($goods_id) - → MyViewAssign([vr_seat_template, goods_spec_data]) - → View::fetch($tplFile) [$tplFile = 绝对路径] - → ThinkTemplate 渲染 ticket_detail.html(含 {include} 标签) +```json +[ + { + "template_id": 4, + "selected_rooms": ["room_id_xxx"], + "selected_sections": { "room_id_xxx": ["A", "B"] }, + "rooms": [ + { + "id": "room_id_xxx", + "name": "1号放映室VV", + "map": ["AAAAB__BBB_BAAAA", "AAAAB__BBB_BAAAA"], + "sections": [ + { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" }, + { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" } + ], + "seats": { + "A": { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" }, + "B": { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" } + } + } + ], + "sessions": [ + { "start": "15:00", "end": "16:59" }, + { "start": "18:00", "end": "21:59" } + ] + } +] ``` -### 待实测项(容器内操作) +**核心变化**: +- `rooms[]` 包含完整座位图+sections+seats,不再查 `vr_seat_templates` 表 +- `selected_sections` 控制每个房间内渲染哪些分区 +- `spec_base_id_map` 格式:`{room_id}_{row}_{colNum}` → `spec_base_id` -```bash -# 在 shopxo-php 容器内 -docker exec -it shopxo-php bash -cd /var/www/html -curl "http://localhost:10000/?s=goods/index/id/118.html" +完整规格见 `docs/VR_GOODS_CONFIG_SPEC.md`。 -# 检查: -# 1. {include file="public/head"} 是否被解析为 HTML 内容 -# 2. {$goods.title} 是否显示商品标题 -# 3. 座位图是否正常渲染 -``` +--- -### 失败备选 +## 三、下一步工作 -若 `{include}` 标签失败,修改 `ticket_detail.html`: +### Step 1(立即):重写 GetGoodsViewData() + +**目标**:适配新 JSON 格式,从 `goods.vr_goods_config` 直接读取 rooms[],注入前端模板。 + +**新输出格式**: ```php - -{include file="public/head" /} - - +[ + 'vr_seat_template' => [ + 'rooms' => [...], // 直接透传 vr_goods_config[0].rooms + 'sessions' => [...], // 直接透传 vr_goods_config[0].sessions + 'selected_sections' => {...}, // 直接透传 vr_goods_config[0].selected_sections + ], + 'goods_spec_data' => [...], // 场次+价格(用于前端卡片) + 'goods_config' => {...} // 原始 vr_goods_config[0] +] ``` +**降级兼容**:若 `vr_goods_config` 中无 `rooms` 字段,按旧逻辑查 `vr_seat_templates` 表。 + +### Step 2(立即):更新 ticket_detail.html JS + +**目标**:适配 `rooms[]` 结构,多房间支持。 + +**改动点**: +1. `seatMap` → `rooms[]`(数组,每个房间一个座位图) +2. `renderSeatMap()` → `renderRoom(roomIndex)`(按房间渲染) +3. `specBaseIdMap` 格式变为 `{room_id}_{row}_{colNum}` → `spec_base_id` +4. `loadSoldSeats()` 实现:查 `vr_tickets.seat_info` 格式为 `room_id/rowLabel/colNum` + +### Step 3:AdminGoodsSaveHandle SKU 生成 + +**目标**:用户选择房间后,在商品保存时自动生成 `goods_spec_base` SKU 条目。 + +**逻辑**:展开 rooms[].map,生成每个座位的 SKU,存入 `spec_base_id_map`。 + --- -## 三、Phase 2 接下来的工作 +## 四、模板渲染当前状态 -### Step 1:模板渲染实测(容器内) - -**操作人:** 大头(容器在本机) - -```bash -docker ps | grep shopxo-php # 确认容器运行中 -curl -s "http://localhost:10000/?s=goods/index/id/118.html" | head -50 -``` - -**成功标准:** HTML 源码中不再有 ThinkTemplate 标签(`{include}` / `{$` / `{if}`),座位图 div 正常显示。 - -### Step 2:座位图已售状态 - -SeatSkuService 需要补充 `loadSoldSeats()` 方法,查询 `vr_tickets` 表中该商品+场次已生成的票,返回已售座位 ID 列表,前端据此灰化已售座位。 - -### Step 3:后台管理页面联调 - -4 个后台控制器(SeatTemplate / Ticket / Verifier / Verification)均已生成,需要: -1. 确认路由可访问(后台 URL 格式) -2. 验证 CRUD 操作正常 -3. 确认 RLS 策略 - -### Step 4:核销 API - -`POST /api/vr_ticket/verify` — B 端小程序扫码调用。 +| 项目 | 状态 | +|------|------| +| 模板渲染 | ✅ 正常(PHP ModuleInclude 方案) | +| 票务 footer | ✅ 已精简 | +| 场次显示 | ❌ 待适配新 JSON 格式 | +| 座位图渲染 | ❌ 待适配 rooms[] 结构 | +| 已售座位标记 | ❌ 待实现 loadSoldSeats() | --- -## 四、数据库表结构(当前) +## 五、数据库表结构(当前) | 表名 | 用途 | 状态 | |------|------|------| -| `vrt_vr_seat_templates` | 座位模板 | ✅ | +| `vrt_vr_seat_templates` | 场馆模板(rooms 母版) | ✅ | | `vrt_vr_tickets` | 电子票 | ✅ | | `vrt_vr_verifiers` | 核销员 | ✅ | | `vrt_vr_verifications` | 核销记录 | ✅ | -| `vrt_vr_audit_log` | 审计日志 | ✅ | -| `goods.vr_goods_config` | 商品配置(JSON) | ✅ | -| `sxo_order_detail` | 订单明细(ShopXO) | ✅ | +| `goods.vr_goods_config` | 商品配置快照(新 JSON 格式) | ⚠️ 待适配 | +| `goods_spec_base` | SKU 库存(ShopXO 原生平表) | ⚠️ 待自动生成 | --- -## 五、已知风险 +## 六、已知风险 | 风险 | 影响 | 缓解 | |------|------|------| -| `{include}` 标签容器内解析失败 | P0,页面无样式 | 切换 ModuleInclude 方案 | -| shopxo-php 容器未启动 | 无法验证 | 每次操作前 `docker ps` 确认 | -| Admin 控制器鉴权链不完整 | 后台无法访问 | 确认继承 Common 并调用 IsLogin/IsPower | -| 座位模板与商品分类绑定逻辑 | 需确认 1:N 还是 1:1 | 实测验证 | - ---- - -## 六、决策点(待大头确认) - -1. **模板 include 方案**:先试 `{include}`,失败后换 `ModuleInclude()`,还是直接内联 CSS/JS? -2. **loadSoldSeats()**:是否需要实时查库,还是前端纯靠 JS 状态管理? -3. **后台前端框架**:Layui 是否继续使用,还是改用其他方案? +| vr_goods_config 仍是旧格式 | 场次/座位图不显示 | AdminGoodsSaveHandle 生成新格式后可解决 | +| 旧版 GetGoodsViewData 未适配新格式 | 前端无数据 | Step 1 完成后解决 | +| spec_base_id_map 格式变化 | 已选座位提交逻辑需同步更新 | Step 2 中同步更新 JS | diff --git a/docs/VR_GOODS_CONFIG_SPEC.md b/docs/VR_GOODS_CONFIG_SPEC.md new file mode 100644 index 0000000..61e1c2b --- /dev/null +++ b/docs/VR_GOODS_CONFIG_SPEC.md @@ -0,0 +1,190 @@ +# vr_goods_config JSON 规格说明 + +> 版本:v2.0 | 日期:2026-04-20 | 状态:已确认,待实现 + +--- + +## 一、设计原则 + +1. **商品发布时快照**:用户在后端选择场馆房间后,将完整的房间数据**复制一份**存入 `goods.vr_goods_config`。不从 `vr_seat_templates` 实时读取。 +2. **绝对一致性**:修改 `vr_seat_templates` 不影响已发布的商品。SKU(spec_base)和 `vr_goods_config` 一起过时、一起更新。 +3. **向下兼容**:保留 `template_id` 字段(用于标识来源),但不再用它去查 `vr_seat_templates` 表。 +4. **单一真相源**:前端渲染所需的所有数据(座位图、场次、价格)全部来自 `vr_goods_config` 的快照,不跨表查询。 + +--- + +## 二、vr_goods_config JSON 结构 + +```json +[ + { + "template_id": 4, + "selected_rooms": ["room_id_1776341371905"], + "selected_sections": { + "room_id_1776341371905": ["A", "B"] + }, + "rooms": [ + { + "id": "room_id_1776341371905", + "name": "1号放映室VV", + "map": [ + "AAAAB__BBB_BAAAA", + "AAAAB__BBB_BAAAA", + "AAAAB__BBB_BAAAA" + ], + "sections": [ + { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" }, + { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" } + ], + "seats": { + "A": { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" }, + "B": { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" } + } + } + ], + "sessions": [ + { "start": "15:00", "end": "16:59" }, + { "start": "18:00", "end": "21:59" } + ] + } +] +``` + +### 字段说明 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `template_id` | int | ✅ | 来源场馆模板 ID(用于溯源,不用于查询) | +| `selected_rooms` | string[] | ✅ | 本商品启用的房间 ID 列表 | +| `selected_sections` | object | ✅ | key=房间ID,value=启用的分区字符列表(如 `["A","B"]`) | +| `rooms` | object[] | ✅ | 房间完整数据快照(直接复制自 `vr_seat_templates.rooms`) | +| `sessions` | object[] | ✅ | 本商品的场次列表 | + +### rooms.seats 字段说明 + +`seats` 是 `sections` 的快捷索引,key = `char`(座位字符),格式与 `sections` 条目相同: +```json +"seats": { + "A": { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" }, + "B": { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" } +} +``` + +### 向下兼容(旧格式迁移) + +旧格式(有 `vr_seat_templates` 表关联逻辑): +```json +{ + "template_id": 4, + "sessions": [{"start": "...", "end": "..."}] +} +``` +识别方式:`rooms` 字段不存在 → 降级读取 `vr_seat_templates` 表。 + +--- + +## 三、SKU 生成逻辑(AdminGoodsSaveHandle Hook) + +商品保存时,根据 `selected_rooms` 数组,从 `vr_seat_templates.rooms` 取出对应房间,展开每个房间的 `map` 座位,生成 SKU 条目到 `goods_spec_base` + `goods_spec_value`。 + +``` +rooms[room_id].map + └─ 每行字符串(如 "AAAAB__BBB_BAAAA") + └─ 每个非 _ / - 的字符 → 一个 SKU + ├─ goods_spec_base.id → 库存主键 + ├─ goods_spec_base.spec_name → "排:row, 座:colNum" + ├─ goods_spec_base.price → seats[char].price + └─ goods_spec_base.spec_type → "vrseat:{room_id}:{char}" +``` + +**spec_base_id_map(存到 vr_goods_config 的 rooms[] 中)格式:** +```json +{ + "{room_id}_{row}_{colNum}": goods_spec_base.id +} +``` + +> 注:具体 SKU 生成字段名/存储位置待 AdminGoodsSaveHandle 实现时确认。 + +--- + +## 四、前端渲染数据流 + +``` +goods.vr_goods_config(快照) + └─ [0].rooms[] → 前端 JS rooms[] + └─ [0].sessions[] → 场次卡片 + └─ [0].selected_sections{} → 控制哪些分区渲染 +``` + +### 前端数据结构 + +```javascript +// GetGoodsViewData() 注入给模板 +{ + vr_seat_template: { + rooms: [...], // rooms 快照数组 + sessions: [...], // 场次列表 + selected_sections: {} // 分区过滤 + }, + goods_spec_data: [...], // 场次规格(price 来自 goods_spec_base) + goods_config: { ... } // 原始 vr_goods_config[0] +} +``` + +### loadSoldSeats(已选座位) + +从 `vr_tickets` 表查询该商品+当前场次已生成的票: +```sql +SELECT seat_info FROM vrt_vr_tickets +WHERE goods_id = :goods_id AND verify_status != 1 +``` +`seat_info` 格式:`"room_id/rowLabel/colNum"`(例:`room_id_xxx/A/3`) + +--- + +## 五、GetGoodsViewData() 重写要点 + +**输入**:`goods_id` + +**输出**: +```php +[ + 'vr_seat_template' => [ + 'rooms' => [...], // 来自 vr_goods_config[0].rooms + 'sessions' => [...], // 来自 vr_goods_config[0].sessions + 'selected_sections' => {...}, // 来自 vr_goods_config[0].selected_sections + ], + 'goods_spec_data' => [...], // 场次+价格(用于前端场次卡片) + 'goods_config' => {...} // 原始 vr_goods_config[0] +] +``` + +**逻辑**: +1. 读取 `goods.vr_goods_config` JSON +2. 若 `rooms` 字段存在 → 直接使用(新格式) +3. 若 `rooms` 不存在 → 降级:按旧逻辑查 `vr_seat_templates` 表(旧格式兼容) +4. 场次价格从 `goods_spec_base` 表读取 + +--- + +## 六、需要更新的文件 + +| 文件 | 操作 | 说明 | +|------|------|------| +| `SeatSkuService.php` | 重写 GetGoodsViewData() | 新 JSON 格式解析 | +| `ticket_detail.html` | 更新 JS | rooms[] 结构渲染 + loadSoldSeats | +| `docs/VR_GOODS_CONFIG_SPEC.md` | 新建 | 本文档,记录 JSON 规格 | +| `docs/PHASE2_PLAN.md` | 更新 | 补充新格式 + 待办 | +| `docs/DEVELOPMENT_LOG.md` | 追加 | 记录本次 JSON 格式升级 | + +--- + +## 七、已确认的设计决策 + +1. ✅ 商品发布时快照 `vr_seat_templates.rooms` 到 `goods.vr_goods_config.rooms` +2. ✅ `vr_goods_config` 包含完整的座位图+sections+seats 数据 +3. ✅ 前端不跨表查询,全部数据来自 `vr_goods_config` 快照 +4. ✅ `spec_base_id_map` 格式:`{room_id}_{row}_{colNum}` → `spec_base_id` +5. ✅ 座位已售状态:查 `vr_tickets.seat_info`(格式:`room_id/rowLabel/colNum`) +6. ⚠️ SKU 生成字段名/存储位置:待 AdminGoodsSaveHandle 实现时确认