vr-shopxo-plugin/docs/06_SEAT_MAP_INTEGRATION.md

250 lines
8.0 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.

# 选座系统 + ShopXO 后台集成架构
> 调研日期2026-04-14
> 关联文档ARCHITECTURE.md, 01_SHOPXO_TECHNICAL_RESEARCH.md
---
## 一、选座地图:行业标准做法
### 1.1 核心原理
**"字符地图"是业界通用方案**,不是我们发明的:
```
'aaa___aaa' ← a=可用座, _=过道/柱子/墙壁
'bbb__bbbbb' ← b=另一种座位(不同价格区)
'____________' ← 纯过道/无座位
```
加载时前端把每个字符翻译成可交互的 DOM/SVG 元素:
- 可选座位 → 可点击
- 过道 `_` → 渲染为空白间隔或装饰元素
- 不同字符类型 → 不同颜色/价格/状态
### 1.2 主流实现对比
| 方案 | 技术 | 优点 | 缺点 |
|---|---|---|---|
| **字符地图 + DOM/SVG** | 字符串地图 + div/SVG | 轻量、易编辑、易生成 | 复杂形状需精确计算 |
| **SVG 手绘** | 设计师导出 SVG | 座位形状自然 | 需要设计工具,导入复杂 |
| **Canvas** | Konva.js / Fabric.js | 性能好,适合超大型场馆 | 无 DOM 元素,交互复杂 |
| **seats.io** | 商业 SaaS | 功能完整 | 付费,不可定制 |
**推荐:字符地图 + Vue 3 SVG 渲染**自研AI 可完全生成)
### 1.3 座位地图 JSON 结构
```json
{
"venue_id": "venue_001",
"map": [
"aaaaaaaaaaaa",
"aaaaaaaaaaaa",
"bbbbbbbb__bb",
"bbbbbbbbbbbb",
"__cccccccccc__"
],
"row_labels": ["A", "B", "C", "D", "E"],
"seats": {
"a": { "price": 299, "label": "VIP区", "classes": "seat-vip" },
"b": { "price": 199, "label": "普通区", "classes": "seat-normal" },
"c": { "price": 99, "label": "后排区", "classes": "seat-back" },
"_": null
},
"sections": [
{ "name": "VIP区", "color": "#FF6B6B", "rows": [0, 1] },
{ "name": "普通区", "color": "#4ECDC4", "rows": [2, 3] }
],
"screen": { "label": "舞台/银幕", "position": "top" }
}
```
### 1.4 座位实时状态(动态层)
```json
{
"seats": {
"1_1": { "status": "available" },
"1_2": { "status": "sold" },
"1_3": { "status": "selected" },
"2_5": { "status": "locked" }
}
}
```
座位状态含义:
- `available` — 可选
- `sold` — 已售
- `selected` — 当前用户选中
- `locked` — 被其他用户临时锁定(可选,支持超时释放)
### 1.5 spec_base_id_map与 ShopXO SKU 绑定)
```json
{
"spec_base_id_map": {
"1_1": { "spec_base_id": 10001, "venue": "A区", "row": "A", "col": 1, "price": 299 },
"1_2": { "spec_base_id": 10002, "venue": "A区", "row": "A", "col": 2, "price": 299 },
"3_5": { "spec_base_id": 10003, "venue": "B区", "row": "C", "col": 5, "price": 199 }
}
}
```
**绑定流程**
```
用户在前端选座 seat_id="3_5"
→ 查 spec_base_id_map 拿到 spec_base_id=10003
→ 调 ShopXO Buy API: goods_id + spec_base_id
→ ShopXO 原子扣 spec_base.inventory = 1FOR UPDATE
→ 订单完成
```
---
## 二、ShopXO 后台集成方案
### 2.1 核心设计:复用 ShopXO 分类作为 Venue Type
**每个 venue type = 一个 ShopXO 分类**
ShopXO 已有完整的商品多级分类系统(`sxo_goods_category`),我们直接复用:
```
sxo_goods_category
├── 演唱会
│ ├── 杭州大剧院-演唱会场
│ ├── 北京鸟巢-演唱会场 ← 绑定 seat_map_json
│ └── 上海梅赛德斯-演唱会场
├── 话剧
│ ├── 人艺剧院
│ └── 国家大剧院
└── 电影(可选)
```
**优点**
- 不需要改 `sxo_goods` 表结构
- 直接用 ShopXO 原生"分类选择器"(后台商品编辑已有,无需开发)
- 商家在 ShopXO 后台创建商品时,分类 = venue type 绑定一步到位
### 2.2 场馆表vr_venues
```sql
CREATE TABLE vr_venues (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
category_id INT UNSIGNED NOT NULL COMMENT 'ShopXO分类ID绑定venue type',
name VARCHAR(180) NOT NULL COMMENT '场馆名称',
address VARCHAR(255) NOT NULL DEFAULT '' COMMENT '场馆地址',
seat_map_json LONGTEXT COMMENT '座位地图JSONmap[] + seats配置',
seat_base_price INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '基础票价',
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '0下架 1上架',
add_time INT UNSIGNED NOT NULL DEFAULT 0,
upd_time INT UNSIGNED NOT NULL DEFAULT 0,
INDEX idx_category_id (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='VR演唱会场馆';
```
**关键字段**
- `category_id`ShopXO 分类 IDShopXO 后台商品编辑时的"分类选择"结果直接对应
- `seat_map_json`:座位地图配置,商家在插件后台导入/编辑
- `seat_base_price`:基础票价
### 2.3 插件后台:场馆 + 座位图编辑器
商家在 ShopXO 后台插件入口管理:
1. **场馆管理**:创建场馆,关联 ShopXO 分类,上传/绘制座位图
2. **座位图编辑器**:输入字符串地图(如 `aaaaaaaaaaaa`)或上传 SVG
3. **场次管理**:创建演出场次,选择场馆,设定时间、价格
### 2.4 插件 Hook向 ShopXO 商品保存/读取注入 venue 信息
```php
// plugins_service_goods_handle_begin — 保存商品时
public static function GoodsSaveHandle(&$params, &$goods, $goods_id)
{
// venue_id 由插件后台单独保存,不依赖 ShopXO 商品表
// 关联vr_venues ← (goods_id) → ShopXO sxo_goods
}
// plugins_service_goods_data — 读取商品时,注入 venue 信息
public static function GoodsDataHandle(&$data, $goods_id)
{
// 读取该商品关联的 venue + 场次列表
$data['vr_venues'] = VrVenueService::GetVenueByGoodsId($goods_id);
$data['vr_sessions'] = VrSessionService::GetSessionsByGoodsId($goods_id);
}
```
### 2.5 商品详情页加载流程
```
用户打开票务商品详情页
→ 触发 Goods.php Hook 判断 item_type=ticket
→ 插件读取 goods_id 对应的 vr_venues + vr_sessions
→ 前端展示:
│ 步骤1选择场次日期+时间)
│ 步骤2加载该场次座位图从 seat_map_json 渲染 SVG
│ 步骤3用户点击座位 → 获取 spec_base_id
│ 步骤4调 ShopXO Buy API 购买该 SKU
```
---
## 三、与 ShopXO spec 系统的衔接
### 3.1 座位图与 ShopXO SKU 的绑定时机
**场次创建时自动生成 SKU 映射**
```php
// 场次保存时,调用 SKU 绑定函数
public static function BindSessionToSpecBase($session_id)
{
// 1. 读取 vr_sessions.seat_map_json
// 2. 遍历 map[],为每个"非_"字符生成/查找 spec_base_id
// 3. 生成 spec_base_id_map 存入 vr_sessions
// 4. 调用 ShopXO GoodsSpecificationsInsert() 写入 spec_base 表
}
```
**绑定关系**
- `vr_sessions.spec_base_id_map` ← JSON 映射seat_id → spec_base_id
- `sxo_goods_spec_base` ← 每个座位一个 SKUinventory=1price=座位价格)
- ShopXO `BuyService::OrderInsertHandle` ← 原子扣 inventory天然防超卖
### 3.2 场次变更时的 SKU 联动
- **场次新增座位**:调用 `GoodsSpecificationsInsert` 新增 spec_base
- **场次删除座位**:将对应 spec_base.inventory 置为 0软删除
- **价格变更**:更新 `sxo_goods_spec_base.price`
---
## 四、实现优先级
| 阶段 | 内容 | 工作量 |
|---|---|---|
| **Phase A** | vr_venues / vr_sessions 表 + CRUD | 小 |
| **Phase B** | 场馆座位图编辑器(字符串地图) | 中 |
| **Phase C** | Vue 3 选座组件(渲染 + 交互) | 中 |
| **Phase D** | spec_base_id_map 绑定逻辑 | 中 |
| **Phase E** | 实时座位状态轮询/推送 | 小 |
**AI 可完全主导全部 phasesA-E**
---
## 五、关键约束确认
| 维度 | 限制 | 结论 |
|---|---|---|
| spec_type 数量 | 无硬限制 | ✅ 想加几个加几个 |
| 单规格选项数 | 无硬限制 | ✅ 500座/场馆没问题 |
| SKU 组合总数 | MySQL 无压力 | ✅ 3×2×500=3000行 OK |
| TEXT 字段容量 | 无实际限制 | ✅ JSON 存几千选项 OK |
| ShopXO 后台扩展 | 通过插件 Hook | ✅ 完全可行 |
| 自提点独立库存 | ShopXO 不支持 | ✅ 用 spec 替代(每座位独立库存)|