vr-shopxo-plugin/docs/01_SHOPXO_TECHNICAL_RESEARC...

17 KiB
Raw Blame History

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 中(组件面板)
  • 后端只存储布局配置 JSONsxo_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.phpPluginsHook() 方法)
  • 模板: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 — 用户 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

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 不支持锁座。对于演唱会选座:

  • 座位作为 SKUinventory = 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 行判断外)