14 KiB
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 官方立场:这是非标准用法,建议走购物车路线。
最大风险点
- OrderSplitService 按仓库拆单 — 如果场次商品和周边商品挂在不同仓库,多座位票务订单会被拆成多个子单。用户会收到多笔支付通知,体验割裂。→ 最小方案走购物车绕过此风险。
- 座位级 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 端:
- Fork
shopxo-uniapp→vr-shopxo-uniapp - 重写
pages/goods-detail/goods-detail.vue,接入 vr_ticket API - 新建
pages/ticket-seat/ticket-seat.vue(选座主流程) - 新建
pages/ticket-wallet/ticket-wallet.vue(票夹) - H5 本地预览 = 小程序编译效果,CSS 完全一致
关键约束(uni-app 开发规范):
- 用
rpx不用vw/vh - 用
<view>不用<div> - 避免
calc()混用单位 position: fixed吸顶在 H5 正常,小程序需 shopxo-uniapp 已有方案
最大风险点
- shopxo-uniapp fork 同步成本 — 官方 shopxo-uniapp 更新后需要手动同步,未来维护成本高。→ 建议在 fork 分支上做票务专属页面,官方页面保持独立升级路径。
- 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 结构:使用 <?php echo ModuleInclude('public/header'); ?> 包裹页面头
- 样式:全部内联 <style>,CSS 类名前缀 vr- 避免冲突
- JS 数据注入:const app = <?php echo json_encode($php_var); ?>
- 资源路径:静态资源用 <?php echo ModuleInclude('images/foo.png'); ?>
- 不使用:Vue/React CDN、外部 CDN(ShopXO 必须离线可用)
【第三层:具体需求】
[座位图 UI 规格:rpx 规范、颜色、尺寸 + 交互事件定义]
[ShopXO 数据契约:goods_spec_data、vr_seat_template、extension_data]
生成代码后处理步骤
- 路径替换:全局搜索
src="/static/→<?php echo ModuleInclude('static/') ?> - 变量注入点:在
<script>顶部注入var seatMap = <?php echo json_encode($vr_seat_template['seat_map']); ?> - 事件绑定:
onclick属性需改为onclick="vrTicketApp.toggleSeat(this)"格式(原生 JS) - 样式隔离:检查是否覆盖
.goods-detail-*等 ShopXO 全局类名,如有则加.vr-ticket-page限定符
最大风险点
- 无代码服务生成的 UI 过于复杂 — 座位图等高交互组件无法用无代码工具精确生成,强行生成会导致大量调试工作。→ 无代码服务适合静态展示区块(票务商品介绍、艺人信息图),座位图选座交互必须手写。
- ShopXO 离线可用约束 — ShopXO 运行在企业内部/私有化部署场景,所有资源必须本地化,无代码服务默认 CDN 引用必须全部替换。
Q4 结论:uni-app 兼容性技术栈选型
核心答案
推荐方案:一套 shopxo-uniapp fork + 条件编译,票务页面走独立路由(H5/小程序双端),商城标准页面复用 shopxo-uniapp 原生实现。
技术选型对比
| 维度 | 原生 HTML 模板 | uni-app fork shopxo-uniapp | Flutter / React Native |
|---|---|---|---|
| H5 本地预览 | ✅ 直接浏览器打开 | ✅ HBuilderX H5 运行 | ❌ 需真机调试 |
| 微信小程序 | ❌ 不支持 | ✅ 一键编译 | ✅ 需分别开发 |
| ShopXO API 对接 | 需手动 HTTP | shopxo-uniapp 已封装 | 需手动 HTTP |
| 学习成本 | 低 | 中(需熟悉 Vue) | 高 |
| 座位图等复杂交互 | 原生 JS 手写 | Vue 组件手写 | 手写 |
| 开发速度 | 快(单文件) | 中 | 慢 |
最终推荐:fork shopxo-uniapp,用 Vue 3 + SCSS,票务页面自研,其他页面复用。
票务页面与商城标准页面共存方案
vr-shopxo-uniapp/
├── pages/index/index.vue ← 改写:底部 Tab 新增「票务」Tab
├── pages/goods-detail/ ← 改写:票务商品跳 ticket-seat 页面
├── pages/ticket-seat/ ← 新建:选座 + 购票主流程(Vue 组件)
├── pages/ticket-wallet/ ← 新建:票夹(我的票)
├── pages/ticket-verify/ ← 新建:B 端核销
├── App.vue ← request_url 指向目标商城
└── pages.json ← 路由配置
H5/小程序一致性:uni-app H5 和小程序都基于 WebView,CSS 渲染一致。关键:用 rpx,用 <view>,避免浏览器私有前缀。
最大风险点
- shopxo-uniapp 官方更新同步 — 100+ forks,官方更新需手动 cherry-pick 到 vr fork。建议将票务专属页面与商城原生页面放在不同目录,改动隔离,升级时只同步商城页面。
- ShopXO 版本与 shopxo-uniapp 版本匹配 — shopxo-uniapp 的 API 契约随 ShopXO 后端版本变化,vr_ticket 插件如使用 shopxo-uniapp,请确认 ShopXO 版本(当前 v6.8.0),使用对应的 shopxo-uniapp 版本。
优先级与依赖
- Q4 依赖 Q2(多座位选座)和 Q1(H5 模板基础)
- Q4 本身是最终落地执行层,前三个 Q 的结论在 Q4 中整合实现
优先级矩阵
| 优先级 | 任务 | 负责 Agent | 前置条件 |
|---|---|---|---|
| P0 | Q2 多 SKU — 走购物车路线打通多座位下单 | BackendArchitect | 无 |
| P1 | Q4 uni-app fork — 建立项目骨架 | FrontendDev | Q1 结论 |
| P2 | Q4 ticket-seat.vue — 选座核心组件 | FrontendDev | P0 完成 |
| P3 | Q1 ticket_detail.html 增强 — 已售座位实时标记 | FrontendDev | 无 |
| P4 | Q3 提示词策略落地 — 无代码服务辅助静态区块 | ProductManager | Q1 结论 |
| P5 | Q2 理想方案 — 插件 hook 拦截 OrderSplitService | BackendArchitect | P0 验证 |
最小可行方案 vs 理想方案对比
| 维度 | 最小可行方案 | 理想方案 |
|---|---|---|
| 多座位下单 | 购物车路线(不碰 OrderSplitService) | 插件 hook 拦截,实现原生多 SKU 单订单 |
| 前端 H5 | 增强 ticket_detail.html(< 3 处改动) | 迁移到 uni-app H5 |
| 前端小程序 | shopxo-uniapp fork,票务页面 Vue 自研 | 完整迁移,小程序体验与 H5 一致 |
| 座位图 | 原生 JS,< 200 行 | Vue 组件,含缩放/拖拽/动画 |
| 观演人表单 | HTML + JS,支持动态增减 | Vue 组件化,数据校验 |
| 核销 B 端 | 复用现有后台核销页面 | 新建小程序核销页面(扫码 + API) |
| 交付周期 | 1 天(可上线 demo) | 2-3 周(完整票务流程) |
最大技术风险点汇总
| 风险 | 严重程度 | 缓解措施 |
|---|---|---|
| OrderSplitService 拆单导致多座位订单被拆 | 高 | 最小方案走购物车绕过;理想方案用插件 hook 拦截 |
| 座位级 SKU 未在后台创建 | 中 | 后台管理界面增加「批量生成座位规格」功能 |
| shopxo-uniapp fork 同步成本 | 中 | 票务页面与商城页面目录隔离,改动隔离升级 |
| 无代码服务无法生成高交互组件 | 低(已有认知) | 座位图等核心交互手写,静态区块用无代码辅助 |
| ShopXO 版本不匹配 shopxo-diy | 低(不走 DIY) | 不使用 DIY 设计器 |
关键文件清单
| 文件 | 用途 |
|---|---|
shopxo/app/service/BuyService.php |
订单创建入口,多 SKU 关键代码 |
shopxo/app/service/OrderSplitService.php |
拆单逻辑,多座位订单被拆的风险点 |
shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html |
当前票务详情页模板,Q2 多 SKU Plan A 代码已在此 |
docs/12_UNIAPP_FRONTEND_RESEARCH.md |
uni-app 调研存档,Q1/Q4 依赖此文档 |
docs/02_FRONTEND_CUSTOMIZATION.md |
ShopXO DIY 设计器局限性证明 |
docs/14_TEMPLATE_RENDER_INVESTIGATION.md |
模板渲染机制调查 |
docs/09_SHOPXO_HOOKS_REFERENCE.md |
插件钩子清单,Q2 理想方案所需 hook 在此 |
结论
- 多座位下单可行:走购物车路线,1 天内可上线多座位下单 Demo。
- uni-app 是最终目标:fork shopxo-uniapp 票务页面自研,商城页面复用,H5 预览 = 小程序编译效果。
- 无代码服务辅助有限:适合静态展示区块,座位图等核心交互必须手写。
- Immediate Action:BackendArchitect 提交 Q2 Plan A(购物车路线),FrontendDev 启动 shopxo-uniapp fork 项目骨架。