17 KiB
ShopXO 技术能力完整调研
调研时间:2026-04-14(上午) 调研方式:源码分析 + 官方文档交叉验证 官方文档站:https://doc.shopxo.net/(开发前必查) 源码位置:
council-research/shopxo-eval/.worktrees/shopxo-evaluator/shopxo-src/
一、DIY 设计器组件系统
1.1 组件定义机制
ShopXO DIY 设计器是 Vue3 SPA:
- 入口:
public/static/diy/js/entry/index-*.js - 组件列表定义在前端 JS 中(组件面板)
- 后端只存储布局配置 JSON(
sxo_diy表)
1.2 渲染层支持的组件类型
模板位置:
app/module/view/layout/public/common/module_view.html
| 组件值 (value) | 功能 | AI 参与度 |
|---|---|---|
images |
单图 | ✅ 替换链接即可 |
many-images |
多图(多种布局) | ✅ |
images-text |
图文混排 | ✅ |
images-magic-cube |
图片魔方(复杂宫格) | ✅ |
video |
视频 | ✅ |
goods |
商品列表(3 种样式:routine/leftright/rolling) | ✅ |
title |
标题文字 | ✅ |
custom |
自定义 HTML | ✅✅ 完全自由 |
1.3 custom 组件渲染逻辑
{{case custom}}
{{if !empty($vss['config']['custom'])}}
{{$vss.config.custom|raw}}
{{/if}}
{{/case}}
直接输出原始 HTML,无任何过滤。
⚠️ 注意:custom 组件在后端渲染层存在,但 admin DIY Vue3 设计器的组件面板 UI 入口不明确(未在源码中找到 admin 侧配置)。替代方案:使用 CustomView 完全弥补(见下节)。
1.4 DIY 设计器局限性
- 展示型页面(首页/专题页):重度依赖拖拽装修,JSON 数据库存储,AI 无法直接参与视觉设计
- 业务型页面(商品/订单/会员):表格 + 表单,AI 参与度高
- 票务插件 UI:完全走代码,不依赖 DIY 系统 → AI 参与度极高
二、CustomView 自定义页面系统 ⭐ 重大发现
2.1 功能概述
ShopXO 内置全代码自定义页面编辑器(Ace Playground Web Component)。
后台入口:营销菜单 → 自定义页面管理
2.2 技术细节
| 能力 | 详情 |
|---|---|
| 编辑器 | Ace Playground(非 iframe,是原生 Web Component) |
| 语言 | HTML + CSS + JavaScript 三栏独立编辑 |
| 实时预览 | iframe 渲染,实时刷新 |
| 模板语法 | ThinkPHP `{{$data.xxx |
| 访问地址 | /index/customview/index?id=xxx |
| 全屏模式 | 支持 is_full_screen 参数 |
2.3 存储内容
| 字段 | 渲染方式 |
|---|---|
html_content |
{{$data.html_content|raw}} 在 div 内 |
css_content |
<style>{{$data.css_content|raw}}</style> 自动包裹 |
js_content |
<script>{{$data.js_content|raw}}</script> 自动包裹 |
2.4 admin 端 Ace 编辑器实现
文件:app/admin/view/default/customview/saveinfo.html
class AcePlayground extends HTMLElement {
constructor() {
var shadow = this.attachShadow({mode: 'open'});
dom.buildDom(['div', {id: 'host'},
['div', {id: 'html'}], // HTML 编辑器
['div', {id: 'css'}], // CSS 编辑器
['div', {id: 'js'}], // JS 编辑器
['iframe', {id: 'preview'}] // 实时预览
], shadow);
this.htmlEditor = ace.edit(shadow.querySelector('#html'), {...});
this.cssEditor = ace.edit(shadow.querySelector('#css'), {...});
this.jsEditor = ace.edit(shadow.querySelector('#js'), {...});
this.preview = shadow.querySelector('#preview');
this.updatePreview(); // 实时更新预览
}
}
2.5 对票务的意义
✅ CustomView 可以实现完全自定义的票务辅助页面:
- 票夹页面(用户的所有电子票)
- 观演人管理页面
- 活动专题页面
在 DIY 设计器中,通过 custom 组件引用 CustomView 页面 URL,或直接在插件钩子中链接到 CustomView 页面。
三、商品详情页钩子系统(30+ 钩子)
3.1 核心文件
- 控制器:
app/index/controller/Goods.php(PluginsHook()方法) - 模板:
app/index/view/default/goods/module/middle_base/right/目录下各模板
3.2 钩子完整列表
📍 相册区域
plugins_view_goods_detail_photo_within ← 相册内部
plugins_view_goods_detail_photo_bottom ← 相册底部
📍 右侧购买面板
plugins_view_goods_detail_panel_original_price_top ← 原价上方
plugins_view_goods_detail_panel_price_top ← 现价上方
plugins_view_goods_detail_panel_price_bottom ← 现价下方
plugins_view_goods_detail_panel_bottom ← 整个面板底部
📍 规格/库存区域 ← 🎯 票务最佳注入点
plugins_view_goods_detail_base_sku_top ← 规格选择区顶部 ⭐
plugins_view_goods_detail_base_inventory_top ← 库存区域顶部
plugins_view_goods_detail_base_inventory_bottom ← 库存区域底部 ⭐
📍 购买导航
plugins_view_goods_detail_base_buy_nav_min_inside_begin ← 购买区内部前
plugins_view_goods_detail_base_buy_nav_min_inside ← 购买区内部后
📍 底部信息(标签页)
plugins_view_goods_detail_tabs_top / _content / _bottom
plugins_view_goods_detail_tabs_comments_top/bottom
plugins_view_goods_detail_tabs_guess_like_top/bottom
📍 全局
plugins_view_goods_detail_content_top/bottom
plugins_view_goods_detail_left_top
plugins_view_goods_detail_title
3.3 钩子渲染机制
控制器中:
foreach($hook_arr as $hook_name) {
$assign[$hook_name.'_data'] = MyEventTrigger($hook_name, [
'hook_name' => $hook_name,
'is_backend' => false,
'goods_id' => $goods_id,
'goods' => &$goods,
]);
}
MyViewAssign($assign);
模板中渲染:
{{foreach $plugins_view_goods_detail_base_sku_top_data as $hook}}
{{if is_string($hook) or is_int($hook)}}
{{$hook|raw}}
{{/if}}
{{/foreach}}
3.4 最佳注入点分析
票务选座 UI 最佳位置:plugins_view_goods_detail_base_sku_top
- 位于规格选择区顶部
- 在购买按钮上方
- 可以完全占据购买区域
- 插件返回 HTML 字符串即可渲染
四、按商品类型完全替换模板
4.1 主题机制
MyView() 函数(app/common.php)支持主题覆盖:
function MyView($view = '', $data = []) {
$theme = DefaultTheme(); // 从配置读取主题名
$file = APP_PATH.$group.DS.'view'.DS.$theme.DS.$view_new.$suffix;
// 如果当前主题有这个文件,就用主题的;否则用 default
}
主题目录:app/index/view/{theme_name}/goods/index.html
主题切换配置:sxo_config 表中 common_default_theme 字段。
⚠️ 限制:主题是全局的,所有商品共用同一套模板。
4.2 按商品类型动态选择模板
在 Goods.php Index() 方法中加 1 行判断:
// Goods.php Index() 方法,约第 440 行
// 在 return MyView(); 之前插入:
if(!empty($goods['item_type']) && $goods['item_type'] == 'ticket') {
return MyView('/goods/ticket_detail'); // 自定义票务模板
}
return MyView(); // 默认模板
对应模板文件:app/index/view/default/goods/ticket_detail.html
这是 ShopXO 允许范围内,唯一能让特定类型商品使用独立模板的方式。
五、插件系统架构
5.1 目录结构
app/plugins/{PluginName}/
├── service/
│ └── BaseService.php ← 必须:配置字段 + 安装/卸载逻辑
├── view/
│ ├── User.php ← 用户中心钩子实现
│ ├── Goods.php ← 商品详情页钩子实现
│ └── ...
├── js/
│ └── ...
└── 配置文件
5.2 BaseService.php 必须实现
namespace app\plugins\{PluginName}\service;
class BaseService {
// 1. 插件配置表单字段
public static function Config($params = []) {
return [
'title' => '插件名称',
'base' => [...], // 基础配置
'items' => [...], // 自定义配置项
];
}
// 2. 安装回调
public static function Install($params = []) { ... }
// 3. 卸载回调
public static function Uninstall($params = []) { ... }
}
5.3 视图钩子实现示例
插件 view/Goods.php:
namespace app\plugins\vr_ticket\view;
class Goods {
// 钩子:plugins_view_goods_detail_base_sku_top
public static function PluginsViewGoodsDetailBaseSkuTop($params) {
$goods = $params['goods'];
if(empty($goods['item_type']) || $goods['item_type'] != 'ticket') {
return ''; // 非票务商品,不输出
}
return '<div id="ticket-seat-map">...</div>';
}
}
ShopXO 事件系统自动发现并调用所有注册该钩子的插件方法。
5.4 Service 层钩子(可改业务逻辑)
plugins_service_goods_buy_nav_button_handle ← 可动态增减购买按钮
plugins_service_goods_spec_data ← 可改规格/库存数据
plugins_service_buy_order_insert_begin ← 订单创建前
plugins_service_buy_order_insert_success ← 订单创建后
六、用户中心钩子
6.1 路由
- 个人资料页:
/index/personal/(Personal.php)— ❌ 无钩子 - 用户中心:
/index/user/(User.php)— ✅ 6 个钩子
6.2 钩子列表
plugins_view_user_center_top ← 用户中心顶部
plugins_view_user_base_bottom ← 用户基础信息底部
plugins_view_user_various_top ← 聚合内容(订单/资产)顶部
plugins_view_user_various_bottom ← 聚合内容底部
plugins_view_user_various_inside_top ← 聚合内容内部顶部 ⭐
plugins_view_user_various_inside_bottom ← 聚合内容内部底部
6.3 最佳注入点
票夹(我的票):注入到 plugins_view_user_various_inside_top
在插件 view/User.php 中:
public static function PluginsViewUserVariousInsideTop($params) {
return render_ticket_list_html($tickets); // 返回票夹 HTML
}
七、搜索页钩子(8 个)
plugins_view_search_top ← 搜索区域顶部
plugins_view_search_bottom ← 搜索区域底部
plugins_view_search_inside_top ← 搜索结果区域顶部
plugins_view_search_inside_bottom ← 搜索结果区域底部
plugins_view_search_data_top ← 商品列表顶部
plugins_view_search_data_bottom ← 商品列表底部
plugins_view_search_nav_top ← 筛选导航顶部
plugins_view_search_nav_inside_begin/end ← 筛选导航内部
八、订单系统关键发现
8.1 订单状态
| 值 | 含义 |
|---|---|
| 0 | 待确认 |
| 1 | 已确认/待支付 |
| 2 | 已支付/待发货 |
| 3 | 已发货/待收货 |
| 4 | 已完成 |
| 5 | 已取消 |
| 6 | 已关闭 |
8.2 支付状态
| 值 | 含义 |
|---|---|
| 0 | 未支付 |
| 1 | 已支付 |
| 2 | 已退款 |
| 3 | 部分退款 |
8.3 订单模式
| 值 | 含义 |
|---|---|
| 0 | 快递 |
| 1 | 同城 |
| 2 | 自提 |
| 3 | 虚拟 |
票务建议:使用 order_model = 3(虚拟),因为不需要物流。
8.4 关键表结构
订单表 sxo_order:
order_no— 订单号(UNIQUE)user_id— 用户 IDstatus— 订单状态pay_status— 支付状态order_model— 订单模式extension_data— 扩展数据 JSON(可存票务核销信息)client_type— 客户端类型(weixin/h5/app 等)
订单地址表 sxo_order_address:
name— 收件人姓名tel— 收件人电话idcard_name— 身份证姓名idcard_number— 身份证号码
核销码表 sxo_order_extraction_code:
CREATE TABLE `sxo_order_extraction_code` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`order_id` int, -- 关联订单ID
`user_id` int, -- 用户ID
`code` char(30), -- 取货码(可用于票务核销)
`add_time` int,
`upd_time` int
);
九、QR 码生成能力
9.1 核心类
位置:extend/base/Qrcode.php
使用 phpqrcode 库(extend/qrcode/phpqrcode.php)
9.2 API 接口
展示模式:GET /?s=index/qrcode/index&content=BASE64_ENCODE(data)
创建模式:\base\Qrcode::Create(['content' => $data, 'root_path' => $path])
9.3 展示模式参数
| 参数 | 说明 | 默认值 |
|---|---|---|
content |
二维码内容(base64 编码) | 当前 URL |
level |
容错率(L/M/Q/H) | L |
size |
大小(1-30) | 6 |
mr |
外边距 | 1 |
9.4 生成票务 QR 码
$ticket_data = json_encode([
'code' => $attendee->ticket_code,
'event' => $event->name,
'session' => $session->session_time,
'seat' => $seat,
'exp' => time() + 86400 * 30 // 30天有效期
]);
$qr_url = MyUrl('index/qrcode/index', [
'content' => urlencode(base64_encode($ticket_data)),
'size' => 8,
'level' => 'H', // 高容错率
]);
在页面中展示:<img src="<?= $qr_url ?>" />
十、购买流程与库存原子性
10.1 Buy API 端点
POST /?s=index/buy/add ← 创建订单
GET /?s=index/buy/index ← 获取结算页数据
关键参数 goods_data(绕过购物车直接下单):
$params['goods_data'] = [
'goods_id' => 123,
'stock' => 1,
'spec' => [['type_id' => $type_id, 'value_id' => $value_id]]
];
10.2 库存扣减原子性
BuyService.php 第 1692-1699 行:
$where = [
'id' => $spec_base_id,
'goods_id' => $goods_id,
'inventory >= ' => $buy_number
];
Db::name('GoodsSpecBase')->where($where)->dec('inventory', $buy_number)->update();
- 订单支付时在事务内原子扣减
- WHERE 条件不满足时
dec()返回 0 行 → 事务回滚 - 结论:ShopXO 自身防超卖是安全的,无需额外 Go 锁座层
10.3 锁座机制
ShopXO 不支持锁座。对于演唱会选座:
- 座位作为 SKU(inventory = 0 或 1)
- 购买时直接扣库存
- 超卖风险由 ShopXO 库存原子性兜底
十一、自提点核销机制(最佳参考)
11.1 核销码表
sxo_order_extraction_code — ShopXO 用此表存储自提点取货码
11.2 uni-app 核销页面
位置:pages/plugins/realstore/check/check.vue
关键代码:
<!-- 扫码按钮(非 H5 平台显示)-->
<!-- #ifndef H5 -->
<uni-icons type="scan" size="56rpx" @tap="scan_event"></uni-icons>
<!-- #endif -->
<!-- 手动输入核销码 -->
<input type="text" v-model="check_value" />
<!-- 扫码触发 -->
uni.scanCode({
success: (res) => {
self.check_value = res.result;
self.form_submit(); // 自动提交验证
}
});
<!-- 提交验证 -->
uni.request({
url: app.globalData.get_request_url('verification', 'adminorderallot', 'realstore'),
method: 'POST',
data: { extraction_code: this.check_value }
});
11.3 核销 API
路径:/?s=admin/orderallot/verification(插件内)
参数:extraction_code(核销码)
返回:
{
"code": 0, // 0=成功,其他=失败
"msg": "核销成功",
"data": {...}
}
11.4 核销页面 B 端 UI 特性
- 成功/失败结果大字展示(绿/红)
- 核销后清空输入框,可连续扫描
- 底部统计(已核销/待核销/今日核销)
- 支持切换核销门店(popup 选择)
十二、ShopXO 其他关键表
12.1 商品表 sxo_goods
| 字段 | 说明 |
|---|---|
site_type |
履约类型(0-8,对应快递/自提/虚拟等) |
is_exist_many_spec |
是否多规格 |
inventory |
主库存 |
site_model |
站点模式 |
12.2 规格相关表
sxo_goods_spec_type— 规格维度(如区域:A区/B区/C区)sxo_goods_spec_value— 规格值映射(如座位:排号+座号)sxo_goods_spec_base— 每个 SKU 的库存+价格(inventory整数字段)
12.3 库存日志表
sxo_order_goods_inventory_log — 记录所有库存变动,支持回滚
十三、综合评估
| 场景 | 能力 | 方式 |
|---|---|---|
| 首页/专题页 DIY | ✅ 完全支持 | DIY 设计器 |
| 自定义 HTML 组件 | ⚠️ 渲染层支持,admin UI 不明确 | custom 组件 |
| 完全自定义页面 | ✅✅ Ace 编辑器 | CustomView |
| 商品详情页注入票务 UI | ✅✅ 30+ 钩子,最佳注入点 SKU 顶部 | 插件钩子 |
| 商品详情页整体替换 | ✅ 1 行控制器代码 | 修改 Goods.php |
| 用户中心注入票夹 | ✅ 6 个钩子 | 插件 |
| 个人资料页 | ❌ 无法自定义 | 固定模板 |
| 搜索页扩展 | ✅ 8 个钩子 | 插件 |
| 订单详情页 | ❌ 无视图钩子,仅 Service 钩子 | 插件改数据 |
| 购买流程 | ✅ 多个 Service 钩子 | 插件改逻辑 |
| B 端核销页 | ✅✅ 参考 realstore/check.vue | Fork + 定制 |
| QR 码生成 | ✅✅ 内置 phpqrcode | \base\Qrcode |
| 插件开发 | ✅ 完整 Hook + Service 机制 | 文档完善 |
AI 参与度:票务核心逻辑 95% 可 AI 生成(除 Goods.php 的 1 行判断外)