605 lines
17 KiB
Markdown
605 lines
17 KiB
Markdown
# 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 行判断外)
|