6.2 KiB
6.2 KiB
vr_goods_config JSON 规格说明
版本:v2.0 | 日期:2026-04-20 | 状态:已确认,待实现
一、设计原则
- 商品发布时快照:用户在后端选择场馆房间后,将完整的房间数据复制一份存入
goods.vr_goods_config。不从vr_seat_templates实时读取。 - 绝对一致性:修改
vr_seat_templates不影响已发布的商品。SKU(spec_base)和vr_goods_config一起过时、一起更新。 - 向下兼容:保留
template_id字段(用于标识来源),但不再用它去查vr_seat_templates表。 - 单一真相源:前端渲染所需的所有数据(座位图、场次、价格)全部来自
vr_goods_config的快照,不跨表查询。
二、vr_goods_config JSON 结构
[
{
"template_id": 4,
"selected_rooms": ["room_id_1776341371905"],
"selected_sections": {
"room_id_1776341371905": ["A", "B"]
},
"rooms": [
{
"id": "room_id_1776341371905",
"name": "1号放映室VV",
"map": [
"AAAAB__BBB_BAAAA",
"AAAAB__BBB_BAAAA",
"AAAAB__BBB_BAAAA"
],
"sections": [
{ "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" },
{ "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" }
],
"seats": {
"A": { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" },
"B": { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" }
}
}
],
"sessions": [
{ "start": "15:00", "end": "16:59" },
{ "start": "18:00", "end": "21:59" }
]
}
]
字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
template_id |
int | ✅ | 来源场馆模板 ID(用于溯源,不用于查询) |
selected_rooms |
string[] | ✅ | 本商品启用的房间 ID 列表 |
selected_sections |
object | ✅ | key=房间ID,value=启用的分区字符列表(如 ["A","B"]) |
rooms |
object[] | ✅ | 房间完整数据快照(直接复制自 vr_seat_templates.rooms) |
sessions |
object[] | ✅ | 本商品的场次列表 |
rooms.seats 字段说明
seats 是 sections 的快捷索引,key = char(座位字符),格式与 sections 条目相同:
"seats": {
"A": { "char": "A", "name": "VIP区", "price": 100, "color": "#f06292" },
"B": { "char": "B", "name": "看台区", "price": 50, "color": "#4fc3f7" }
}
向下兼容(旧格式迁移)
旧格式(有 vr_seat_templates 表关联逻辑):
{
"template_id": 4,
"sessions": [{"start": "...", "end": "..."}]
}
识别方式:rooms 字段不存在 → 降级读取 vr_seat_templates 表。
三、SKU 生成逻辑(AdminGoodsSaveHandle Hook)
商品保存时,根据 selected_rooms 数组,从 vr_seat_templates.rooms 取出对应房间,展开每个房间的 map 座位,生成 SKU 条目到 goods_spec_base + goods_spec_value。
rooms[room_id].map
└─ 每行字符串(如 "AAAAB__BBB_BAAAA")
└─ 每个非 _ / - 的字符 → 一个 SKU
├─ goods_spec_base.id → 库存主键
├─ goods_spec_base.spec_name → "排:row, 座:colNum"
├─ goods_spec_base.price → seats[char].price
└─ goods_spec_base.spec_type → "vrseat:{room_id}:{char}"
spec_base_id_map(存到 vr_goods_config 的 rooms[] 中)格式:
{
"{room_id}_{row}_{colNum}": goods_spec_base.id
}
注:具体 SKU 生成字段名/存储位置待 AdminGoodsSaveHandle 实现时确认。
四、前端渲染数据流
goods.vr_goods_config(快照)
└─ [0].rooms[] → 前端 JS rooms[]
└─ [0].sessions[] → 场次卡片
└─ [0].selected_sections{} → 控制哪些分区渲染
前端数据结构
// GetGoodsViewData() 注入给模板
{
vr_seat_template: {
rooms: [...], // rooms 快照数组
sessions: [...], // 场次列表
selected_sections: {} // 分区过滤
},
goods_spec_data: [...], // 场次规格(price 来自 goods_spec_base)
goods_config: { ... } // 原始 vr_goods_config[0]
}
loadSoldSeats(已选座位)
从 vr_tickets 表查询该商品+当前场次已生成的票:
SELECT seat_info FROM vrt_vr_tickets
WHERE goods_id = :goods_id AND verify_status != 1
seat_info 格式:"room_id/rowLabel/colNum"(例:room_id_xxx/A/3)
五、GetGoodsViewData() 重写要点
输入:goods_id
输出:
[
'vr_seat_template' => [
'rooms' => [...], // 来自 vr_goods_config[0].rooms
'sessions' => [...], // 来自 vr_goods_config[0].sessions
'selected_sections' => {...}, // 来自 vr_goods_config[0].selected_sections
],
'goods_spec_data' => [...], // 场次+价格(用于前端场次卡片)
'goods_config' => {...} // 原始 vr_goods_config[0]
]
逻辑:
- 读取
goods.vr_goods_configJSON - 若
rooms字段存在 → 直接使用(新格式) - 若
rooms不存在 → 降级:按旧逻辑查vr_seat_templates表(旧格式兼容) - 场次价格从
goods_spec_base表读取
六、需要更新的文件
| 文件 | 操作 | 说明 |
|---|---|---|
SeatSkuService.php |
重写 GetGoodsViewData() | 新 JSON 格式解析 |
ticket_detail.html |
更新 JS | rooms[] 结构渲染 + loadSoldSeats |
docs/VR_GOODS_CONFIG_SPEC.md |
新建 | 本文档,记录 JSON 规格 |
docs/PHASE2_PLAN.md |
更新 | 补充新格式 + 待办 |
docs/DEVELOPMENT_LOG.md |
追加 | 记录本次 JSON 格式升级 |
七、已确认的设计决策
- ✅ 商品发布时快照
vr_seat_templates.rooms到goods.vr_goods_config.rooms - ✅
vr_goods_config包含完整的座位图+sections+seats 数据 - ✅ 前端不跨表查询,全部数据来自
vr_goods_config快照 - ✅
spec_base_id_map格式:{room_id}_{row}_{colNum}→spec_base_id - ✅ 座位已售状态:查
vr_tickets.seat_info(格式:room_id/rowLabel/colNum) - ⚠️ SKU 生成字段名/存储位置:待 AdminGoodsSaveHandle 实现时确认