183 lines
6.9 KiB
Markdown
183 lines
6.9 KiB
Markdown
# 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
|
||
<?php echo ModuleInclude('public/header'); ?>
|
||
```
|
||
|
||
这说明 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" /}` 改为 `<?php echo ModuleInclude('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` |
|