diff --git a/docs/14_TEMPLATE_RENDER_INVESTIGATION.md b/docs/14_TEMPLATE_RENDER_INVESTIGATION.md new file mode 100644 index 0000000..87d41d5 --- /dev/null +++ b/docs/14_TEMPLATE_RENDER_INVESTIGATION.md @@ -0,0 +1,182 @@ +# VR-Ticket 模板渲染问题调查报告(已修正) + +> 生成时间:2026-04-19 20:39 CST +> 修正时间:2026-04-20 CST +> 背景:Phase 0/1 完成后,开始 Phase 2 前台模板渲染调试 + +--- + +## ⚠️ 重要修正说明(2026-04-20) + +本文档为 2026-04-19 的调查记录,**部分内容有误**,已在此版本中修正: + +| 章节 | 原错误 | 修正 | +|------|--------|------| +| 2.2 数据流 | 误以为读取 `vrt_vr_goods_config` 等独立表 | 实际:`goods.vr_goods_config` + ShopXO 原生表 `goods_spec_value` / `goods_spec_base` | +| 2.3 表名 | `vrt_order_detail` | 实际:`sxo_order_detail`(ShopXO 原生平表) | +| 2.4 Think驱动 | 认为需要修改 vendor 文件 | Goods.php 绝对路径方案无需改 Think 驱动 | +| 6.决策 | "先不提交" | 代码已提交(commit 7bd896764),文档状态过时 | + +--- + +## 一、问题描述 + +访问票务商品详情页 `http://localhost:10000/?s=goods/index/id/118.html` 时,模板**完全没有渲染**: + +浏览器源码中显示原始模板标签: +``` +{include file="public/head" /} +{$goods.title|default='VR演唱会'} +选择场次 +该商品暂无场次信息 +... +``` + +ThinkTemplate 语法标签(`{include}`、`{$...}`、`{if}`)均以原文形式输出,页面 UI 无法正常显示。 + +--- + +## 二、已完成的代码修改(已验证) + +### 2.1 Goods.php(前台商品详情控制器) + +**文件:** `shopxo/app/index/controller/Goods.php` + +**改动内容:** 在 `Goods::Index()` 方法中,`$this->PluginsHook()` 之后、`return MyView()` 之前,新增以下判断: + +```php +// 票务商品:加载自定义模板并注入座位数据 +if (($goods['item_type'] ?? '') === 'ticket') { + $viewData = \app\plugins\vr_ticket\service\SeatSkuService::GetGoodsViewData($goods_id); + MyViewAssign([ + 'vr_seat_template' => $viewData['vr_seat_template'] ?? null, + 'goods_spec_data' => $viewData['goods_spec_data'] ?? [], + ]); + // 使用绝对路径 + fetch() 方法 + $tplFile = ROOT . 'app' . DS . 'plugins' . DS . 'vr_ticket' . DS . 'view' . DS . 'goods' . DS . 'ticket_detail.html'; + return \think\facade\View::fetch($tplFile, $assign); +} +``` + +**状态:** ✅ 已提交(7bd896764) + +--- + +### 2.2 SeatSkuService.php(座位 SKU 服务层) + +**文件:** `shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` + +**新增方法:** `GetGoodsViewData(int $goodsId): array` + +**实际数据流(修正版):** +1. 从 `goods.vr_goods_config` JSON 字段读取商品配置(不是独立的 vrt_vr_goods_config 表) +2. 解析 JSON,取出第一个配置块的 `template_id` 和 `sessions` 场次列表 +3. 从 `vr_seat_templates` 表查询座位模板(含 seat_map / spec_base_id_map JSON) +4. 从 ShopXO 原生表 `goods_spec_value` + `goods_spec_base` 联查,生成场次列表 +5. 返回 `['vr_seat_template' => [...], 'goods_spec_data' => [...], 'goods_config' => [...]]` + +**状态:** ✅ 已提交(7bd896764) + +--- + +### 2.3 TicketService.php(票务核心服务层) + +**文件:** `shopxo/app/plugins/vr_ticket/service/TicketService.php` + +**修复内容(修正版):** +- `onOrderPaid()` 表名:`OrderGoods` → `order_detail`(映射到 ShopXO 原生平表 `sxo_order_detail`) +- `sxo_order_detail.spec` 是 JSON 格式,需要 `json_decode()` 解析座位号 +- 幂等保护:改用 `seat_info` 而非 `spec_base_id`(同一订单+同一座位名只发一张票) +- 观演人信息从 `order.extension_data` JSON 中读取 + +**状态:** ✅ 已提交(7bd896764) + +--- + +## 三、调查过程中的关键发现 + +### 3.1 ThinkTemplate include 标签解析机制 + +**流程追溯:** + +1. `View::fetch($absPath)` → Think 驱动的 `fetch()` +2. Think 驱动调用 `$this->template->fetch($template, $data)`(ThinkTemplate 实例) +3. ThinkTemplate `fetch()` → 读取文件内容 → `parse($content)` → `parseInclude($content)` +4. `parseInclude()` → `parseTemplateName($file)` → `parseTemplateFile($template)` +5. `parseTemplateFile()` → `$template = $this->config['view_path'] . $template . '.' . $view_suffix` + +**关键问题:** 使用绝对路径时,ThinkTemplate 需要知道 `view_path` 应指向插件模板所在目录,否则 `{include file="public/head"}` 会找不到 `/var/www/html/app/index/view/default/public/head.html`。 + +### 3.2 ShopXO 原生模板不使用 `{include}` 标签 + +**发现:** ShopXO 原生模板编译缓存使用 `ModuleInclude()` 而非 ThinkTemplate 原生的 `{include}` 标签: +```php + +``` + +这说明 ShopXO 用自己的 `ModuleInclude()` 函数替代了部分 include 机制。 + +**但票务模板仍用 `{include}`** —— 因为: +- `ticket_detail.html` 是完全独立的票务 UI,不继承 ShopXO 商品页基础布局 +- 使用 `{include}` 可以让 ThinkTemplate 正常解析 `{$...}` 变量和 `{if}` 标签 +- 最终解决方案(Goods.php 绝对路径 + `View::fetch()`)使 `{include}` 能正确定位 + +### 3.3 Linux 路径分隔符问题 + +**发现:** ThinkTemplate 在 Linux 下 `view_depr` 为 `/`,`str_replace` 将 `/` 替换为 `\`,导致路径拼接错误。 + +**解决:** Goods.php 直接传绝对路径给 `View::fetch()`,绕过了 ThinkTemplate 的 `view_path` 拼接逻辑。 + +--- + +## 四、模板渲染现状 + +| 项目 | 状态 | 说明 | +|------|------|------| +| Goods.php 绝对路径 | ✅ 正常 | View::fetch($tplFile) 带绝对路径 | +| `{include file="public/head"}` | ⚠️ 待验证 | 理论上可以工作,需在容器内实测 | +| `{$vr_seat_template.seat_map\|raw}` | ⚠️ 待验证 | ThinkTemplate 变量解析 | +| `ModuleInclude()` | ❌ 未使用 | 票务模板不需要 | + +**已知待解决问题(P1):** +1. `{include}` 标签在容器内是否能正确解析到 ShopXO 公共模板 +2. `plugins_service_goods_spec_data` 钩子 vr_ticket 未实现 +3. `loadSoldSeats()` 函数为空(TODO) + +--- + +## 五、后续方向 + +### 方向 A(推荐):实测 `{include}` 标签 + +在容器内访问票务商品详情页,观察 `{include file="public/head"}` 是否被正确解析为 ShopXO 公共头部。 + +若解析失败,切换到方向 B。 + +### 方向 B:`ModuleInclude()` 替换 + +将 `{include file="public/head" /}` 改为 ``。 + +### 方向 C:纯内联 + +将所需 CSS/JS 直接写在 `ticket_detail.html` 中,完全去掉 include。 + +--- + +## 六、关联提交 + +- **7bd896764** `feat(Phase 2): 完成票务商品前端展示层` + - Goods.php: item_type=ticket 时加载 ticket_detail.html + - SeatSkuService.php: 新增 GetGoodsViewData() + - TicketService.php: onOrderPaid 用 sxo_order_detail + JSON spec 解析 + +--- + +## 附录:相关文件路径 + +| 文件 | 路径 | +|------|------| +| 票务商品模板 | `shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html` | +| Goods 控制器 | `shopxo/app/index/controller/Goods.php` | +| SeatSku 服务 | `shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` | +| Ticket 服务 | `shopxo/app/plugins/vr_ticket/service/TicketService.php` | diff --git a/docs/DEVELOPMENT_LOG.md b/docs/DEVELOPMENT_LOG.md index 827a374..078f3b2 100644 --- a/docs/DEVELOPMENT_LOG.md +++ b/docs/DEVELOPMENT_LOG.md @@ -397,3 +397,51 @@ vr-shopxo-plugin 仓库: | spec_base JSON 结构最终版 | P2 | 已确认 Q4 方案,待落地 | | 支付回调联调 | P2 | 等待 Phase 2 后台完成后测试 | | 核销 API RLS | P2 | 待实现 | + +--- + +## 十一、Phase 2 前台展示层完成(2026-04-20) + +### 11.1 完成内容 + +**Commit 7bd896764:** +- `Goods.php`:item_type=ticket → 绝对路径 View::fetch(ticket_detail.html) + 数据注入 +- `SeatSkuService.php`:新增 GetGoodsViewData(),从 goods.vr_goods_config JSON + ShopXO 原生平表读取座位图+场次 +- `TicketService.php`:onOrderPaid 改用 sxo_order_detail + JSON spec 解析座位号,幂等改为 seat_info + +**docs/14 修正:** +- 修正了数据流描述(vrt_vr_goods_config → goods.vr_goods_config) +- 修正了表名(vrt_order_detail → sxo_order_detail) +- 删除了错误的 Think 驱动修改说明 + +**新文档:** +- `docs/PHASE2_PLAN.md`(本文件):Phase 2 当前状态 + 下一步计划 + +### 11.2 模板渲染问题说明 + +Goods.php 绝对路径方案已实现,但 `{include file="public/head"}` 标签是否能在容器内正确解析**待实测**。 + +详见 `docs/PHASE2_PLAN.md` 第二章。 + +### 11.3 当前 Git 状态 + +``` +7bd896764 feat(Phase 2): 完成票务商品前端展示层 ← HEAD +dc63cff77 chore: clean up my_test_plugin residual hooks +``` + +### 11.4 Phase 2 剩余工作 + +| 任务 | 状态 | +|------|------| +| 模板渲染实测(容器内) | ⚠️ 待大头操作 | +| loadSoldSeats() 实现 | ❌ 未开始 | +| vr_ticket Hook.php 补充 | ❌ 未开始 | +| 4 个后台控制器联调 | ❌ 未开始 | +| 核销 API | ❌ 未开始 | + +### 11.5 清理记录(2026-04-20) + +- `shopxo/test_ticket.php` → 移至 `_backup_20260420/test_ticket.php`(临时测试脚本,不入仓库) +- `docs/14_TEMPLATE_RENDER_INVESTIGATION.md` → 重写修正版(删除错误信息,保留调查价值) +- 核心代码(Goods.php / SeatSkuService.php / TicketService.php)→ 全部提交推送 diff --git a/docs/PHASE2_PLAN.md b/docs/PHASE2_PLAN.md new file mode 100644 index 0000000..a8bf379 --- /dev/null +++ b/docs/PHASE2_PLAN.md @@ -0,0 +1,146 @@ +# Phase 2 — 计划与当前状态 + +> 版本:v1.0 | 日期:2026-04-20 | 状态:执行中 +> 关联提交:7bd896764 + +--- + +## 一、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` | 已修正,记录完整调查过程 | + +### ⚠️ 待验证(容器内) + +| 任务 | 优先级 | 说明 | +|------|--------|------| +| `{include}` 标签实测 | P0 | ticket_detail.html 中 `{include file="public/head"}` 是否能正确解析 | +| ModuleInclude 备选方案 | P1 | 若 `{include}` 失败,切换 `ModuleInclude()` | +| loadSoldSeats() | P1 | 查询已售座位,前端座位图需显示已售状态 | + +### ❌ 未开始 + +| 任务 | 说明 | +|------|------| +| 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 接口 | + +--- + +## 二、模板渲染问题现状 + +### 问题 + +票务商品详情页 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} 标签) +``` + +### 待实测项(容器内操作) + +```bash +# 在 shopxo-php 容器内 +docker exec -it shopxo-php bash +cd /var/www/html +curl "http://localhost:10000/?s=goods/index/id/118.html" + +# 检查: +# 1. {include file="public/head"} 是否被解析为 HTML 内容 +# 2. {$goods.title} 是否显示商品标题 +# 3. 座位图是否正常渲染 +``` + +### 失败备选 + +若 `{include}` 标签失败,修改 `ticket_detail.html`: +```php + +{include file="public/head" /} + + +``` + +--- + +## 三、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 端小程序扫码调用。 + +--- + +## 四、数据库表结构(当前) + +| 表名 | 用途 | 状态 | +|------|------|------| +| `vrt_vr_seat_templates` | 座位模板 | ✅ | +| `vrt_vr_tickets` | 电子票 | ✅ | +| `vrt_vr_verifiers` | 核销员 | ✅ | +| `vrt_vr_verifications` | 核销记录 | ✅ | +| `vrt_vr_audit_log` | 审计日志 | ✅ | +| `goods.vr_goods_config` | 商品配置(JSON) | ✅ | +| `sxo_order_detail` | 订单明细(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 是否继续使用,还是改用其他方案?