vr-shopxo-plugin/docs/PLAN_5DIM_REFACTOR.md

359 lines
12 KiB
Markdown
Raw Permalink Normal View History

# Phase 3 P0 — 5维 Spec 重构:演播室层补全
> 版本v1.0 | 日期2026-04-22 | 状态:**P0 阻塞**
---
## 一、问题定义
### 1.1 现象
当前商品详情页ticket_detail.html可以选择
- 场次
- 场馆
- 分区A区/B区/C区
- 座位
但设计文档要求的层级是:
```
场次 → 场馆 → 演播室 → 分区 → 座位号
```
**演播室第3层完全消失**。用户在整个购买流程中感知不到这一层的存在。
### 1.2 影响
- seat_map JSON 没有 `rooms` 字段,只有 flat `sections[]`
- GoodsSpecType 里没有 `$vr-演播室` 记录
- SPEC_DIMS 常量只有 4 维(缺演播室)
- buildSeatSpecMap() 无法输出演播室维度
- 前后端代码虽有 `rooms` fallback 预留,但从未真正启用
---
## 二、数据模型现状
### 2.1 当前 seat_map JSONgoods_id=112座位模板 ID=1
```json
{
"venue": {
"name": "国家体育馆",
"address": "北京市朝阳区奥体中心",
"image": ""
},
"sections": [
{"char": "A", "name": "VIP区", "color": "#e74c3c"},
{"char": "B", "name": "看台", "color": "#3498db"},
{"char": "C", "name": "普通", "color": "#2ecc71"}
],
"map": ["AAAAAA", "BBBBBB", "CCCCCC"],
"seats": {
"A": {"price": 899, "color": "#e74c3c", "label": "VIP"},
"B": {"price": 599, "color": "#3498db", "label": "看台"},
"C": {"price": 299, "color": "#2ecc71", "label": "普通"}
},
"row_labels": ["A", "B", "C"]
}
```
**问题**`sections`、`map`、`seats` 都是 flat 结构,没有 `rooms` 嵌套层。
### 2.2 目标 seat_map JSON
```json
{
"venue": {
"name": "国家体育馆",
"address": "北京市朝阳区奥体中心",
"image": ""
},
"rooms": [
{
"id": "room_001",
"name": "主厅",
"sections": [
{"char": "A", "name": "VIP区", "color": "#e74c3c"},
{"char": "B", "name": "看台", "color": "#3498db"},
{"char": "C", "name": "普通", "color": "#2ecc71"}
],
"map": ["AAAAAA", "BBBBBB", "CCCCCC"],
"seats": {
"A": {"price": 899, "color": "#e74c3c", "label": "VIP"},
"B": {"price": 599, "color": "#3498db", "label": "看台"},
"C": {"price": 299, "color": "#2ecc71", "label": "普通"}
}
}
]
}
```
**变更说明**
- `sections`、`map`、`seats` 从 flat 移入 `rooms[0]`
- `rooms[].id` = 演播室标识(`room_001`
- `rooms[].name` = 演播室名称(`主厅`
- 保留 flat `sections/map/seats` 作为 fallbackAdmin.php:646 和 ticket_detail.html:262 已有兼容逻辑)
- 未来可扩展多个 room多厅模式
### 2.3 当前 GoodsSpecTypegoods_id=112
| ID | name | 含义 | value 示例 |
|-----|----------------|-------------|-----------|
| 1942 | `$vr-场馆` | 场馆 | `[{"name":"国家体育馆","images":""}]` |
| 1943 | `$vr-分区` | 分区 | `[{"name":"A区","images":""},{"name":"B区","images":""},{"name":"C区","images":""}]` |
| 1944 | `$vr-时段` | 场次时间 | `[{"name":"2026-05-01 19:00","images":""}]` |
| 1945 | `$vr-座位号` | 座位号 | `[{"name":"待选座位","images":""}]` |
**注意**:数据库里是 `$vr-时段`SPEC_DIMS 里是 `$vr-场次`,需统一命名。
### 2.4 GoodsSpecValuegoods_id=112
**当前0 条**。
BatchGenerate() 虽然写了 GoodsSpecBaseSKU 存在,有价格/库存),但没有写 GoodsSpecValue维度连接表导致 buildSeatSpecMap() 只能从 seat_map JSON 反推维度,演播室完全丢失。
---
## 三、目标 5 维 Spec 结构
### 3.1 SPEC_DIMS 常量(目标值)
```php
const SPEC_DIMS = [
'$vr-场次', // 第1维场次时间来自 GoodsSpecType
'$vr-场馆', // 第2维场馆名来自 GoodsSpecType
'$vr-演播室', // 第3维演播室新增
'$vr-分区', // 第4维分区/区号(来自 seat_map.rooms[].sections
'$vr-座位号', // 第5维座位号来自 seat_key 的 row_col 部分)
];
```
**命名统一**`$vr-场次` 替代 `$vr-时段`
### 3.2 seat_key 格式(不变)
```
{room_id}_{row_label}_{col_num}
room_001_A_1 → 主厅 A排1号
```
### 3.3 GoodsSpecType 目标记录goods_id=112
| 顺序 | name | 来源 | value 说明 |
|-----|----------------|-------------|-----------|
| 1 | `$vr-场次` | 商品规格维度 | 场次时间列表 |
| 2 | `$vr-场馆` | 商品规格维度 | 场馆名 |
| 3 | `$vr-演播室` | seat_map.rooms[].name | 演播室列表 |
| 4 | `$vr-分区` | seat_map.rooms[].sections[].name | 分区列表 |
| 5 | `$vr-座位号` | seat_key row_col | 座位号(自动生成)|
### 3.4 buildSeatSpecMap() 目标输出
```php
$seatSpecMap['room_001_A_1'] = [
'spec_base_id' => 123,
'price' => 899.00,
'inventory' => 1,
'spec' => [
['type' => '$vr-场次', 'value' => '2026-05-01 19:00'],
['type' => '$vr-场馆', 'value' => '国家体育馆'],
['type' => '$vr-演播室', 'value' => '主厅'], // ← 新增
['type' => '$vr-分区', 'value' => 'VIP区'],
['type' => '$vr-座位号', 'value' => 'A1'],
],
'venueName' => '国家体育馆',
'roomId' => 'room_001',
'roomName' => '主厅', // ← 新增
'section' => ['char' => 'A', 'name' => 'VIP区', 'color' => '#e74c3c'],
'rowLabel' => 'A',
'colNum' => 1,
];
```
---
## 四、受影响文件清单
### 4.1 数据库(需 Migration
| 表 | 操作 | 说明 |
|----|------|------|
| `vrt_goods_spec_type` | 清空 goods_id=112 的旧记录重新插入5条 | 加 `$vr-演播室`,统一 `$vr-场次` |
| `vrt_goods_spec_base` | 保留(已有 sku + seat_key | 不改结构 |
| `vrt_goods_spec_value` | 清空重建按5维生成 | 连接 spec_base_id 和维度 |
| `vrt_vr_seat_templates` | 更新 seat_map JSON | 加 rooms 层 |
| `vrt_vr_goods_config` | 检查 config JSON 是否受影响 | 通常只存 template_id 和快照 |
> 当前数据量极小1个模板0条 GoodsSpecValue可直接 truncate 后重生成。
### 4.2 PHP 文件
| 文件 | 行号 | 改动 |
|------|------|------|
| `SeatSkuService.php` | 29 | `SPEC_DIMS` 改为5维`$vr-演播室``$vr-场次` 替代 `$vr-时段` |
| `SeatSkuService.php` | ~171-178 | `batchGenerate()` 按5维提取维度需加演播室提取 |
| `SeatSkuService.php` | ~270 | `whereIn('name', SPEC_DIMS)` 过滤5个维度 |
| `SeatSkuService.php` | ~306 | 插入缺失维度逻辑按5维顺序 |
| `SeatSkuService.php` | ~522-700 | `buildSeatSpecMap()` 完全重写:从 rooms[] 结构读取,加 roomName 提取 |
| `SeatSkuService.php` | ~648-665 | switch case 加 `$vr-演播室` |
| `BaseService.php` | 187-190 | 维度默认值数组加 `$vr-演播室` |
| `TicketService.php` | 65,68 | `$vr-座位号` / `$vr-分区` 不变 |
| `Admin.php` | ~176-191 | VenueSave 保存 seat_map 时自动包 rooms 层fallback已有 |
### 4.3 前端文件
| 文件 | 改动 |
|------|------|
| `ticket_detail.html` | `specTypeList` 增加 `$vr-演播室` 选择器;`renderAllSelectors()` 渲染演播室选择器 |
| `ticket_detail.html` | `filterSeats()` 增加 `currentRoom` 过滤条件 |
| `ticket_detail.html` | `submit()` 的 spec 数组加 `$vr-演播室` 维度 |
### 4.4 不需要改的文件fallback 已存在)
- `Admin.php:646-653` — 已有 `$seatMap['rooms']` fallback会自动适配新 JSON
- `ticket_detail.html:262` — 已有 `seatMapData.rooms[0].map` fallback
- `ticket_detail.html:269-272` — 已有 `rooms` fallback 逻辑
---
## 五、Migration 执行步骤
> 假设 goods_id=112座位模板 ID=1。
### Step 1更新 seat_map JSON座位模板
```sql
-- 查看当前 seat_map
SELECT id, name, seat_map FROM vrt_vr_seat_templates WHERE id=1;
-- 更新 JSON 结构:加 rooms 层
-- 旧结构 flat sections/map/seats → 移入 rooms[0]
```
JSON 转换伪代码:
```php
$old = json_decode($old_seat_map, true);
$new = [
'venue' => $old['venue'],
'rooms' => [[
'id' => 'room_001',
'name' => '主厅',
'sections' => $old['sections'] ?? [],
'map' => $old['map'] ?? [],
'seats' => $old['seats'] ?? [],
]],
// 保留 flat fallback兼容旧代码
'sections' => $old['sections'] ?? [],
'map' => $old['map'] ?? [],
'seats' => $old['seats'] ?? [],
];
```
### Step 2重建 GoodsSpecType5维
```sql
DELETE FROM vrt_goods_spec_type WHERE goods_id=112;
INSERT INTO vrt_goods_spec_type (goods_id, name, value, add_time) VALUES
(112, '$vr-场次', '[{"name":"2026-05-01 19:00","images":""}]', UNIX_TIMESTAMP()),
(112, '$vr-场馆', '[{"name":"国家体育馆","images":""}]', UNIX_TIMESTAMP()),
(112, '$vr-演播室', '[{"name":"主厅","images":""}]', UNIX_TIMESTAMP()),
(112, '$vr-分区', '[{"name":"A区","images":""},{"name":"B区","images":""},{"name":"C区","images":""}]', UNIX_TIMESTAMP()),
(112, '$vr-座位号', '[{"name":"待选座位","images":""}]', UNIX_TIMESTAMP());
```
### Step 3重建 GoodsSpecValue连接 goods_spec_base 和维度)
当前 GoodsSpecBase 有 sku + extends.seat_key。需要生成 5 条 GoodsSpecValue 记录,每条对应一个维度。
GoodsSpecValue 表结构:
```sql
-- goods_spec_base_id → 哪个 SKU
-- name → 维度名(如 $vr-演播室)
-- value → 维度值(如 主厅)
-- md5_key → 唯一键
```
生成逻辑(参考 buildSeatSpecMap
1. 遍历所有 GoodsSpecBasegoods_id=112, inventory>0
2. 从 extends.seat_key 解析 room_id, row_label, col_num
3. 从 seat_map JSON 反查 roomName通过 room_id
4. 生成 5 条 GoodsSpecValue
### Step 4验证
1. 访问商品详情页,检查 specTypeList 是否包含 5 个维度
2. 检查前端演播室选择器是否正确渲染
3. 选择座位后 submit检查 goods_data 中的 spec 数组是否有 5 个维度
4. 检查 BuyService::BuyGoods 能正确解析 5 维 goods_data
---
## 六、前端交互变更
### 6.1 新的选择器层级
```
[场次选择器] ← goods_spec_data已有
[场馆选择器] ← specTypeList['$vr-场馆'].options已有
[演播室选择器] ← specTypeList['$vr-演播室'].options新增
[分区选择器] ← specTypeList['$vr-分区'].options已有
[座位图] ← 按 currentRoom/currentSection 过滤(已有 filterSeats需加 room 过滤)
```
### 6.2 filterSeats() 变更
```javascript
// 现有
if (self.currentVenue) { matchVenue = ... }
if (self.currentSection) { matchSection = ... }
// 新增
if (self.currentRoom) {
matchRoom = false;
for (var i = 0; i < seatInfo.spec.length; i++) {
if (seatInfo.spec[i].type === '$vr-演播室' && seatInfo.spec[i].value === self.currentRoom) {
matchRoom = true;
break;
}
}
}
```
### 6.3 submit() spec 数组格式(不变)
```javascript
spec: seatInfo.spec // 5维完整数组
```
---
## 七、已知约束
1. **当前是单 room 模式**rooms[0]),演播室选择器默认选主厅,用户不可切换。未来可扩展多 room。
2. **GoodsSpecValue 为 0 的根因**BatchGenerate() 没有写 GoodsSpecValue只有 GoodsSpecBase。这是之前就存在的问题不是本次引入的。本次修复 BatchGenerate 的同时也要补全 GoodsSpecValue。
3. **命名统一**`$vr-时段` → `$vr-场次`,涉及 DB 数据和 SPEC_DIMS 常量。
---
## 八、验收标准
- [ ] seat_map JSON 有 `rooms[]` 结构sections/map/seats 移入 rooms[0]
- [ ] GoodsSpecType 有 5 条记录,包含 `$vr-演播室`
- [ ] SPEC_DIMS 是 5 维数组
- [ ] buildSeatSpecMap() 输出 seatSpecMap 包含 roomName 和 `$vr-演播室` 维度
- [ ] 前端有演播室选择器
- [ ] filterSeats() 按 currentRoom 过滤
- [ ] submit() 提交的 spec 数组有 5 个维度
- [ ] BuyService::BuyGoods 能正确处理 5 维 goods_data
---
## 九、相关文档
- `docs/SESSION_REPORT_20260421_PHASE2_FIX.md` — Phase 2 会话记录
- `docs/FULL_PLAN.md` — 完整开发计划
- `docs/PLAN_GHOST_SPEC_FIX.md` — 幽灵 spec 修复(相关)
- `shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` — 核心服务
- `shopxo/app/plugins/vr_ticket/admin/Admin.php` — 座位模板管理