vr-shopxo-plugin/reviews/council-phase2-assessment.md

260 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# VR 演唱会票务小程序 Phase 2 技术评估报告
> 日期2026-04-21
> 协作产出BackendArchitect、FrontendDev、FirstPrinciples
> 源码依据BuyService.php、GoodsCartService.php、SeatSkuService.php、ticket_detail.html
---
## 执行摘要
Phase 2 完成了对 4 个已知问题的根因分析,识别了 1 个未被提及的潜在 Bug多场次并给出了分优先级的修复方案。
**结论**:所有 4 个问题均为可修复的技术问题,无架构级障碍。
---
## 问题总览
| # | 问题 | 优先级 | 根因分类 |
|---|------|--------|---------|
| 1 | 购买提交流程失效 | **P0** | GET→POST 机制错误 + 参数契约不匹配 |
| 2 | 缩放时舞台不跟随 | **P1** | DOM 结构导致 transform 不共享 |
| 3 | spec 加载机制回滚 | **P1** | ShopXO spec 匹配机制不兼容 + API 端点缺失 |
| 4 | 商品详情/图片加载 | **P2** | 模板未引入 ShopXO 内容渲染组件 |
**新增发现(潜在 Bug**
| # | 问题 | 优先级 |
|---|------|--------|
| 5 | GetGoodsViewData() 只返回第一个场次 | **P2 潜在** |
---
## Issue 1P0购买提交流程失效
### 根因分析(三层叠加)
**第一层(致命)**`location.href` 产生 GET 请求,但 `Buy::Index()` 只在 `data_post` 为真时调用 `BuyDataStorage()`
```php
// Buy.php:56-62
public function Index()
{
if($this->data_post) {
BuyService::BuyDataStorage($this->user['id'], $this->data_post);
return MyRedirect(MyUrl('index/buy/index'));
} else {
// GET 分支:从 session 读取URL 参数从未被读取
$buy_data = BuyService::BuyDataRead($this->user['id']);
}
}
```
`goods_params` URL 参数从未被读取 → `BuyDataStorage` 从未被调用 → `BuyDataRead` 返回空 → "商品数据为空"。
**第二层(严重)**:字段名不匹配。
- 前端发送:`goods_params`JSON 数组)
- ShopXO 期望:`goods_data`JSON 数组)
**第三层(中等)**:规格匹配机制不兼容。
- 当前:`spec_base_id: parseInt(specBaseId)` — 直接传 ID
- ShopXO`spec: [{type, value}]` — 通过 type:value 字符串匹配 GoodsSpecValue 表
### 推荐修复
**前端FrontendDev**
```javascript
// 隐藏表单 POST最小化变更复用 Buy 链路)
submit: function() {
var goodsDataList = this.selectedSeats.map(function(seat, i) {
var specBaseId = self.specBaseIdMap[seat.seatKey] || self.sessionSpecId;
return {
goods_id: self.goodsId,
spec: [{type: '座位', value: seat.seatKey}], // ShopXO 规格格式
stock: 1
};
});
var form = document.createElement('form');
form.method = 'POST';
form.action = MyUrl('index/buy/index');
var input = document.createElement('input');
input.name = 'goods_data';
input.value = Base64.encode(JSON.stringify(goodsDataList));
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
```
**关于 extension_data观演人信息**ShopXO 原生不支持 `extension_data`OrderDetail 表无此字段)。推荐方案:新增 `vr_order_attendee` 表,在 `BuyService::OrderInsert()` 后存储观演人信息。
### 关键提醒FirstPrinciples
> Issue 1 的准确描述是「Buy 传输机制损坏」,而非「购物车格式错误」。
>
> 当前代码实际上绕过了购物车,直接进入 Buy 链路。这说明直觉上的「绕过购物车」需求并不存在——只是 `submit()` 的传递方式错了。
---
## Issue 2P1缩放时舞台不跟随
### 根因分析
```html
<div class="vr-seat-map-wrapper">
<div class="vr-stage">舞 台</div> <!-- 平级 sibling -->
<div class="vr-seat-rows" id="seatRows"></div>
</div>
```
CSS `transform: scale()` 只作用于应用元素的子树。舞台和座位行是平级元素,缩放座位时舞台不动。
### 推荐修复
**方案:将舞台和座位包裹在同一 zoom 容器内**FrontendDev 实施)
```html
<div class="vr-seat-map-wrapper">
<div class="vr-zoom-container" id="zoomContainer">
<div class="vr-stage">舞 台</div>
<div class="vr-seat-rows" id="seatRows"></div>
</div>
</div>
```
```css
.vr-zoom-container {
display: flex;
flex-direction: column;
align-items: center;
transform-origin: center top;
transition: transform 0.2s ease;
}
```
**注意**:舞台的 `border-radius` 在缩放后可能变形,需在 zoom 场景下单独调整。
---
## Issue 3P1spec 加载机制回滚
### 根因分析
**问题 A**`loadSoldSeats()` 是空 stub无任何网络请求。
```javascript
loadSoldSeats: function() {
// TODO: 从后端加载已售座位
// $.get(...); // 空,无任何调用
}
```
**问题 B**ShopXO 的 `GoodsSpecDetail` 通过 `spec.value` 字符串匹配规格,而非直接接受 `spec_base_id`
```php
// GoodsService.php:2749-2757
$spec = array_column($params['spec'], 'value');
$where['value'] = $spec;
$ids = Db::name('GoodsSpecValue')->where($where)->column('goods_spec_base_id');
```
VR 票务场景下,每个座位对应独立的 `GoodsSpecBase` 记录inventory=1。ShopXO 标准流程需要生成 `GoodsSpecValue` 记录type='座位', value='seatKey')。
### 推荐修复
**后端BackendArchitect**:新增插件 API 端点
```
GET /?s=api/vr-ticket/sold-seats&goods_id=X&spec_base_id=Y
Response: {code: 0, data: {sold_seats: ["A_1", "A_2", "B_5"]}}
```
前端在选中场次后调用此接口,标记 `.sold` class。
**关于 spec 加载**ShopXO 的 spec 数据通过 `GetGoodsViewData()` 在模板渲染时注入前端。如果已生成了 `GoodsSpecBase` 记录,最直接的方式是维护座位→规格映射,并提供独立的已售座位查询 API而不是依赖 ShopXO 的规格匹配流程。
---
## Issue 4P2商品详情/图片加载
### 现状
- **商品内容**`$goods['content']`):✅ 正常渲染
- **商品相册**`$goods['images']`):⚠️ 数据存在但未使用
- `renderSessions()` 依赖 `goods_spec_data`,不含 `images`
- `.vr-goods-photos` 已定义样式但从未被调用
- **`.goods-detail-content` CSS**:⚠️ 缺失
### 建议
如需展示商品图片,在模板中添加图片渲染逻辑。如票务详情页不需要 ShopXO 商品内容区,可降级为「确认不需要」。
---
## Issue 5P2 潜在GetGoodsViewData 只返回第一个场次
### 根因分析
```php
// SeatSkuService.php:BatchGenerate()
return array_merge($data, [
'goods_spec_data' => $validConfigs[0], // ← 只取第一个配置
'vr_seat_template' => $template,
]);
```
`validConfigs[0]` 意味着多场次商品只返回第一个场次的座位模板和规格数据。当一个商品配置了多场演唱会时,其余场次对用户不可见。
### 修复方向
修改 `BatchGenerate()` 返回格式,将 `goods_spec_data` 改为数组(`$validConfigs`),前端根据选中场次索引读取对应数据。
---
## 第一性原则视角的关键提醒
1. **P0 的真正来源**`submit()` 用 GET 做 POST 的事。Buy 链路本身可用,不需要重构 spec 系统。
2. **spec_base_id_map 是性能缓存**:不是业务必需。如果 `onOrderPaid` 能通过 seatKey 查询到 spec_base_idmap 可以去掉。保留它是合理的优化,但需要确保同步机制。
3. **购物车对票务无价值**:当前实现已绕过购物车进入 Buy 链路。选座的实时性决定了购物车会增加超卖风险,快速购买是正确方向。
4. **已售座位展示是 P1不是 P0**:真正的 P0 是 `onOrderPaid` 防双售。前端是否实时显示已售状态,是体验优化,不是业务正确性的根本。
5. **`onOrderPaid` 是座位唯一性权威**(未审计):在 P0 修复部署前,必须验证此 Hook 是否正确实现了座位锁定逻辑。若未实现,任何前端修复都无法防止重复销售。
6. **最小修复范围**:只需修复 `submit()` 的传递方式(隐藏表单 POST。不需要重构 spec 系统,不需要引入实时已售座位更新(除非 spec 加载方案已实施)。
---
## 修复优先级与分工
| 优先级 | 问题 | 负责 | 修复说明 |
|--------|------|------|---------|
| P0 | Issue 1 submit() | FrontendDev | 改用隐藏表单 POST复用 Buy 链路 |
| P1 | Issue 2 舞台缩放 | FrontendDev | 新增 zoom wrapper 容器 |
| P1 | Issue 3 spec 加载 | BackendArchitect | 新增插件 sold_seats API 端点 |
| P2 | Issue 4 商品详情 | FrontendDev | 确认是否需要,补充 CSS |
| P2 | Issue 5 多场次 | BackendArchitect | BatchGenerate 返回数组格式 |
| FP | extension_data | BackendArchitect | 新增 vr_order_attendee 表 |
---
## 附录ShopXO Buy 链路关键代码索引
| 文件 | 行号 | 说明 |
|------|------|------|
| `Buy.php` | 56-62 | Index() — POST/GET 分支BuyDataStorage/BuyDataRead |
| `BuyService.php` | ~51 | BuyGoods — goods_data 参数校验 + base64 解码 |
| `BuyService.php` | ~173 | GoodsSpecificationsHandle — 规格解析 |
| `BuyService.php` | ~104-109 | GoodsSpecDetail 调用 — 通过 spec.value 匹配 |
| `GoodsService.php` | 2720-2795 | GoodsSpecDetail — type:value 查询 GoodsSpecValue |
| `BuyService.php` | 1932-1936 | BuyDataStorage — session 缓存21600s TTL |
| `SeatSkuService.php` | BatchGenerate | 返回 validConfigs[0] — 多场次 Bug |
---
*VR 演唱会票务小程序 Phase 2 技术评估 — Council 协作完成*