2026-04-15 13:15:45 +00:00
|
|
|
|
# 后台编辑器 + 商品发布注入方案设计
|
|
|
|
|
|
|
|
|
|
|
|
> 版本:v1.0 | 日期:2026-04-15 | 状态:**待大头确认后执行**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 一、整体架构一句话
|
|
|
|
|
|
|
|
|
|
|
|
**插件在 ShopXO 后台建一套"场馆配置"管理界面,用户发布票务商品时,选场馆 → 插件自动生成海量 Spec 并注入商品,用户全程不碰 ShopXO 原生 Spec 管理。**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 二、三大核心区域
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ ShopXO 原生后台 │
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ┌─────────────────┐ ┌──────────────────────┐ │
|
|
|
|
|
|
│ │ 商品管理 │ │ 插件专属后台(新增) │ │
|
|
|
|
|
|
│ │ 发布/编辑商品 │ ←→ │ 场馆配置管理 │ │
|
|
|
|
|
|
│ │ (注入点) │ │ 座位分区模板编辑器 │ │
|
|
|
|
|
|
│ └─────────────────┘ │ 场次配置 │ │
|
|
|
|
|
|
│ └──────────────────────┘ │
|
|
|
|
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 区域 A:插件专属后台(新增)
|
|
|
|
|
|
|
|
|
|
|
|
商户在 ShopXO 后台左侧菜单进入「VR票务」:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
VR票务
|
|
|
|
|
|
├── 场馆配置 ← 新增
|
|
|
|
|
|
├── 座位模板 ← 已存在(Phase 2)
|
|
|
|
|
|
├── 电子票管理 ← 已存在
|
|
|
|
|
|
├── 核销员管理 ← 已存在
|
|
|
|
|
|
└── 核销记录 ← 已存在
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**场馆配置**管理的内容:
|
|
|
|
|
|
- 场馆名称、地址、图片
|
|
|
|
|
|
- 该场馆下的分区(Zone)列表:VIP区/看台区/普通区
|
|
|
|
|
|
- 每个分区的基础价格、颜色配置
|
|
|
|
|
|
- 座位排布(每排几个座位,用字母+数字标记如 A_1, A_2)
|
|
|
|
|
|
|
|
|
|
|
|
**数据落地**:`vr_seat_templates` 表的 `seat_map` JSON 字段(venue 信息作为 JSON 顶层嵌入)。
|
|
|
|
|
|
|
|
|
|
|
|
### 区域 B:ShopXO 商品发布页(注入点)
|
|
|
|
|
|
|
|
|
|
|
|
商户在 ShopXO 后台「商品管理 → 添加商品」:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
添加商品
|
|
|
|
|
|
[商品名称] [商品分类]
|
|
|
|
|
|
|
|
|
|
|
|
▼ 规格型号 ← ShopXO 原生区域,我们注入票务选择器
|
|
|
|
|
|
票务配置 ← 新增:插件注入的区域
|
|
|
|
|
|
[请选择场馆 ▼] ← 场馆下拉
|
|
|
|
|
|
[请选择分区 ▼] ← 分区多选(根据场馆联动)
|
|
|
|
|
|
|
|
|
|
|
|
[商品详情 富文本编辑器]
|
|
|
|
|
|
▼ 其他Tab(参数/图片等)← ShopXO 原生
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 区域 C:插件商品详情页(已存在)
|
|
|
|
|
|
|
|
|
|
|
|
用户在前台看到票务商品详情页,选座下单,这个已实现。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 三、场馆配置管理(区域 A)
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 数据结构
|
|
|
|
|
|
|
|
|
|
|
|
场馆 + 分区 + 座位,全部编码进 `vr_seat_templates.seat_map` 一个 JSON:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"venue": {
|
|
|
|
|
|
"name": "国家体育馆",
|
|
|
|
|
|
"address": "北京市朝阳区",
|
|
|
|
|
|
"image": "/uploads/vr/venue/1.jpg"
|
|
|
|
|
|
},
|
|
|
|
|
|
"map": ["AAAAAA", "BBBBBB", "CCCCCC"],
|
|
|
|
|
|
"zones": {
|
|
|
|
|
|
"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": [
|
|
|
|
|
|
{ "char": "A", "name": "VIP区", "color": "#e74c3c" },
|
|
|
|
|
|
{ "char": "B", "name": "看台区", "color": "#3498db" },
|
|
|
|
|
|
{ "char": "C", "name": "普通区", "color": "#2ecc71" }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**关键理解**:venue 信息和 seat_map 存在同一个 JSON 里,不拆表。`vr_seat_templates` 表的每一行 = 一个场馆配置(包含分区和座位布局)。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 场馆配置管理页面(表单可视化编辑器)
|
|
|
|
|
|
|
|
|
|
|
|
商户在插件后台「场馆配置 → 添加」,看到以下表单:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
【场馆基本信息】
|
|
|
|
|
|
场馆名称:[________________________]
|
|
|
|
|
|
场馆地址:[________________________]
|
|
|
|
|
|
场馆图片:[上传按钮]
|
|
|
|
|
|
|
|
|
|
|
|
【分区配置】(可以加/减分区)
|
|
|
|
|
|
┌──────────────────────────────────────────────┐
|
|
|
|
|
|
│ 分区 A │ 标签:VIP区 │ 单价:899元 │ 颜色:[红] │
|
|
|
|
|
|
├──────────────────────────────────────────────┤
|
|
|
|
|
|
│ 分区 B │ 标签:看台区 │ 单价:599元 │ 颜色:[蓝] │
|
|
|
|
|
|
├──────────────────────────────────────────────┤
|
|
|
|
|
|
│ 分区 C │ 标签:普通区 │ 单价:299元 │ 颜色:[绿] │
|
|
|
|
|
|
└──────────────────────────────────────────────┘
|
|
|
|
|
|
[+ 添加分区] [- 删除分区]
|
|
|
|
|
|
|
|
|
|
|
|
【座位排布预览】
|
|
|
|
|
|
A A A A A A
|
|
|
|
|
|
B B B B B B
|
|
|
|
|
|
C C C C C C
|
|
|
|
|
|
|
|
|
|
|
|
每排座位数:[6___] 排数自动生成
|
|
|
|
|
|
|
|
|
|
|
|
【保存】 【取消】
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**技术实现**:
|
|
|
|
|
|
- layui 表单 + Vue3 CDN(轻量,不破坏 ShopXO 后台已有的 jQuery/layui 结构)
|
|
|
|
|
|
- 约 500 行前端代码,1-1.5 人天
|
|
|
|
|
|
- 保存时:表单数据 → 编码成上面的 JSON → 写入 `vr_seat_templates.seat_map`
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 与 ShopXO Spec 的关系
|
|
|
|
|
|
|
|
|
|
|
|
**商户不需要知道 Spec 是什么。** 他们只知道"我在场馆配置里建了一个场馆,里面有 A/B/C 三个区"。
|
|
|
|
|
|
|
|
|
|
|
|
Spec 是插件内部的事情。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 四、商品发布页注入(区域 B)
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 注入点
|
|
|
|
|
|
|
|
|
|
|
|
利用 ShopXO 钩子 `plugins_view_admin_goods_save`,在商品发布页的「规格型号」tab 里注入我们的票务配置面板。
|
|
|
|
|
|
|
|
|
|
|
|
**注入位置**:商品发布页 → 「规格型号」Tab → `<div class="am-form-group">` 容器内
|
|
|
|
|
|
|
|
|
|
|
|
商户视角:
|
|
|
|
|
|
```
|
|
|
|
|
|
规格型号
|
|
|
|
|
|
○ 使用商品的规格 ← ShopXO 原生(普通商品选这个)
|
|
|
|
|
|
● 使用票务配置 ← 新增(票务商品选这个)
|
|
|
|
|
|
|
|
|
|
|
|
▼ 票务配置(仅当"使用票务配置"选中时展开)
|
|
|
|
|
|
场馆:[请选择场馆 ▼] ← 来自 vr_seat_templates 表
|
|
|
|
|
|
分区:[□VIP区 □看台区 □普通区] ← 根据所选场馆联动
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 注入原理
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
ShopXO admin Goods::SaveInfo()
|
|
|
|
|
|
→ 调用 hook plugins_view_admin_goods_save
|
|
|
|
|
|
→ 触发 vr_ticket/hook/AdminGoodsSave.php
|
|
|
|
|
|
→ 返回票务配置面板 HTML
|
|
|
|
|
|
→ 插入 saveinfo.html 的 base tab 内
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
关键代码路径(已在 ShopXO 源码中确认):
|
|
|
|
|
|
```php
|
|
|
|
|
|
// Goods.php:159-167
|
|
|
|
|
|
$hook_name = 'plugins_view_admin_goods_save';
|
|
|
|
|
|
$assign[$hook_name.'_data'] = MyEventTrigger($hook_name, [...]);
|
|
|
|
|
|
MyViewAssign($assign);
|
|
|
|
|
|
return MyView(); // 模板里用 {{$plugins_view_admin_goods_save_data}} 输出
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 钩子注册
|
|
|
|
|
|
|
|
|
|
|
|
在 `plugin.json` 中新增:
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"backend_hook": {
|
|
|
|
|
|
"plugins_view_admin_goods_save": [
|
|
|
|
|
|
"\\app\\plugins\\vr_ticket\\hook\\AdminGoodsSave"
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 场馆下拉数据来源
|
|
|
|
|
|
|
|
|
|
|
|
`AdminGoodsSave.php` 查询 `vr_seat_templates` 表,返回场馆列表(只返回顶层信息,不需要查所有座位):
|
|
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
|
$templates = Db::name('vr_seat_templates')
|
|
|
|
|
|
->field('id, name, seat_map')
|
|
|
|
|
|
->where(['status' => 1])
|
|
|
|
|
|
->select();
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($templates as &$t) {
|
|
|
|
|
|
$seatMap = json_decode($t['seat_map'], true);
|
|
|
|
|
|
$t['venue_name'] = $seatMap['venue']['name'] ?? $t['name'];
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 五、商品发布完整流程
|
|
|
|
|
|
|
|
|
|
|
|
### 场景:商户发布一张票务商品
|
|
|
|
|
|
|
|
|
|
|
|
**Step 1**:商户进入 ShopXO 后台 → 商品管理 → 添加商品
|
|
|
|
|
|
|
|
|
|
|
|
**Step 2**:填写基础信息
|
|
|
|
|
|
```
|
|
|
|
|
|
商品名称:周杰伦 VR 虚拟演唱会
|
|
|
|
|
|
商品分类:VR演出(绑定到票务插件的分类)
|
|
|
|
|
|
商品类型:[票务 ▼](已由插件注入的字段)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Step 3**:在「规格型号」Tab 选票务配置
|
|
|
|
|
|
```
|
|
|
|
|
|
规格型号
|
|
|
|
|
|
● 使用票务配置
|
|
|
|
|
|
|
|
|
|
|
|
场馆:[国家体育馆 ▼] ← AdminGoodsSave 注入的下拉
|
|
|
|
|
|
分区:[✓VIP区 ✓看台区] ← 多选,根据场馆联动
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Step 4**:点击发布
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Save() 被调用
|
|
|
|
|
|
↓
|
|
|
|
|
|
GoodsService::GoodsSave() 执行标准商品保存逻辑
|
|
|
|
|
|
↓
|
|
|
|
|
|
触发钩子 plugins_service_goods_save_handle
|
|
|
|
|
|
↓
|
|
|
|
|
|
AdminGoodsSaveHandle() 收到 POST 数据
|
|
|
|
|
|
├── 提取 venue_id 和选中的 zone chars
|
|
|
|
|
|
├── 调用 SeatSkuService::BatchGenerate(goods_id, venue_id, zones)
|
|
|
|
|
|
│ └── 为每个 zone 的每个座位生成一行 goods_spec_base(inventory=1)
|
|
|
|
|
|
│ └── 同时写入 goods_spec_value($vr-场馆 / $vr-分区 / $vr-时段 / $vr-座位号)
|
|
|
|
|
|
├── 更新 vr_seat_templates.spec_base_id_map
|
|
|
|
|
|
└── 返回(让标准保存流程继续)
|
|
|
|
|
|
↓
|
|
|
|
|
|
商品保存完成
|
|
|
|
|
|
↓
|
|
|
|
|
|
订单后续流程不变(已有实现)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**商户的感知**:我在下拉里选了个场馆和分区,点发布,商品就上线了。Spec 生成是静默的、无感的。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 六、Spec 生成后的内部结构(技术细节)
|
|
|
|
|
|
|
|
|
|
|
|
以"国家体育馆 + VIP区(A) + 看台区(B)"为例:
|
|
|
|
|
|
|
|
|
|
|
|
### goods_spec_base(每行 = 一个座位 SKU)
|
|
|
|
|
|
|
|
|
|
|
|
| spec_base_id | goods_id | inventory | price |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 2001 | 123 | 1 | 899 | ← A_1 座位
|
|
|
|
|
|
| 2002 | 123 | 1 | 899 | ← A_2 座位
|
|
|
|
|
|
| ... | 123 | 1 | 899 | ← A_6 座位
|
|
|
|
|
|
| 3001 | 123 | 1 | 599 | ← B_1 座位
|
|
|
|
|
|
| ... | 123 | 1 | 599 | ← B_6 座位
|
|
|
|
|
|
|
|
|
|
|
|
### goods_spec_type(每行 = 一个规格维度)
|
|
|
|
|
|
|
|
|
|
|
|
| id | goods_id | name | value |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | 123 | $vr-场馆 | `[{"name":"国家体育馆"}]` |
|
|
|
|
|
|
| 2 | 123 | $vr-分区 | `[{"name":"VIP区"},{"name":"看台区"}]` |
|
|
|
|
|
|
| 3 | 123 | $vr-座位号 | `[{"name":"A_1"},{"name":"A_2"},...,{"name":"B_6"}]` |
|
|
|
|
|
|
|
|
|
|
|
|
### spec_base_id_map(内存/缓存)
|
|
|
|
|
|
|
|
|
|
|
|
商户在前台选座时,前端根据 seatKey(如 `"A_1"`)查表得到对应的 spec_base_id:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"A_1": 2001, "A_2": 2002, ..., "A_6": 2006,
|
|
|
|
|
|
"B_1": 3001, "B_2": 3002, ..., "B_6": 3012
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 七、商品编辑时的处理(优先级低)
|
|
|
|
|
|
|
|
|
|
|
|
**问题**:商户编辑已发布票务商品时,ShopXO 后台会显示琳琅满目的 Spec 列表(几千个座位 SKU),吓死人。
|
|
|
|
|
|
|
|
|
|
|
|
**解决方向**(暂不实现,先让创建流程跑通):
|
|
|
|
|
|
1. 编辑页加载时,解析 `spec_base_id_map`,还原出 venue + zone 信息
|
|
|
|
|
|
2. 在票务配置面板里回显"当前绑定的场馆 + 分区"
|
|
|
|
|
|
3. 若商户修改了 venue/zone:重新生成 Spec(或提示"需先解绑")
|
|
|
|
|
|
4. 如果不修改:Spec 列表保持不动
|
|
|
|
|
|
|
|
|
|
|
|
**目前策略**:先让创建流程跑通,编辑流程后续迭代。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 八、与 ShopXO 原生 Spec 管理的关系
|
|
|
|
|
|
|
|
|
|
|
|
| 维度 | ShopXO 原生 Spec | 我们的票务 Spec |
|
|
|
|
|
|
|------|----------------|----------------|
|
|
|
|
|
|
| 谁创建 | 商户在商品编辑页手动添加 | 插件在发布时自动生成 |
|
|
|
|
|
|
| 谁看到 | 商户在后台规格管理看到 | 商户无感(我们注入的表单已覆盖场景) |
|
|
|
|
|
|
| 用户在前台看到 | 购物车/下单流程 | 票务选座 UI(ticket_detail.html)|
|
|
|
|
|
|
| 核销 | 不涉及 | 每座位一个 QR(vr_tickets 表)|
|
|
|
|
|
|
|
|
|
|
|
|
**商户永远不需要进入 ShopXO 原生的"规格管理"界面来管理票务座位。** 票务 Spec 的完整生命周期由插件控制。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 九、实施步骤
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 3-1:后台场馆配置管理(新增 admin 页面)
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] 新建 `admin/controller/Venue.php`
|
|
|
|
|
|
- [ ] 新建 `admin/view/venue/list.html`(场馆列表)
|
|
|
|
|
|
- [ ] 新建 `admin/view/venue/save.html`(场馆表单编辑器:venue + zone + 座位排布)
|
|
|
|
|
|
- [ ] 升级 `vr_seat_templates.seat_map` JSON 结构(加入 venue 顶层)
|
|
|
|
|
|
- [ ] 将现有测试数据的 seat_map 迁移为带 venue 的格式
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 3-2:商品发布页注入
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] 在 `plugin.json` 注册 `plugins_view_admin_goods_save` 钩子
|
|
|
|
|
|
- [ ] 新建 `hook/AdminGoodsSave.php`(注入票务配置面板 HTML)
|
|
|
|
|
|
- [ ] 场馆下拉联动分区多选(Vue3,轻量)
|
|
|
|
|
|
- [ ] 注册 `plugins_service_goods_save_handle` 钩子处理保存数据
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 3-3:Spec 自动生成接入
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] `SeatSkuService::BatchGenerate()` 接入商品发布流程(传入 goods_id)
|
|
|
|
|
|
- [ ] `spec_base_id_map` 写入 `vr_seat_templates` 表
|
|
|
|
|
|
- [ ] `extension_data` 写入 order_goods(选座信息追溯)
|
|
|
|
|
|
|
|
|
|
|
|
### Phase 3-4(优先级低):商品编辑回显
|
|
|
|
|
|
|
|
|
|
|
|
- [ ] 编辑页加载时解析 spec_base_id_map,还原 venue + zone
|
|
|
|
|
|
- [ ] 编辑页票务配置面板回显
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 十、总结
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
商户操作:插件后台建场馆配置
|
|
|
|
|
|
↓
|
|
|
|
|
|
发布商品时:选场馆 + 分区
|
|
|
|
|
|
↓
|
|
|
|
|
|
插件静默:生成海量 Spec(每座位1个 SKU)写入商品
|
|
|
|
|
|
↓
|
|
|
|
|
|
用户前台:看到票务选座 UI(无感知 Spec)
|
|
|
|
|
|
↓
|
|
|
|
|
|
购买选座:每座位 → 1 个 order_goods → 1 张 QR
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
商户不需要知道 Spec,不需要碰规格管理,不需要理解 SKU。插件把这些全部封装成"选场馆、选分区"两个动作。
|
2026-04-15 13:22:22 +00:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 附录:完整状态流转图(Mermaid)
|
|
|
|
|
|
|
|
|
|
|
|
> 以下为系统完整状态流转,覆盖场馆配置 → 商品发布 → 用户下单全链路。
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
stateDiagram-v2
|
|
|
|
|
|
[*] --> 新建场馆: 管理员创建
|
|
|
|
|
|
新建场馆 --> 新建分区: 绑定场馆
|
|
|
|
|
|
新建分区 --> 绘制座位布局: 绑定分区
|
|
|
|
|
|
绘制座位布局 --> 生成SeatMapJSON: 保存
|
|
|
|
|
|
生成SeatMapJSON --> 发布票务商品: 选择模板
|
|
|
|
|
|
发布票务商品 --> 选择场次: 注入表单
|
|
|
|
|
|
选择场次 --> 批量生成Spec: 保存商品
|
|
|
|
|
|
批量生成Spec --> 商品上架: Spec写入完成
|
|
|
|
|
|
商品上架 --> 用户浏览: C端展示
|
|
|
|
|
|
用户浏览 --> 用户选座下单: 选择座位
|
|
|
|
|
|
用户选座下单 --> 订单生成: goodsParams提交
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant 商户
|
|
|
|
|
|
participant 商品发布页 as ShopXO商品发布页<br/>(注入后的表单)
|
|
|
|
|
|
participant AdminGoodsSaveHook as AdminGoodsSave<br/>(钩子返回HTML)
|
|
|
|
|
|
participant SaveHook as plugins_service_<br/>goods_save_handle
|
|
|
|
|
|
participant BatchGen as SeatSkuService<br/>::BatchGenerate()
|
|
|
|
|
|
participant DB as goods_spec_base<br/>goods_spec_value
|
|
|
|
|
|
|
|
|
|
|
|
商户->>商品发布页: 选择场馆 + 分区
|
|
|
|
|
|
商户->>商品发布页: 点"发布商品"
|
|
|
|
|
|
|
|
|
|
|
|
商品发布页->>SaveHook: POST {venue_id, zones[]}
|
|
|
|
|
|
SaveHook->>BatchGen: BatchGenerate(goods_id, template_id, zones[])
|
|
|
|
|
|
|
|
|
|
|
|
loop 每500条一批
|
|
|
|
|
|
BatchGen->>DB: INSERT spec_base<br/>$vr-场馆 = "鸟巢"<br/>$vr-分区 = "VIP区A"<br/>$vr-时段 = "2026-06-01 19:00"<br/>$vr-座位号 = "A_1" ~ "A_500"
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
BatchGen-->>SaveHook: 返回座位级spec_base_id_map
|
|
|
|
|
|
SaveHook-->>商品发布页: 保存成功
|
|
|
|
|
|
商品发布页-->>商户: 商品上架
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
subgraph UserFlow["用户端"]
|
|
|
|
|
|
U1["用户浏览商品详情页<br/>ticket_detail.html"]
|
|
|
|
|
|
U2["看到:场次选择器<br/>座位可视化图(彩色热力图)"]
|
|
|
|
|
|
U3["点击座位 A_1"]
|
|
|
|
|
|
U4["goodsParams 自动组装<br/>goodsParamsList[0]:<br/>spec_ids: [场馆spec, 分区spec,<br/>时段spec, 座位spec]<br/>stock: 1<br/>spec_base_id: 座位spec的ID"]
|
|
|
|
|
|
U5["提交订单"]
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
subgraph Invisible["用户不可见的底层"]
|
|
|
|
|
|
DB1["spec_base 表<br/>包含全部spec记录<br/>但用户端只展示座位图"]
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
U1 --> U2 --> U3 --> U4 --> U5
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 编辑商品时的 Spec 解析问题
|
|
|
|
|
|
|
|
|
|
|
|
> ⚠️ 如果不解析直接展示,spec_base 里几千条记录会把编辑页撑爆。
|
|
|
|
|
|
|
|
|
|
|
|
**解决方案**:商品表增加一个 `vr_ticket_config` 字段(JSON),只存「选择了什么」,编辑时从此 JSON 还原选择状态,而非从 spec_base 反推。
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TB
|
|
|
|
|
|
A["编辑已有票务商品"]
|
|
|
|
|
|
A --> B{是否有 vr_ticket_config?}
|
|
|
|
|
|
B -->|无(存量数据)| C["显示空表单<br/>提示「请重新选择」"]
|
|
|
|
|
|
B -->|有| D["解析 vr_ticket_config JSON"]
|
|
|
|
|
|
D --> E["还原:场馆 → 分区 → 座位选择状态"]
|
|
|
|
|
|
E --> F["管理员可修改选择<br/>重新生成 spec_base 映射"]
|
|
|
|
|
|
F --> G["保存时:先删除旧spec<br/>再批量插入新spec"]
|
|
|
|
|
|
```
|