vr-shopxo-plugin/docs/11_EDITOR_AND_INJECTION_DES...

16 KiB
Raw Blame History

后台编辑器 + 商品发布注入方案设计

版本v2.0 | 日期2026-04-15 | 状态:待大头确认后执行

v2.0 更新:经 PM Auditor 代码级核查,修正 10 处与实际代码不符的描述(见文末勘误表)


一、整体架构一句话

插件在 ShopXO 后台建一套"场馆配置"管理界面,用户发布票务商品时,选场馆 → 插件自动生成海量 Spec 并注入商品,用户全程不碰 ShopXO 原生 Spec 管理。


二、三大核心区域

┌─────────────────────────────────────────────────────────────────┐
│  ShopXO 原生后台                                                   │
│                                                                   │
│  ┌─────────────────┐    ┌──────────────────────┐                │
│  │ 商品管理         │    │ 插件专属后台(新增)     │                │
│  │  发布/编辑商品   │ ←→ │ 场馆配置管理            │                │
│  │  (注入点)      │    │  座位分区模板编辑器     │                │
│  └─────────────────┘    └──────────────────────┘                │
└─────────────────────────────────────────────────────────────────┘

区域 A插件专属后台新增

商户在 ShopXO 后台左侧菜单进入「VR票务」

VR票务
  ├── 场馆配置        ← Phase 3-1 新增(当前不存在)
  ├── 座位模板        ← 已存在Phase 2
  ├── 电子票管理      ← 已存在
  ├── 核销员管理      ← 已存在
  └── 核销记录        ← 已存在

⚠️ 当前状态Venue.php 控制器不存在。vr_seat_templates 表只有 name / category_id / seat_map / spec_base_id_map 字段,venue 信息目前不存在于数据库中。

区域 BShopXO 商品发布页(注入点)

⚠️ 待核实:商品发布页 Tab 名称需对照 saveinfo.html 模板确认。当前描述"规格型号"Tab 可能不准确。

商户在 ShopXO 后台「商品管理 → 添加商品」:

添加商品
  [商品名称] [商品分类]
  
  ▼ 票务配置           ← ShopXO 原生区域,我们注入票务选择器
    [请选择场馆 ▼]    ← 场馆下拉(来自 vr_seat_templates 表)
    [请选择分区 ▼]    ← 分区多选(根据场馆联动)
    
  [商品详情 富文本编辑器]
  ▼ 其他Tab参数/图片等)← ShopXO 原生

区域 C插件商品详情页已存在

用户在前台看到票务商品详情页,选座下单,这个已实现。


三、场馆配置管理(区域 A

3.1 数据结构(当前 vs 目标)

⚠️ 当前 seat_map 实际结构(从 SeatSkuService::BatchGenerate 代码反推):

  • venue 顶层字段
  • zones 顶层字段
  • 顶层字段为:mapseatssectionsrow_labels
  • $vr-场馆 的值目前硬编码为"国家体育馆"

目标结构Phase 3-1 需调整)

{
  "venue": {                       // ← Phase 3-1 新增:从 venue 表读取或直接存这里
    "name": "国家体育馆",
    "address": "北京市朝阳区",
    "image": "/uploads/vr/venue/1.jpg"
  },
  "map": ["AAAAAA", "BBBBBB", "CCCCCC"],
  "seats": {                      // ← 实际字段名,不是 zones
    "A": { "price": 899, "color": "#e74c3c", "label": "VIP区" },
    "B": { "price": 599, "color": "#3498db", "label": "看台区" },
    "C": { "price": 299, "color": "#2ecc71", "label": "普通区" }
  },
  "sections": [                   // ← 实际字段名,存储分区元信息
    { "char": "A", "name": "VIP区", "color": "#e74c3c" },
    { "char": "B", "name": "看台区", "color": "#3498db" },
    { "char": "C", "name": "普通区", "color": "#2ecc71" }
  ],
  "row_labels": ["A", "B", "C"]
}

关键理解

  • seats = 每行row char的默认价格/颜色/标签(整行统一)
  • sections = 每个分区的元信息char → name/color 映射)
  • venue 信息目前不存在于 seat_mapPhase 3-1 需要决策:是加到 seat_map 里,还是新建 vr_venues

3.2 场馆配置管理页面(表单可视化编辑器)

⚠️ 待实现:当前 Venue.php 控制器不存在Phase 3-1 需要新建。

商户在插件后台「场馆配置 → 添加」,看到以下表单:

【场馆基本信息】
  场馆名称:[________________________]
  场馆地址:[________________________]
  场馆图片:[上传按钮]
  
【分区配置】(可以加/减分区)
  ┌──────────────────────────────────────────────────┐
  │ Char │ 标签VIP区  │ 单价899元 │ 颜色:[红]   │
  ├──────────────────────────────────────────────────┤
  │ Char │ 标签:看台区 │ 单价599元 │ 颜色:[蓝]   │
  ├──────────────────────────────────────────────────┤
  │ Char │ 标签:普通区 │ 单价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 venue 信息的设计决策(待讨论)

选项 Avenue 信息存入 seat_map.venueJSON 顶层)

  • 优点:简单,不改表结构
  • 缺点:一个模板只能关联一个 venue

选项 B:新建 vr_venues 表,vr_seat_templates.venue_id 外键关联

  • 优点:一个 venue 可对应多个模板(如鸟巢主场地 + 鸟巢室外场)
  • 缺点:多一张表,多一套 CRUD

当前 vr_seat_templates 表只有 name 字段同时承载"模板名"和"场馆名",暂未分离。


四、商品发布页注入(区域 B

4.1 注入原理

ShopXO admin Goods::SaveInfo() 
  → 调用 hook plugins_view_admin_goods_saveGoods.php:159
  → 触发 vr_ticket/hook/AdminGoodsSave.phpPhase 3-2 新建)
  → 返回票务配置面板 HTML
  → 插入 saveinfo.html 的 base tab 内(<div class="am-form-group"> 容器)

4.2 钩子注册方式

⚠️ 重要修正ShopXO 插件系统不支持 backend_hook 字段。 plugin.json 实际使用 hooks 数组。

plugin.json 正确格式

{
  "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 表:

$templates = Db::name('vr_seat_templates')
    ->field('id, name, seat_map')
    ->where(['status' => 1])
    ->select();

当前 seat_map 中无 venue.name,所以下拉显示的是 vr_seat_templates.name 字段(如 "Bird Nest - Zone A")。

⚠️ Phase 3-1 需要决策venue 独立后,下拉应显示 venue.name 而非模板 name。


五、商品发布完整流程

场景:商户发布一张票务商品

Step 1:商户进入 ShopXO 后台 → 商品管理 → 添加商品

Step 2:填写基础信息

商品名称:周杰伦 VR 虚拟演唱会
商品分类VR演出

Step 3:在注入的票务配置面板选场馆和分区

票务配置
  场馆:[Bird Nest - Zone A ▼]   ← AdminGoodsSave 注入的下拉
  分区:[✓VIP区  ✓看台区]        ← 多选,根据场馆联动

Step 4:点击发布

商户点击"发布商品"
    ↓
Goods::Save() 调用 GoodsService::GoodsSave()Goods.php:187
    ↓
GoodsService::GoodsSave() 执行标准商品保存逻辑
    ↓
触发钩子 plugins_service_goods_save_handleGoodsService.php:1550
    ↓
AdminGoodsSaveHandle() 收到 POST 数据
    ├── 提取 template_id 和选中的分区 chars
    ├── 调用 SeatSkuService::BatchGenerate($goodsId, $templateId)
    │     └── 注意BatchGenerate() 签名是 (int $goodsId, int $seatTemplateId)
    │         └── 当前版本:按 template 全量生成(不支持按 zones 过滤)
    │         └── 为每个座位生成一行 sxo_goods_spec_baseinventory=1
    │         └── 同时写入 sxo_goods_spec_value$vr-场馆/$vr-分区/$vr-时段/$vr-座位号)
    ├── 更新 vr_seat_templates.spec_base_id_map持久化非内存
    └── 返回(让标准保存流程继续)
    ↓
商品保存完成
    ↓
订单后续流程不变(已有实现)

六、Spec 生成后的内部结构(技术细节)

⚠️ 表前缀ShopXO 原生表使用 sxo_ 前缀,插件自定义表使用 {$prefix}vrt_

以"国家体育馆 + VIP区(A) + 看台区(B)"为例:

sxo_goods_spec_base每行 = 一个座位 SKU

spec_base_id goods_id inventory price
2001 123 1 899
2002 123 1 899
... 123 1 899
3001 123 1 599
... 123 1 599

sxo_goods_spec_type每行 = 一个规格维度)

id goods_id name value
1 123 $vr-场馆 [{"name":"国家体育馆"}]
2 123 $vr-分区 [{"name":"VIP区"},{"name":"看台区"}]
3 123 $vr-时段 [{"name":"待选场次"}]
4 123 $vr-座位号 [{"name":"A_1"},{"name":"A_2"},...,{"name":"B_6"}]

vr_seat_templates.spec_base_id_map持久化字段

⚠️ 修正spec_base_id_map 不是内存/缓存,是持久化到 vr_seat_templates 表的字段。

商户在前台选座时,前端根据 seatKey"A_1")查表得到对应的 spec_base_id

{
  "A_1": 2001, "A_2": 2002, ..., "A_6": 2006,
  "B_1": 3001, "B_2": 3002, ..., "B_6": 3012
}

七、BatchGenerate 实际接口(已实现代码)

⚠️ 关键修正:文档之前描述的参数签名与实际代码不符。

实际签名SeatSkuService.php:34

public static function BatchGenerate(int $goodsId, int $seatTemplateId): array

返回格式

[
    '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 列表(几千个座位 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
商户感知 在后台规格管理看到 无感(我们注入的表单已覆盖场景)
用户在前台看到 购物车/下单流程 票务选座 UIticket_detail.html
核销 不涉及 每座位一个 QRvr_tickets 表)

商户永远不需要进入 ShopXO 原生的"规格管理"界面来管理票务座位。


十、实施步骤

Phase 3-1后台场馆配置管理新建 admin 页面)

  • 决策venue 信息存在 seat_map 内还是独立表?
  • 新建 admin/controller/Venue.php
  • 新建 admin/view/venue/list.html(场馆列表)
  • 新建 admin/view/venue/save.html表单编辑器venue + zone + 座位排布)
  • 升级 vr_seat_templates.seat_map JSON 结构(加入 venue 顶层)
  • 将现有测试数据迁移为带 venue 的格式

Phase 3-2商品发布页注入

  • plugin.jsonhooks 数组中追加钩子(不用 backend_hook
  • 新建 hook/AdminGoodsSave.php(注入票务配置面板 HTML
  • 新建 hook/AdminGoodsSaveHandle.php(处理保存数据)
  • 场馆下拉联动分区多选Vue3轻量
  • 确认商品发布页实际 Tab 名称(待对照 saveinfo.html

Phase 3-3Spec 自动生成接入

  • BatchGenerate() 增加按 zones 过滤参数(如需要)
  • extension_data 写入 order_goods选座信息追溯

Phase 3-4优先级低商品编辑回显

  • 新建 sxo_goods.vr_ticket_config 字段迁移
  • 编辑页加载时解析 vr_ticket_config,还原 venue + zone
  • 编辑页票务配置面板回显

十一、勘误表v1.0 → v2.0

# v1.0 描述 实际代码 v2.0 修正
1 plugin.jsonbackend_hook 注册 ShopXO 用 hooks 数组 改为追加到 hooks 数组
2 AdminGoodsSave.php 已存在或待实现 文件不存在 明确为 Phase 3-2 新建任务
3 seat_map 顶层有 venue 字段 不存在,$vr-场馆 硬编码 新增设计决策venue 存在哪
4 seat_map 顶层有 zones 字段 实际是 seatssections 全文修正字段名
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 商品分类需绑定票务配置 代码中未强制校验 删除此约束描述