# ShopXO 技术能力完整调研 > 调研时间:2026-04-14(上午) > 调研方式:源码分析 + Council 并行 agent 调研(ShopXO / Bagisto / Saleor / Medusa) > 源码位置:`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` 组件渲染逻辑 ```php {{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|raw}}` 可用(会员信息、商品数据等) | | **访问地址** | `/index/customview/index?id=xxx` | | **全屏模式** | 支持 `is_full_screen` 参数 | ### 2.3 存储内容 | 字段 | 渲染方式 | |---|---| | `html_content` | `{{$data.html_content\|raw}}` 在 div 内 | | `css_content` | `` 自动包裹 | | `js_content` | `` 自动包裹 | ### 2.4 admin 端 Ace 编辑器实现 文件:`app/admin/view/default/customview/saveinfo.html` ```javascript 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 钩子渲染机制 控制器中: ```php 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); ``` 模板中渲染: ```php {{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`)支持主题覆盖: ```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 行判断: ```php // 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 必须实现 ```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`: ```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 '
...
'; } } ``` 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` 中: ```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` — 用户 ID - `status` — 订单状态 - `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`**: ```sql 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 码 ```php $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', // 高容错率 ]); ``` 在页面中展示:`` --- ## 十、购买流程与库存原子性 ### 10.1 Buy API 端点 ``` POST /?s=index/buy/add ← 创建订单 GET /?s=index/buy/index ← 获取结算页数据 ``` 关键参数 `goods_data`(绕过购物车直接下单): ```php $params['goods_data'] = [ 'goods_id' => 123, 'stock' => 1, 'spec' => [['type_id' => $type_id, 'value_id' => $value_id]] ]; ``` ### 10.2 库存扣减原子性 `BuyService.php` 第 1692-1699 行: ```php $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` 关键代码: ```vue 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`(核销码) 返回: ```json { "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 行判断外)