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

218 lines
8.3 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.

# Council Phase 2 技术评估报告
> 协作产出 | 日期2026-04-21
> 参与 AgentBackendArchitect、FrontendDev、FirstPrinciples
---
## 问题总览
| # | 问题 | 优先级 | 根因分类 |
|---|------|--------|---------|
| 1 | 购物车提交格式错误 | P0 | API 传递方式 + 参数契约不匹配 |
| 2 | 缩放时舞台元素不跟随 | P1 | DOM 结构导致 CSS transform 不共享 |
| 3 | spec 加载问题(已回滚) | P1 | ShopXO 规格匹配机制 + API 链路不明确 |
| 4 | 商品详情/图片加载 | P2 | 模板未引入 ShopXO 商品内容组件 |
---
## Issue 1P0购物车提交格式错误
### 根因分析(三层)
**第一层(致命)**`location.href` 产生 GET 请求,但 `Buy::Index()` 只在 `$this->data_post` 时处理下单逻辑。
```php
// Buy.php:58-61
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 表
### 推荐修复(前后端)
**前端BackendArchitect + FrontendDev 联合)**
```javascript
// 方案 A隐藏表单 POST最小化变更
submit: function() {
var goods_data = this.selectedSeats.map(function(seat, i) {
return {
goods_id: self.goodsId,
spec: [{type: '座位', value: seat.seatKey}], // ← ShopXO 规格格式
stock: 1,
extension_data: JSON.stringify({attendee: attendeeData[i], seat: {...}})
};
});
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 = JSON.stringify(goods_data);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
```
**后端BackendArchitect**
- 方案 B推荐在插件中新增 `VrTicketBuy` 控制器,复用 BuyService 链路,但绕过 ShopXO 的 spec 匹配(直接通过 spec_base_id 查询)
- 方案 C`BuyService::BuyInitialize` 中增加插件扩展点,允许 vr_ticket 插件注入自定义的规格处理逻辑
### API 设计建议
当前实现是「用 GET 做 POST 的事」。正确的做法是:
1. 隐藏表单 POST `goods_data``index/buy/index`ShopXO 原生)
2. 或者插件新增端点POST `goods_data``plugins/vr_ticket/buy-direct`
---
## Issue 2P1缩放时舞台元素不跟随
### 根因分析
```html
<div class="vr-seat-map-wrapper">
<div class="vr-stage">舞 台</div> <!-- 舞台wrapper 直接子元素 -->
<div class="vr-seat-rows" id="seatRows"></div> <!-- 座位行 -->
</div>
```
CSS `transform: scale()` 只作用于应用元素的子树。`.vr-stage` 和 `.vr-seat-rows` 是平级,没有共同的 transform 容器。
### 推荐修复FrontendDev
**方案:将舞台和座位行包裹在同一 zoom 容器内**
```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;
}
```
JS 缩放时,操作 `#zoomContainer``transform: scale(X)`,舞台和座位同步缩放。
**注意**:舞台的 `border-radius: 50% 50% 0 0 / 20px 20px 0 0` 弧形在缩放后可能变形,需要在 zoom 场景下调整。
---
## Issue 3P1spec 加载问题
### 根因分析
**问题 A**ShopXO 的 `GoodsSpecDetail` 通过 `spec.value` 字符串匹配规格,而非直接接受 `spec_base_id`
```php
// GoodsService.php:2749-2757
$spec = array_column($params['spec'], 'value'); // ['A_1', 'VIP']
$where['value'] = $spec;
$ids = Db::name('GoodsSpecValue')->where($where)->column('goods_spec_base_id');
```
VR 票务场景下,每个座位对应独立的 `GoodsSpecBase` 记录inventory=1。要通过 ShopXO 标准流程加载,需要为每个座位生成 `GoodsSpecValue` 记录type='座位', value='A_1')。
**问题 B**:插件 API 端点未建立,导致前端 `loadSoldSeats()` 是 TODO stub。
### 推荐修复
**后端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"]}}
```
前端在选中场次后调用此接口,标记已售座位。
**关于 ShopXO spec 机制**:如果 VR 票务已经生成了 `GoodsSpecBase` 记录,最直接的方式是让插件维护座位→规格的映射,并提供独立的已售座位查询 API而不是依赖 ShopXO 的规格匹配流程。
---
## Issue 4P2商品详情/图片加载
### 根因分析
`ticket_detail.html` 是插件独立模板ShopXO 商品的 `content_web` 和图片数据由 `Goods.php Index()` 加载,但插件模板可能未正确引入这些数据。
### 推荐修复
确认 ticket_detail.html 是否需要 ShopXO 商品内容渲染。如果需要,应该在模板中引入 ShopXO 的商品内容组件:
- 商品详情:`$goods['content_web']` 由 GoodsService 处理
- 商品图册:通过 `ResourcesService` 获取
如果票务 UI 不需要 ShopXO 商品内容区(票务详情页有自己的布局),则此问题可降级为「确认不需要」。
---
## 第一性原则视角的关键提醒FirstPrinciples
1. **P0 的真正来源**submit() 的 URL 重定向方式错了,修复后 Buy 链路本身是可用的——不需要重构 spec 系统。
2. **spec_base_id_map 是性能缓存**:不是业务必需。如果 `onOrderPaid` 能通过 seatKey 查询到 spec_base_idmap 可以去掉。保留它是合理的优化,但需要确保同步机制。
3. **购物车对票务无价值**:当前实现已经在用 Buy 链路,不是 Cart 链路。说明直觉上的「绕过购物车」需求其实不存在——只是 submit() 的传递方式错了。
4. **已售座位展示是 P1不是 P0**:真正的 P0 是 `onOrderPaid` 防双售。前端是否实时显示已售状态,是体验优化,不是业务正确性的根本。
5. **多场次 bug**`GetGoodsViewData()` 只返回第一个配置的场次(取 `validConfigs[0]`)。如果一个商品有多个场次配置,只显示第一个——这是潜在的 bug。
6. **最小修复范围**:只需修复 submit() 的传递方式(隐藏表单 POST不需要重构 spec 系统,不需要引入实时已售座位更新(除非 spec 加载方案已实施)。
---
## 修复优先级与分工
| 优先级 | 问题 | 负责方 | 修复说明 |
|--------|------|--------|---------|
| P0 | Issue 1 submit() | BackendArchitect + FrontendDev | 改用隐藏表单 POST复用 Buy 链路 |
| P1 | Issue 2 舞台缩放 | FrontendDev | 新增 zoom wrapper 容器 |
| P1 | Issue 3 spec 加载 | BackendArchitect | 新增插件 API 端点 |
| P2 | Issue 4 商品详情 | 延后 | 确认是否需要 |
---
## 附录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 |
---
*Council Phase 2 技术评估报告 — 由 BackendArchitect、FrontendDev、FirstPrinciples 协作完成*