250 lines
8.0 KiB
Markdown
250 lines
8.0 KiB
Markdown
# 选座系统 + 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 = 1(FOR 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 '座位地图JSON(map[] + 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 分类 ID,ShopXO 后台商品编辑时的"分类选择"结果直接对应
|
||
- `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` ← 每个座位一个 SKU(inventory=1,price=座位价格)
|
||
- 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 可完全主导全部 phases(A-E)**。
|
||
|
||
---
|
||
|
||
## 五、关键约束确认
|
||
|
||
| 维度 | 限制 | 结论 |
|
||
|---|---|---|
|
||
| spec_type 数量 | 无硬限制 | ✅ 想加几个加几个 |
|
||
| 单规格选项数 | 无硬限制 | ✅ 500座/场馆没问题 |
|
||
| SKU 组合总数 | MySQL 无压力 | ✅ 3×2×500=3000行 OK |
|
||
| TEXT 字段容量 | 无实际限制 | ✅ JSON 存几千选项 OK |
|
||
| ShopXO 后台扩展 | 通过插件 Hook | ✅ 完全可行 |
|
||
| 自提点独立库存 | ShopXO 不支持 | ✅ 用 spec 替代(每座位独立库存)|
|