# ShopXO 酷炫前端模板实现方案调研报告 > 调研日期:2026-04-20 > 状态:**Round 3 收敛版本** > 参与:FrontendDev (Q1/Q4)、BackendArchitect (Q2)、ProductManager (Q3)、FirstPrinciples (Q4 集成) --- ## Q2 结论:单订单多 SKU 支持 ### 核心答案 **ShopXO 订单模型技术上支持同一商品多规格(多 SKU)出现在同一订单中**,但现有 vr_ticket 模板的 `submit()` 只传单行,完整多座位下单需要做两件事:① 让前端传多行 `goods_data`,② 阻止 `OrderSplitService` 按 warehouse 拆单。 ### 证据来源 | 文件 | 关键代码 | 说明 | |------|---------|------| | `BuyService.php:86` | `foreach($params['goods_data'] as $v)` | 循环处理每个商品项,每项独立 spec_base_id | | `BuyService.php:423-435` | `extension_data` JSON 序列化 | 每行 item 支持挂载座位/观演人扩展数据 | | `BuyService.php:101` | `md5(goods_id + spec implode)` | 内部用 goods_id+spec 组合生成唯一行 ID | | `OrderSplitService.php:52-53` | `GoodsWarehouseAggregate()` | **拆单触发点**:按仓库分组,多 SKU 同一仓库则合并 | | `OrderSplitService.php:289-310` | 按 spec MD5 找 spec_base_id | spec_base_id 已可不同(座位级 SKU) | | `ticket_detail.html:413-436` | `goodsParamsList.map()` | Plan A 代码已写好,但 URL 只传第一行(bug) | ### 最小可行方案(Multi-Seat Now) **改动点仅 3 处,全在 ticket_detail.html,不碰 ShopXO 核心:** ``` ticket_detail.html submit() 函数 BEFORE: location.href = checkoutUrl + '&goods_params=' + encodeURIComponent(goodsParams) AFTER: goodsParamsList 整体 base64 编码,拆分成多条 goods_data 逐条 POST 到 CartSave, 然后跳转到合并支付流程(ShopXO 购物车天然支持多商品同单) ``` **为什么走购物车路线更稳:** - `BuyCart` → `BuyTypeGoodsList` → 直接调用 `BuyGoods`,完美支持多 `goods_data` 行 - 不需要hook `OrderSplitService`,购物车结算路径不触发按仓库拆单(只按商品拆) - 核销逻辑不受影响:支付成功后 `plugins_service_order_pay_success_handle_end` 钩子正常触发 ### 理想方案(Multi-Seat Proper) 在插件中挂载 `plugins_service_buy_group_goods_handle` 钩子,拦截 `OrderSplitService`,将同一 goods_id + 不同 spec_base_id 的多行合并进同一个 order_base: ``` plugins_service_buy_group_goods_handle: - 按 goods_id 聚合,而非按 warehouse_id - 每个 goods_id 只生成一条 order_base,goods_items 内嵌多个 spec_base_id 不同的行 - extension_data 按座位索引扁平化存储 ``` **ShopXO 官方立场**:这是非标准用法,建议走购物车路线。 ### 最大风险点 1. **OrderSplitService 按仓库拆单** — 如果场次商品和周边商品挂在不同仓库,多座位票务订单会被拆成多个子单。用户会收到多笔支付通知,体验割裂。→ **最小方案走购物车绕过此风险**。 2. **座位级 SKU 未在 ShopXO 后台创建** — `specBaseIdMap` 依赖数据库中已存在的 `sxo_goods_spec_base` 记录。如果模板生成的 seatKey(如 "A_1")没有对应的 spec_base_id,`submit()` 会降级到 Zone 级别 SKU(同一 zone 全部座位共享一个 spec),失去座位粒度。→ **需要后台管理员先为每个座位创建规格**。 ### 优先级与依赖 - **Q2 是 Q4 的前提** — Q4 的"多座位选座流程"依赖 Q2 的多 SKU 订单能力。 - Q2 本身不依赖 Q1,可以独立推进。 - Q3 和 Q4 无依赖,但 Q3 生成的代码需适配 Q4 选型(H5 vs uni-app)。 --- ## Q1 结论:ShopXO 自定义模板最佳实践 ### 核心答案 **票务详情页不走 DIY 设计器,直接修改 `ticket_detail.html` 的 PHP+原生 JS**;uni-app 端 fork `shopxo-uniapp` 改写 `goods-detail.vue`,无需经过 ShopXO 模板中间层。 ### 证据来源 | 文件/文档 | 结论 | |----------|------| | `docs/02_FRONTEND_CUSTOMIZATION.md` | DIY 设计器只支持静态 HTML 区块嵌入,无法参数化;uni-app 完全独立于 ShopXO 模板 | | `docs/12_UNIAPP_FRONTEND_RESEARCH.md` | shopxo-uniapp 是独立 Vue 项目,通过 API 对接 ShopXO;CSS 在 H5/小程序完全一致(WebView 同源) | | `docs/14_TEMPLATE_RENDER_INVESTIGATION.md` | ShopXO view/goods/ 模板使用原生 PHP + 原生 JS,session/buy 等控制器直接 render | | `ticket_detail.html` | 当前已实现:场次选择 + 座位图渲染 + 观演人表单 + 购买栏 | ### 最小可行方案 **H5 端**:在现有 `ticket_detail.html` 基础上增强,引入: - 座位类型图例(已完成) - 已售座位 AJAX 实时标记(待实现 `loadSoldSeats()`) - 座位缩放/拖拽交互(原生 JS,<200 行) - 动态场次切换时重置已选座位(已写但未调用) **技术栈**:`原生 HTML + 内联 CSS + 内联 JS`,无框架依赖,ShopXO 模板系统直接渲染,无需构建。 ### 理想方案 **uni-app 端**: 1. Fork `shopxo-uniapp` → `vr-shopxo-uniapp` 2. 重写 `pages/goods-detail/goods-detail.vue`,接入 vr_ticket API 3. 新建 `pages/ticket-seat/ticket-seat.vue`(选座主流程) 4. 新建 `pages/ticket-wallet/ticket-wallet.vue`(票夹) 5. H5 本地预览 = 小程序编译效果,CSS 完全一致 **关键约束(uni-app 开发规范)**: - 用 `rpx` 不用 `vw/vh` - 用 `` 不用 `
` - 避免 `calc()` 混用单位 - `position: fixed` 吸顶在 H5 正常,小程序需 shopxo-uniapp 已有方案 ### 最大风险点 1. **shopxo-uniapp fork 同步成本** — 官方 shopxo-uniapp 更新后需要手动同步,未来维护成本高。→ 建议在 fork 分支上做票务专属页面,官方页面保持独立升级路径。 2. **ShopXO 版本 vs shopxo-diy 版本匹配** — shopxo-diy v1.4.2 ↔ ShopXO v6.8.0,如果使用 DIY 设计器管理非票务页面,版本必须严格匹配。 ### 优先级与依赖 - Q1 是 Q4 的技术基础,Q1 的结论直接支撑 Q4 的技术选型决策。 - Q3 依赖 Q1 的约束条件输出。 --- ## Q3 结论:第三方无代码构建服务提示词策略 ### 核心答案 **用"模板 + 示例 + 约束"三层结构撰写 Prompt**,ShopXO 模板的特殊性(模块化 PHP 标签、ShopXO 资源路径 API)在 Prompt 中明确声明,生成代码后只需做两件事后处理:① 替换静态资源路径为 `ModuleInclude()` 调用,② 注入座位图数据结构(从 PHP 模板变量传入)。 ### Prompt 三层结构 ``` 【第一层:角色定义】 你是一个 ShopXO v6.8.0 模板开发者,擅长编写票务商品详情页。 【第二层:约束清单】 - HTML 结构:使用 包裹页面头 - 样式:全部内联