# 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` |