# ShopXO VR票务插件 — 架构文档 > 版本:v2.1(2026-04-14 更新,新增通用扩展方法论 + Q4 spec_value 复用粒度) > 源码位置:council-research/shopxo-eval/.worktrees/shopxo-evaluator/shopxo-src/ ## 项目概述 基于 ShopXO 生态的 VR 演唱会票务插件(Plan B)。 当 vr-ticket-mp 主线项目因维护成本或架构限制无法继续时,此插件作为 Plan B: - **完全复用** ShopXO 已有能力(会员体系/积分/优惠券/微信支付) - **仅扩展** 票务专属逻辑(座位/观演人/QR核销) - **优先通过插件机制扩展**,如插件机制不够用,允许直接修改 ShopXO 源码的最小范围(MIT 协议允许)。原则:改源码比绕弯快时,直接改;以进度为先,不为「不修改」而引入额外复杂度。 --- ## 核心设计决策(v2.0 重大更新) ### 1. spec = 场次 ✅ 已确认 **ShopXO 的每个 spec 选项 = 一个演出场次** - 不需要独立的 `vr_sessions` 表 - 商品的多个 spec 值(如"2026-06-01 19:30"、"2026-06-02 14:00")= 多个场次 - 用户在前端选择场次(spec)→ 再选择座位 → 提交购买 ### 2. 座位模板绑定分类 ✅ 已确认 座位模板 `vr_seat_templates` 通过分类 ID 绑定到商品: - 后台创建座位模板 → 选择绑定分类(如"VR演唱会-A区") - 创建商品 → 选择该分类 - 插件自动将模板 seat_map 写入商品 `venue_data` ### 3. venue_data 仅存座位配置 ✅ 已确认 `sxo_goods.venue_data` 精简为: ```json { "seat_map": { /* 座位图配置(来自 vr_seat_templates)*/ }, "spec_base_id_map": { /* seat_id → spec_base_id */ } } ``` sessions 信息由 ShopXO spec 系统提供,不再存储在 venue_data 中。 ### 4. 票务商品识别:venue_data 非空 ✅ 已确认 - 不新增 `item_type` 字段 - 判断逻辑:`!empty($goods['venue_data'])` → 票务商品 - 前端 Vue:判断 `goods.venue_data` 是否有值 ### 5. 完全通过 Hook 实现,不改 Goods.php ✅ 已确认 - `plugins_view_goods_detail_base_sku_top` 注入票务选座 UI - 完全覆盖原有规格选择区 - Goods.php 修改方案降级为备案(如 Hook 注入范围不够时启用) --- ## 核心技术发现(2026-04-14 调研) ### 1. CustomView Ace 编辑器 ⭐ ShopXO 内置全代码自定义页面编辑器,HTML/CSS/JS 三栏,实时预览。 文件:`app/admin/view/default/customview/saveinfo.html` 访问:后台 → 营销 → 自定义页面管理 ### 2. 商品详情页 30+ 钩子 ⭐ 最佳注入点:`plugins_view_goods_detail_base_sku_top`(规格选择区顶部) - 完全注入票务选座 UI - 不修改核心代码 ### 3. 票务商品识别方案(v2.0 更新)⭐ > **已废弃 Goods.php 修改方案**:完全通过 Hook + 前端判断实现 > - `plugins_view_goods_detail_base_sku_top` 注入票务选座 UI > - 前端 Vue 判断 `goods.venue_data` 是否有值 → 有值则加载票务专用前端 > - **Goods.php 不再需要修改** ### 4. shopxo-uniapp 支持微信小程序 ⭐ HBuilderX 导入 → 配置 AppID → 发行 → 微信开发者工具 条件编译指令已配置(`#ifdef MP-WEIXIN`) ### 5. QR 码生成内置 ⭐ 使用 `\base\Qrcode` 类 + phpqrcode 库: ```php $qr_url = MyUrl('index/qrcode/index', ['content' => base64_encode($data)]); ``` ### 6. 自提点核销页面可直接参考 ⭐ `pages/plugins/realstore/check/check.vue` — 完整 B 端核销 UI - uni.scanCode 扫码 - 手动输入兼容 - 成功/失败状态展示 ### 7. 订单 extension_data 存票务信息 ⭐ `sxo_order.extension_data` — JSON 扩展字段,可存核销状态 --- ## 整体架构 ``` ┌─────────────────────────────────────────────────────┐ │ ShopXO PHP 后端 │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ │ │ 会员/积分 │ │ 商品/订单 │ │ 微信支付 │ │ │ │ coupons │ │ items/orders│ │ payment │ │ │ └──────────────┘ └──────────────┘ └──────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ vr_ticket 插件 │ │ │ │ ┌────────────────────────────────────────┐ │ │ │ │ │ SeatTemplateService — 座位模板管理 │ │ │ │ │ │ TicketService — QR票生成/发放 │ │ │ │ │ │ VerifyService — 核销验证 │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────┐ │ │ │ │ │ 钩子: plugins_view_goods_detail_* │ │ │ │ │ │ 钩子: plugins_service_buy_order_* │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ shopxo-uniapp │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ 商品详情页 │ │ 票务选座页 │ ← Fork │ │ │goods-detail.vue│ │ ticket-buy.vue │ 自定义 │ │ └──────────────┘ └──────────────────┘ │ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ 用户中心 │ │ 票夹页 │ ← 新建 │ │ │ user.vue │ │ ticket-wallet.vue│ │ │ └──────────────┘ └──────────────────┘ │ │ ┌──────────────┐ │ │ │ 核销页 │ ← Fork realstore/check.vue │ │ │ verify.vue │ │ │ └──────────────┘ │ └─────────────────────────────────────────────────────┘ ``` --- ## 数据模型 ### ShopXO 复用表 | 表 | 用途 | |---|---| | `sxo_user` | 会员 | | `sxo_wallet` | 余额钱包 | | `sxo_integral` | 积分 | | `sxo_coupon` | 优惠券 | | `sxo_order` | 订单 | | `sxo_order_address` | 地址(含身份证) | | `sxo_payment` | 支付配置 | | `sxo_goods` | 商品(含票务商品,venue_data 存座位配置) | | `sxo_goods_spec_base` | SKU(每个座位 = inventory=1 的 SKU) | ### 插件独立表(v2.0 精简版) | 表 | 用途 | |---|---| | `vr_seat_templates` | 座位模板(seat_map JSON + 绑定分类ID) | | `vr_tickets` | 电子票(含QR数据 + 观演人) | | `vr_verifiers` | 核销员 | | `vr_verifications` | 核销记录 | > **v2.0 变更**:删除 `vr_events`、`vr_sessions`、`vr_venues` 表,座位配置由 `vr_seat_templates` 通过分类绑定实现。 ### vr_seat_templates 表结构 ```sql CREATE TABLE vr_seat_templates ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(180) NOT NULL COMMENT '模板名称(如:鸟巢-A区)', category_id BIGINT UNSIGNED NOT NULL COMMENT '绑定的 ShopXO 分类ID', seat_map LONGTEXT NOT NULL COMMENT '座位地图JSON', status TINYINT UNSIGNED DEFAULT 1, add_time INT UNSIGNED DEFAULT 0, upd_time INT UNSIGNED DEFAULT 0, UNIQUE KEY category_id (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='VR演唱会座位模板'; ``` ### vr_tickets 表结构 ```sql CREATE TABLE vr_tickets ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL COMMENT '订单ID', order_no CHAR(60) NOT NULL, goods_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, ticket_code CHAR(36) NOT NULL COMMENT 'UUID票码', qr_data TEXT COMMENT '加密QR内容', seat_info VARCHAR(255) COMMENT '座位信息(如 A区-3排-5座)', real_name VARCHAR(60) COMMENT '观演人姓名', phone CHAR(15) COMMENT '手机号', id_card CHAR(18) COMMENT '身份证号', verify_status TINYINT DEFAULT 0 COMMENT '0未核销, 1已核销', verify_time INT UNSIGNED DEFAULT 0, verifier_id BIGINT UNSIGNED DEFAULT 0, issued_at INT UNSIGNED DEFAULT 0, created_at INT UNSIGNED DEFAULT 0, updated_at INT UNSIGNED DEFAULT 0, UNIQUE KEY ticket_code (ticket_code), KEY order_id (order_id), KEY user_id (user_id), KEY verify_status (verify_status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='VR演唱会电子票'; ``` --- ## venue_data JSON 结构(v2.0 精简版) ```json { "seat_map": { "map": ["aaaaaaaaaaaa", "aaaaaaaaaaaa", "bbbbbb__bb", "bbbbbbbbbbbb"], "row_labels": ["A", "B", "C", "D"], "seats": { "a": { "price": 599, "label": "VIP区", "classes": "seat-vip" }, "b": { "price": 399, "label": "普通区", "classes": "seat-normal" }, "_": null }, "sections": [ { "name": "VIP区", "color": "#FF6B6B", "rows": [0, 1] }, { "name": "普通区", "color": "#4ECDC4", "rows": [2, 3] } ] }, "spec_base_id_map": { "1_1": { "spec_base_id": 10001, "row": "A", "col": 1, "seat_type": "a", "price": 599 }, "1_2": { "spec_base_id": 10002, "row": "A", "col": 2, "seat_type": "a", "price": 599 }, "3_5": { "spec_base_id": 10003, "row": "C", "col": 5, "seat_type": "b", "price": 399 } } } ``` --- ## 插件目录结构 ``` app/plugins/vr_ticket/ ├── plugin.json # 插件声明 ├── service/ │ ├── BaseService.php # 基础配置 │ ├── SeatTemplateService.php # 座位模板管理(替代 vr_sessions/vr_venues) │ ├── TicketService.php # 票生成/发放 │ └── VerifyService.php # 核销逻辑 ├── view/ │ └── Goods.php # 商品详情页钩子(备用,不修改核心) ├── Admin/ │ ├── Controller/ │ │ ├── SeatTemplateController.php # 座位模板管理 │ │ └── TicketController.php # 票务管理 │ └── View/ │ ├── seat_template_list.html │ ├── seat_template_save.html │ ├── ticket_list.html │ └── verification_list.html ├── Api/ │ └── Controller/ │ ├── SeatTemplateController.php # 座位模板API │ └── TicketController.php # 票/核销API └── EventListener.php # ShopXO事件监听 database/migrations/ # 数据库迁移 static/vr_ticket/ # 静态资源 ``` --- ## 完整购票流程 ``` 1. 商家后台 → VR票务插件 → 座位模板管理(创建 seat_map → 绑定分类) → 商品管理 → 创建商品 → 选择绑定分类(自动继承 venue_data) → 商品规格管理 → 添加规格(每个规格 = 一个场次,如"2026-06-01 晚场") 2. 用户前端 → 首页浏览 → 进入商品详情页 → 判断 goods.venue_data 是否有值 → 跳转到票务选座页(pages/ticket-buy) → 选择场次(spec)→ 选择座位 → 填写观演人信息 → 提交订单 → 微信支付 3. 支付成功 → ShopXO 支付回调 → TicketService::OnOrderPaid() 触发 → 生成 QR 票 → 存入 vr_tickets 表 → 用户可在票夹页查看 4. 核销(独立页面) → 工作人员打开核销页(pages/plugins/vr-ticket-verify) → 扫描用户 QR 码 → POST /api/ticket/verify → VerifyService 更新核销状态 → 返回核销结果 ``` --- ## 与 vr-ticket-mp 对比 | 维度 | vr-ticket-mp(主线) | vr-shopxo-plugin(Plan B) | |---|---|---| | **后端** | Go + Gin(自建) | PHP + ThinkPHP(ShopXO) | | **数据库** | Supabase Postgres | ShopXO MySQL | | **前端** | uni-app(自建) | shopxo-uniapp(已有) | | **会员体系** | Supabase Auth | ShopXO 内置 | | **积分/优惠券** | 自建 | ShopXO 内置 | | **微信支付** | 自建 | ShopXO 内置 | | **QR 核销** | 自建 | 基于 ShopXO 已有机制 | | **场次管理** | vr_sessions 独立表 | ShopXO spec(已确认) | | **座位配置** | venue_data 完整配置 | vr_seat_templates 绑定分类(已确认) | | **部署** | Docker | 虚拟主机即可 | | **AI 参与度** | 80% | 85-90% | | **维护成本** | 高 | 低 | --- ## 通用扩展方法论(v2.1 新增) > 已确认的通用插件扩展模式,可套用到票务/实物/套票等所有自定义类型商品。 ``` Plugin Table (vr_xxx) ↑ │ 绑定 spec_value (全局可复用) │ sxo_goods.extension_data (JSON) ↑ │ 插件在商品加载时注入 │ Frontend — 读 extension_data → 判断 type(票务/实物/套票) → 渲染对应自定义组件 ``` ### 核心链路 1. **插件数据表**(`vr_xxx`)存储自定义业务数据 2. **绑定 spec_value**:插件 spec_value 关联插件数据(spec_value 全局可复用) 3. **写入 `goods.extension_data`**:商品保存时,插件将 spec_value 映射写入 ShopXO 扩展字段 4. **前端按 type 渲染**:前端读取 `extension_data` → 判断 `type` 字段 → 加载对应自定义组件 --- ## 待确认问题(pending-council) 详见 `docs/ALIGNMENT.md` Section 二。 | 问题 | 说明 | |---|---| | Q1 | 座位模板与分类的绑定粒度(一个分类 = 一个座位区,还是一个商品可绑定多模板?) | | Q2 | spec_base_id_map 生成时机(每个 spec 独立座位,还是所有 spec 共用座位配置?) | | Q3 | 观演人信息存储位置(extension_data / vr_tickets / 作为 spec 选项) | | Q4 | spec_value 复用粒度 — 全局可复用 vs 商品级私有?(见上方通用扩展方法论) | --- ## 技术栈 - **PHP 8+** / ThinkPHP 8(ShopXO 生态) - **MySQL 5.7+**(ShopXO 同一实例) - **uni-app**(shopxo-uniapp,已支持微信小程序) - **phpqrcode**(`extend/qrcode/phpqrcode.php`,内置) - **uni.scanCode**(微信/支付宝等小程序扫码) --- ## 官方文档(开发前必查) - 官方文档站:https://doc.shopxo.net/ - 插件开发文档:https://doc.shopxo.net/article/3.html - 开发文档索引:https://doc.shopxo.net/article/4.html - uniapp 打包教程:https://doc.shopxo.net/article/1/293727233598554112.html - 完整索引:[docs/OFFICIAL_DOCS.md](docs/OFFICIAL_DOCS.md) ## 开发文档 | 文档 | 内容 | |---|---| | [docs/00_OVERVIEW.md](docs/00_OVERVIEW.md) | 项目总览 | | [docs/01_SHOPXO_TECHNICAL_RESEARCH.md](docs/01_SHOPXO_TECHNICAL_RESEARCH.md) | ShopXO 技术能力完整调研 | | [docs/02_FRONTEND_CUSTOMIZATION.md](docs/02_FRONTEND_CUSTOMIZATION.md) | shopxo-uniapp 编译与自定义 | | [docs/03_VERIFICATION_SYSTEM.md](docs/03_VERIFICATION_SYSTEM.md) | 核销系统设计 | | [docs/04_IMPLEMENTATION_ROADMAP.md](docs/04_IMPLEMENTATION_ROADMAP.md) | 实施路线图与 Agent 分工 | | [docs/06_SEAT_MAP_INTEGRATION.md](docs/06_SEAT_MAP_INTEGRATION.md) | 选座系统 + 座位地图(v2.0 已精简) | | [docs/ALIGNMENT.md](docs/ALIGNMENT.md) | 架构对齐文档(用户需求 vs 文档现状) |