diff --git a/docs/11_EDITOR_AND_INJECTION_DESIGN.md b/docs/11_EDITOR_AND_INJECTION_DESIGN.md index 365a68c..2597116 100644 --- a/docs/11_EDITOR_AND_INJECTION_DESIGN.md +++ b/docs/11_EDITOR_AND_INJECTION_DESIGN.md @@ -1,6 +1,8 @@ # 后台编辑器 + 商品发布注入方案设计 -> 版本:v1.0 | 日期:2026-04-15 | 状态:**待大头确认后执行** +> 版本:v2.0 | 日期:2026-04-15 | 状态:**待大头确认后执行** +> +> v2.0 更新:经 PM Auditor 代码级核查,修正 10 处与实际代码不符的描述(见文末勘误表) --- @@ -20,8 +22,7 @@ │ │ 商品管理 │ │ 插件专属后台(新增) │ │ │ │ 发布/编辑商品 │ ←→ │ 场馆配置管理 │ │ │ │ (注入点) │ │ 座位分区模板编辑器 │ │ -│ └─────────────────┘ │ 场次配置 │ │ -│ └──────────────────────┘ │ +│ └─────────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -31,32 +32,27 @@ ``` VR票务 - ├── 场馆配置 ← 新增 + ├── 场馆配置 ← Phase 3-1 新增(当前不存在) ├── 座位模板 ← 已存在(Phase 2) ├── 电子票管理 ← 已存在 ├── 核销员管理 ← 已存在 └── 核销记录 ← 已存在 ``` -**场馆配置**管理的内容: -- 场馆名称、地址、图片 -- 该场馆下的分区(Zone)列表:VIP区/看台区/普通区 -- 每个分区的基础价格、颜色配置 -- 座位排布(每排几个座位,用字母+数字标记如 A_1, A_2) - -**数据落地**:`vr_seat_templates` 表的 `seat_map` JSON 字段(venue 信息作为 JSON 顶层嵌入)。 +> ⚠️ **当前状态**:`Venue.php` 控制器不存在。`vr_seat_templates` 表只有 `name` / `category_id` / `seat_map` / `spec_base_id_map` 字段,`venue` 信息目前不存在于数据库中。 ### 区域 B:ShopXO 商品发布页(注入点) +> ⚠️ **待核实**:商品发布页 Tab 名称需对照 `saveinfo.html` 模板确认。当前描述"规格型号"Tab 可能不准确。 + 商户在 ShopXO 后台「商品管理 → 添加商品」: ``` 添加商品 [商品名称] [商品分类] - ▼ 规格型号 ← ShopXO 原生区域,我们注入票务选择器 - 票务配置 ← 新增:插件注入的区域 - [请选择场馆 ▼] ← 场馆下拉 + ▼ 票务配置 ← ShopXO 原生区域,我们注入票务选择器 + [请选择场馆 ▼] ← 场馆下拉(来自 vr_seat_templates 表) [请选择分区 ▼] ← 分区多选(根据场馆联动) [商品详情 富文本编辑器] @@ -71,36 +67,47 @@ VR票务 ## 三、场馆配置管理(区域 A) -### 3.1 数据结构 +### 3.1 数据结构(当前 vs 目标) -场馆 + 分区 + 座位,全部编码进 `vr_seat_templates.seat_map` 一个 JSON: +> ⚠️ **当前 seat_map 实际结构**(从 `SeatSkuService::BatchGenerate` 代码反推): +> - **无** `venue` 顶层字段 +> - **无** `zones` 顶层字段 +> - 顶层字段为:`map`、`seats`、`sections`、`row_labels` +> - `$vr-场馆` 的值目前**硬编码**为"国家体育馆" + +**目标结构(Phase 3-1 需调整)**: ```json { - "venue": { + "venue": { // ← Phase 3-1 新增:从 venue 表读取或直接存这里 "name": "国家体育馆", "address": "北京市朝阳区", "image": "/uploads/vr/venue/1.jpg" }, "map": ["AAAAAA", "BBBBBB", "CCCCCC"], - "zones": { + "seats": { // ← 实际字段名,不是 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": [ + "sections": [ // ← 实际字段名,存储分区元信息 { "char": "A", "name": "VIP区", "color": "#e74c3c" }, { "char": "B", "name": "看台区", "color": "#3498db" }, { "char": "C", "name": "普通区", "color": "#2ecc71" } - ] + ], + "row_labels": ["A", "B", "C"] } ``` -**关键理解**:venue 信息和 seat_map 存在同一个 JSON 里,不拆表。`vr_seat_templates` 表的每一行 = 一个场馆配置(包含分区和座位布局)。 +**关键理解**: +- `seats` = 每行(row char)的默认价格/颜色/标签(整行统一) +- `sections` = 每个分区的元信息(char → name/color 映射) +- `venue` 信息目前**不存在**于 seat_map,Phase 3-1 需要决策:是加到 seat_map 里,还是新建 `vr_venues` 表 ### 3.2 场馆配置管理页面(表单可视化编辑器) +> ⚠️ **待实现**:当前 `Venue.php` 控制器不存在,Phase 3-1 需要新建。 + 商户在插件后台「场馆配置 → 添加」,看到以下表单: ``` @@ -110,13 +117,13 @@ VR票务 场馆图片:[上传按钮] 【分区配置】(可以加/减分区) - ┌──────────────────────────────────────────────┐ - │ 分区 A │ 标签:VIP区 │ 单价:899元 │ 颜色:[红] │ - ├──────────────────────────────────────────────┤ - │ 分区 B │ 标签:看台区 │ 单价:599元 │ 颜色:[蓝] │ - ├──────────────────────────────────────────────┤ - │ 分区 C │ 标签:普通区 │ 单价:299元 │ 颜色:[绿] │ - └──────────────────────────────────────────────┘ + ┌──────────────────────────────────────────────────┐ + │ Char │ 标签:VIP区 │ 单价:899元 │ 颜色:[红] │ + ├──────────────────────────────────────────────────┤ + │ Char │ 标签:看台区 │ 单价:599元 │ 颜色:[蓝] │ + ├──────────────────────────────────────────────────┤ + │ Char │ 标签:普通区 │ 单价:299元 │ 颜色:[绿] │ + └──────────────────────────────────────────────────┘ [+ 添加分区] [- 删除分区] 【座位排布预览】 @@ -132,83 +139,77 @@ VR票务 **技术实现**: - layui 表单 + Vue3 CDN(轻量,不破坏 ShopXO 后台已有的 jQuery/layui 结构) - 约 500 行前端代码,1-1.5 人天 -- 保存时:表单数据 → 编码成上面的 JSON → 写入 `vr_seat_templates.seat_map` +- 保存时:表单数据 → 编码成 JSON → 写入 `vr_seat_templates.seat_map` -### 3.3 与 ShopXO Spec 的关系 +### 3.3 venue 信息的设计决策(待讨论) -**商户不需要知道 Spec 是什么。** 他们只知道"我在场馆配置里建了一个场馆,里面有 A/B/C 三个区"。 +**选项 A**:venue 信息存入 `seat_map.venue`(JSON 顶层) +- 优点:简单,不改表结构 +- 缺点:一个模板只能关联一个 venue -Spec 是插件内部的事情。 +**选项 B**:新建 `vr_venues` 表,`vr_seat_templates.venue_id` 外键关联 +- 优点:一个 venue 可对应多个模板(如鸟巢主场地 + 鸟巢室外场) +- 缺点:多一张表,多一套 CRUD + +> 当前 `vr_seat_templates` 表只有 `name` 字段同时承载"模板名"和"场馆名",暂未分离。 --- ## 四、商品发布页注入(区域 B) -### 4.1 注入点 - -利用 ShopXO 钩子 `plugins_view_admin_goods_save`,在商品发布页的「规格型号」tab 里注入我们的票务配置面板。 - -**注入位置**:商品发布页 → 「规格型号」Tab → `
` 容器内 - -商户视角: -``` -规格型号 - ○ 使用商品的规格 ← ShopXO 原生(普通商品选这个) - ● 使用票务配置 ← 新增(票务商品选这个) - - ▼ 票务配置(仅当"使用票务配置"选中时展开) - 场馆:[请选择场馆 ▼] ← 来自 vr_seat_templates 表 - 分区:[□VIP区 □看台区 □普通区] ← 根据所选场馆联动 -``` - -### 4.2 注入原理 +### 4.1 注入原理 ``` ShopXO admin Goods::SaveInfo() - → 调用 hook plugins_view_admin_goods_save - → 触发 vr_ticket/hook/AdminGoodsSave.php + → 调用 hook plugins_view_admin_goods_save(Goods.php:159) + → 触发 vr_ticket/hook/AdminGoodsSave.php(Phase 3-2 新建) → 返回票务配置面板 HTML - → 插入 saveinfo.html 的 base tab 内 + → 插入 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.2 钩子注册方式 -### 4.3 钩子注册 +> ⚠️ **重要修正**:ShopXO 插件系统**不支持** `backend_hook` 字段。 +> `plugin.json` 实际使用 `hooks` 数组。 -在 `plugin.json` 中新增: +**plugin.json 正确格式**: ```json { - "backend_hook": { - "plugins_view_admin_goods_save": [ - "\\app\\plugins\\vr_ticket\\hook\\AdminGoodsSave" - ] - } + "hooks": [ + "plugins_service_order_pay_success_handle_end", + "plugins_service_order_delete_success", + "plugins_view_admin_goods_save", + "plugins_service_goods_save_handle" + ] } ``` +> 当前 `plugin.json` 只有前两个钩子,后两个是 **Phase 3-2 需要追加的**。 + +### 4.3 AdminGoodsSave.php(待新建) + +> ⚠️ **当前不存在**:`vr_ticket/` 目录下无 `AdminGoodsSave.php` 文件。 + +Phase 3-2 需要新建: +- 文件路径:`plugins/vr_ticket/hook/AdminGoodsSave.php` +- `plugins_view_admin_goods_save` 钩子返回 HTML 注入票务表单 +- `plugins_service_goods_save_handle` 钩子处理票务数据保存 + ### 4.4 场馆下拉数据来源 -`AdminGoodsSave.php` 查询 `vr_seat_templates` 表,返回场馆列表(只返回顶层信息,不需要查所有座位): +`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']; -} ``` +当前 `seat_map` 中无 `venue.name`,所以下拉显示的是 `vr_seat_templates.name` 字段(如 "Bird Nest - Zone A")。 + +> ⚠️ Phase 3-1 需要决策:venue 独立后,下拉应显示 venue.name 而非模板 name。 + --- ## 五、商品发布完整流程 @@ -220,50 +221,51 @@ foreach ($templates as &$t) { **Step 2**:填写基础信息 ``` 商品名称:周杰伦 VR 虚拟演唱会 -商品分类:VR演出(绑定到票务插件的分类) -商品类型:[票务 ▼](已由插件注入的字段) +商品分类:VR演出 ``` -**Step 3**:在「规格型号」Tab 选票务配置 +**Step 3**:在注入的票务配置面板选场馆和分区 ``` -规格型号 - ● 使用票务配置 - - 场馆:[国家体育馆 ▼] ← AdminGoodsSave 注入的下拉 - 分区:[✓VIP区 ✓看台区] ← 多选,根据场馆联动 +票务配置 + 场馆:[Bird Nest - Zone A ▼] ← AdminGoodsSave 注入的下拉 + 分区:[✓VIP区 ✓看台区] ← 多选,根据场馆联动 ``` **Step 4**:点击发布 ``` -Save() 被调用 - ↓ +商户点击"发布商品" + ↓ +Goods::Save() 调用 GoodsService::GoodsSave()(Goods.php:187) + ↓ GoodsService::GoodsSave() 执行标准商品保存逻辑 - ↓ -触发钩子 plugins_service_goods_save_handle - ↓ + ↓ +触发钩子 plugins_service_goods_save_handle(GoodsService.php:1550) + ↓ 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 - └── 返回(让标准保存流程继续) - ↓ + ├── 提取 template_id 和选中的分区 chars + ├── 调用 SeatSkuService::BatchGenerate($goodsId, $templateId) + │ └── 注意:BatchGenerate() 签名是 (int $goodsId, int $seatTemplateId) + │ └── 当前版本:按 template 全量生成(不支持按 zones 过滤) + │ └── 为每个座位生成一行 sxo_goods_spec_base(inventory=1) + │ └── 同时写入 sxo_goods_spec_value($vr-场馆/$vr-分区/$vr-时段/$vr-座位号) + ├── 更新 vr_seat_templates.spec_base_id_map(持久化,非内存) + └── 返回(让标准保存流程继续) + ↓ 商品保存完成 - ↓ + ↓ 订单后续流程不变(已有实现) ``` -**商户的感知**:我在下拉里选了个场馆和分区,点发布,商品就上线了。Spec 生成是静默的、无感的。 - --- ## 六、Spec 生成后的内部结构(技术细节) +> ⚠️ **表前缀**:ShopXO 原生表使用 `sxo_` 前缀,插件自定义表使用 `{$prefix}vrt_`。 + 以"国家体育馆 + VIP区(A) + 看台区(B)"为例: -### goods_spec_base(每行 = 一个座位 SKU) +### sxo_goods_spec_base(每行 = 一个座位 SKU) | spec_base_id | goods_id | inventory | price | |---|---|---|---| @@ -273,15 +275,18 @@ AdminGoodsSaveHandle() 收到 POST 数据 | 3001 | 123 | 1 | 599 | ← B_1 座位 | ... | 123 | 1 | 599 | ← B_6 座位 -### goods_spec_type(每行 = 一个规格维度) +### sxo_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"}]` | +| 3 | 123 | $vr-时段 | `[{"name":"待选场次"}]` | +| 4 | 123 | $vr-座位号 | `[{"name":"A_1"},{"name":"A_2"},...,{"name":"B_6"}]` | -### spec_base_id_map(内存/缓存) +### vr_seat_templates.spec_base_id_map(持久化字段) + +> ⚠️ **修正**:`spec_base_id_map` 不是内存/缓存,是持久化到 `vr_seat_templates` 表的字段。 商户在前台选座时,前端根据 seatKey(如 `"A_1"`)查表得到对应的 spec_base_id: @@ -294,154 +299,109 @@ AdminGoodsSaveHandle() 收到 POST 数据 --- -## 七、商品编辑时的处理(优先级低) +## 七、BatchGenerate 实际接口(已实现代码) -**问题**:商户编辑已发布票务商品时,ShopXO 后台会显示琳琅满目的 Spec 列表(几千个座位 SKU),吓死人。 +> ⚠️ **关键修正**:文档之前描述的参数签名与实际代码不符。 -**解决方向**(暂不实现,先让创建流程跑通): -1. 编辑页加载时,解析 `spec_base_id_map`,还原出 venue + zone 信息 -2. 在票务配置面板里回显"当前绑定的场馆 + 分区" -3. 若商户修改了 venue/zone:重新生成 Spec(或提示"需先解绑") -4. 如果不修改:Spec 列表保持不动 +**实际签名**(`SeatSkuService.php:34`): +```php +public static function BatchGenerate(int $goodsId, int $seatTemplateId): array +``` -**目前策略**:先让创建流程跑通,编辑流程后续迭代。 +**返回格式**: +```php +[ + 'code' => 0, + 'data' => [ + 'total' => 18, + 'generated' => 12, + 'spec_base_id_map' => ['A_1' => 2001, 'A_2' => 2002, ...] + ] +] +``` + +**与文档描述的差异**: +- 文档说:`(goods_id, venue_id, zones[])` — ❌ 错误 +- 实际是:`(int $goodsId, int $seatTemplateId)` — 全量按模板生成,不支持按 zones 过滤 + +> ⚠️ 如果 Phase 3-2 需要支持"按分区选择生成",需要修改 `BatchGenerate()` 增加 zones 过滤逻辑。 --- -## 八、与 ShopXO 原生 Spec 管理的关系 +## 八、商品编辑时的处理(优先级低) + +**问题**:商户编辑已发布票务商品时,ShopXO 后台会显示琳琅满目的 Spec 列表(几千个座位 SKU)。 + +**解决方案**(暂不实现,先让创建流程跑通): + +**方案 A(推荐)**:商品表新增 `vr_ticket_config` JSON 字段 +- 保存时:只存 `{ template_id, zones[], created_at }` +- 编辑时:从此字段还原venue + zone 选择状态,不从 spec_base 反推 +- **需要迁移**:新增 `sxo_goods.vr_ticket_config` 字段 + +**方案 B**:直接用 `sxo_goods.extension_data`(如果 ShopXO 支持) + +> ⚠️ 当前商品表**无** `vr_ticket_config` 字段。方案 A 需要新建数据库迁移。 + +--- + +## 九、与 ShopXO 原生 Spec 管理的关系 | 维度 | ShopXO 原生 Spec | 我们的票务 Spec | |------|----------------|----------------| | 谁创建 | 商户在商品编辑页手动添加 | 插件在发布时自动生成 | -| 谁看到 | 商户在后台规格管理看到 | 商户无感(我们注入的表单已覆盖场景) | +| 表前缀 | `sxo_` | `sxo_goods_spec_base` / `sxo_goods_spec_value` | +| 商户感知 | 在后台规格管理看到 | 无感(我们注入的表单已覆盖场景) | | 用户在前台看到 | 购物车/下单流程 | 票务选座 UI(ticket_detail.html)| | 核销 | 不涉及 | 每座位一个 QR(vr_tickets 表)| -**商户永远不需要进入 ShopXO 原生的"规格管理"界面来管理票务座位。** 票务 Spec 的完整生命周期由插件控制。 +**商户永远不需要进入 ShopXO 原生的"规格管理"界面来管理票务座位。** --- -## 九、实施步骤 +## 十、实施步骤 -### Phase 3-1:后台场馆配置管理(新增 admin 页面) +### Phase 3-1:后台场馆配置管理(新建 admin 页面) +- [ ] 决策:venue 信息存在 seat_map 内还是独立表? - [ ] 新建 `admin/controller/Venue.php` - [ ] 新建 `admin/view/venue/list.html`(场馆列表) -- [ ] 新建 `admin/view/venue/save.html`(场馆表单编辑器:venue + zone + 座位排布) +- [ ] 新建 `admin/view/venue/save.html`(表单编辑器:venue + zone + 座位排布) - [ ] 升级 `vr_seat_templates.seat_map` JSON 结构(加入 venue 顶层) -- [ ] 将现有测试数据的 seat_map 迁移为带 venue 的格式 +- [ ] 将现有测试数据迁移为带 venue 的格式 ### Phase 3-2:商品发布页注入 -- [ ] 在 `plugin.json` 注册 `plugins_view_admin_goods_save` 钩子 +- [ ] 在 `plugin.json` 的 `hooks` 数组中追加钩子(**不用** `backend_hook`) - [ ] 新建 `hook/AdminGoodsSave.php`(注入票务配置面板 HTML) +- [ ] 新建 `hook/AdminGoodsSaveHandle.php`(处理保存数据) - [ ] 场馆下拉联动分区多选(Vue3,轻量) -- [ ] 注册 `plugins_service_goods_save_handle` 钩子处理保存数据 +- [ ] 确认商品发布页实际 Tab 名称(待对照 `saveinfo.html`) ### Phase 3-3:Spec 自动生成接入 -- [ ] `SeatSkuService::BatchGenerate()` 接入商品发布流程(传入 goods_id) -- [ ] `spec_base_id_map` 写入 `vr_seat_templates` 表 +- [ ] `BatchGenerate()` 增加按 zones 过滤参数(如需要) - [ ] `extension_data` 写入 order_goods(选座信息追溯) ### Phase 3-4(优先级低):商品编辑回显 -- [ ] 编辑页加载时解析 spec_base_id_map,还原 venue + zone +- [ ] 新建 `sxo_goods.vr_ticket_config` 字段迁移 +- [ ] 编辑页加载时解析 `vr_ticket_config`,还原 venue + zone - [ ] 编辑页票务配置面板回显 --- -## 十、总结 +## 十一、勘误表(v1.0 → v2.0) -``` -商户操作:插件后台建场馆配置 - ↓ -发布商品时:选场馆 + 分区 - ↓ -插件静默:生成海量 Spec(每座位1个 SKU)写入商品 - ↓ -用户前台:看到票务选座 UI(无感知 Spec) - ↓ -购买选座:每座位 → 1 个 order_goods → 1 张 QR -``` - -商户不需要知道 Spec,不需要碰规格管理,不需要理解 SKU。插件把这些全部封装成"选场馆、选分区"两个动作。 - ---- - -## 附录:完整状态流转图(Mermaid) - -> 以下为系统完整状态流转,覆盖场馆配置 → 商品发布 → 用户下单全链路。 - -```mermaid -stateDiagram-v2 - [*] --> 新建场馆: 管理员创建 - 新建场馆 --> 新建分区: 绑定场馆 - 新建分区 --> 绘制座位布局: 绑定分区 - 绘制座位布局 --> 生成SeatMapJSON: 保存 - 生成SeatMapJSON --> 发布票务商品: 选择模板 - 发布票务商品 --> 选择场次: 注入表单 - 选择场次 --> 批量生成Spec: 保存商品 - 批量生成Spec --> 商品上架: Spec写入完成 - 商品上架 --> 用户浏览: C端展示 - 用户浏览 --> 用户选座下单: 选择座位 - 用户选座下单 --> 订单生成: goodsParams提交 -``` - -```mermaid -sequenceDiagram - participant 商户 - participant 商品发布页 as ShopXO商品发布页
(注入后的表单) - participant AdminGoodsSaveHook as AdminGoodsSave
(钩子返回HTML) - participant SaveHook as plugins_service_
goods_save_handle - participant BatchGen as SeatSkuService
::BatchGenerate() - participant DB as goods_spec_base
goods_spec_value - - 商户->>商品发布页: 选择场馆 + 分区 - 商户->>商品发布页: 点"发布商品" - - 商品发布页->>SaveHook: POST {venue_id, zones[]} - SaveHook->>BatchGen: BatchGenerate(goods_id, template_id, zones[]) - - loop 每500条一批 - BatchGen->>DB: INSERT spec_base
$vr-场馆 = "鸟巢"
$vr-分区 = "VIP区A"
$vr-时段 = "2026-06-01 19:00"
$vr-座位号 = "A_1" ~ "A_500" - end - - BatchGen-->>SaveHook: 返回座位级spec_base_id_map - SaveHook-->>商品发布页: 保存成功 - 商品发布页-->>商户: 商品上架 -``` - -```mermaid -flowchart LR - subgraph UserFlow["用户端"] - U1["用户浏览商品详情页
ticket_detail.html"] - U2["看到:场次选择器
座位可视化图(彩色热力图)"] - U3["点击座位 A_1"] - U4["goodsParams 自动组装
goodsParamsList[0]:
spec_ids: [场馆spec, 分区spec,
时段spec, 座位spec]
stock: 1
spec_base_id: 座位spec的ID"] - U5["提交订单"] - end - - subgraph Invisible["用户不可见的底层"] - DB1["spec_base 表
包含全部spec记录
但用户端只展示座位图"] - 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["显示空表单
提示「请重新选择」"] - B -->|有| D["解析 vr_ticket_config JSON"] - D --> E["还原:场馆 → 分区 → 座位选择状态"] - E --> F["管理员可修改选择
重新生成 spec_base 映射"] - F --> G["保存时:先删除旧spec
再批量插入新spec"] -``` +| # | v1.0 描述 | 实际代码 | v2.0 修正 | +|---|---------|---------|---------| +| 1 | `plugin.json` 用 `backend_hook` 注册 | ShopXO 用 `hooks` 数组 | 改为追加到 `hooks` 数组 | +| 2 | `AdminGoodsSave.php` 已存在或待实现 | 文件不存在 | 明确为 Phase 3-2 新建任务 | +| 3 | seat_map 顶层有 `venue` 字段 | 不存在,`$vr-场馆` 硬编码 | 新增设计决策:venue 存在哪 | +| 4 | seat_map 顶层有 `zones` 字段 | 实际是 `seats` 和 `sections` | 全文修正字段名 | +| 5 | `BatchGenerate(goods_id, venue_id, zones[])` | `(int $goodsId, int $seatTemplateId)` 全量生成 | 修正签名,注明全量/过滤差异 | +| 6 | `goods_spec_base` 前缀是 `vrt_` | ShopXO 原生表是 `sxo_` | 修正前缀,说明 `sxo_` vs `vrt_` | +| 7 | `vr_ticket_config` 字段存在 | 不存在,需新建迁移 | 明确为 Phase 3-4 新建任务 | +| 8 | 场馆配置是 Phase 2 已完成 | `Venue.php` 不存在,Phase 3-1 待实现 | 修正当前状态标注 | +| 9 | `spec_base_id_map` 是内存/缓存 | 持久化到 `vr_seat_templates.spec_base_id_map` | 修正存储位置描述 | +| 10 | 商品分类需绑定票务配置 | 代码中未强制校验 | 删除此约束描述 |