# 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` 组件渲染逻辑

```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` | `<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`

```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 '<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` 中：
```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',  // 高容错率
]);
```

在页面中展示：`<img src="<?= $qr_url ?>" />`

---

## 十、购买流程与库存原子性

### 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
<!-- 扫码按钮（非 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`（核销码）

返回：
```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 行判断外）
