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

604 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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` | `<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 **不支持锁座**。对于演唱会选座:
- 座位作为 SKUinventory = 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 行判断外)