vr-shopxo-plugin/council-output/EDITOR_RESEARCH.md

198 lines
7.5 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.

# vr-shopxo-plugin 编辑器方案调研报告
> 版本v1.0 | 日期2026-04-15 | AgentBackendArchitect (Q2) + FrontendDev (Q1)
## Q2商品发布页替换方案邪门方案可行性 — BackendArchitect
### 核心代码路径
| 文件 | 作用 |
|------|------|
| `shopxo/app/admin/controller/Goods.php:82-177` | SaveInfo() 方法 |
| `shopxo/app/admin/controller/Goods.php:187-192` | Save() 方法 |
| `shopxo/app/service/GoodsService.php:1549-1565` | plugins_service_goods_save_handle 钩子 |
| `shopxo/app/admin/view/default/goods/saveinfo.html:505-510` | 钩子渲染位置 |
---
### Q2-A: 钩子调用位置分析
**`plugins_view_admin_goods_save` 在 SaveInfo() 中的位置**Goods.php:159-167
```php
$hook_name = 'plugins_view_admin_goods_save';
$assign[$hook_name.'_data'] = MyEventTrigger($hook_name, [
'hook_name' => $hook_name,
'is_backend' => true,
'goods_id' => isset($params['id']) ? $params['id'] : 0,
'data' => &$data,
'params' => &$params,
]);
// 紧接着:
MyViewAssign($assign);
return MyView(); // 渲染 saveinfo.html
```
**结论:钩子在模板渲染之前被调用,结果存入 `$assign['plugins_view_admin_goods_save_data']` 供模板使用。**
---
### Q2-B: 能否完全替换页面内容?
**关键发现NO — 钩子仅是注入点,不是替换点。**
查看 `saveinfo.html` 模板结构(简化):
```
ModuleInclude('public/header')
<div class="content">
<form action="admin/goods/save" method="POST">
[商品名称输入框]
[商品分类选择器]
[nav_switch_btn: base/spec/parameters/photos/content/video/seo/use_guide]
<!-- base tab 内容 -->
<div class="am-form-group">
<label>...</label>
<div>
plugins_view_admin_goods_save ← 钩子在此注入
</div>
</div>
[SEO信息tab]
[popup submit按钮]
</form>
</div>
```
钩子注入位置在第 505-510 行:
```html
{{if !empty($plugins_view_admin_goods_save_data) and is_array(...)}}
{{foreach $plugins_view_admin_goods_save_data as $hook}}
{{$hook|raw}}
{{/foreach}}
{{else /}}
{{:ModuleInclude('public/not_data')}}
{{/if}}
```
**注入内容受限于 `<div class="am-form-group">` 容器内**,外层 `<form>`、Tab 导航、商品名称/分类等核心字段无法被替换。
### 替代方案:模板文件覆盖
`MyView()` 函数common.php:984-991支持主题覆盖插件文件
```php
if(substr($view, 0, 16) == '../../../plugins') {
$plugins_view_file = APP_PATH.$group.DS.'view'.DS.$theme.DS.'plugins'.DS.str_replace(...);
if(@file_exists($plugins_view_file)) {
$view = $plugins_view_file; // 主题文件覆盖插件文件
}
}
```
但这只对 `plugins` 控制器的路径生效。SaveInfo() 是 `admin/goods` 路径,无法利用此机制。
**唯一可行的完全替换路径**:将 `saveinfo.html` 复制到 `app/admin/view/default/goods/saveinfo.html`ShopXO 默认主题目录),然后修改。但这是覆盖核心文件,升级 ShopXO 时会丢失。
**推荐:不要完全替换页面。** 改为在 base tab 内注入 ticket 专属表单,或添加新的 tab 项。
---
### Q2-C: Save() 数据接收方式
**Goods::Save()Goods.php:187-192**
```php
public function Save() {
$params = $this->data_request; // ← ThinkPHP 标准请求数据($_POST
$params['admin'] = $this->admin;
return ApiService::ApiDataReturn(GoodsService::GoodsSave($params));
}
```
数据源是 ThinkPHP 的 `$this->data_request`,等价于标准 `$_POST`。**任何自定义表单都可以 POST 到 `admin/goods/save`,只要字段名符合 GoodsService 期望。**
**GoodsService::GoodsSave() 中钩子位置GoodsService.php:1549-1565**
```php
// 构建 $data 数组(从 $params 提取 title, category_ids 等)
$data['title'] = $params['title'] ?? '';
// ... 更多字段 ...
// 商品保存处理钩子 — 在事务启动之前
$ret = EventReturnHandle(MyEventTrigger('plugins_service_goods_save_handle', [
'params' => &$params, // 引用:可修改
'data' => &$data, // 引用:可修改(影响最终 INSERT/UPDATE
'spec' => &$specifications['data'],
'goods_id' => isset($params['id']) ? intval($params['id']) : 0,
]));
if(isset($ret['code']) && $ret['code'] != 0) {
return $ret; // ← 钩子可提前返回,阻止标准保存
}
```
**关键能力**
1. `$data` 数组通过引用传入,插件可修改后影响最终 `INSERT/UPDATE`
2. 钩子返回 `['code'=>0, 'msg'=>'...', 'data'=>...]` 可阻止标准流程(直接返回)
3.`$params['title']`、`$params['category_ids']` 等字段在钩子调用前已被提取进 `$data`
**两条可行路径**
- **路径A推荐**:在 `$data` 中填入最小必需字段title、category_ids 等),让标准 INSERT 继续执行,插件在钩子内完成票务数据保存
- **路径B**:钩子直接 `Db::startTrans()` 自己处理票务数据,然后 `return ['code'=>0]` 阻止标准流程
---
### Q2-D: 插件视图文件路径可行性
目前 `plugin.json` 中未注册 `plugins_view_admin_goods_save` 钩子(只有 `onOrderPaid`)。需要两步启用:
1. **注册钩子**:在 `plugin.json` 添加:
```json
"backend_hook": {
"plugins_view_admin_goods_save": ["\\app\\plugins\\vr_ticket\\hook\\AdminGoodsSave"]
}
```
2. **实现 AdminGoodsSave.php**:返回 HTML 字符串,注入到 saveinfo.html 的 base tab
**插件视图文件路径**`plugins/vr_ticket/view/admin/goods/ticket_save.html`(如果走模板覆盖方案)
---
### item_type 字段验证
- `goods` 表已存在 `item_type` 字段EventListener.php:129值包括 `'normal'` / `'ticket'` / `'physical'`
- 前台 `app/index/controller/Goods.php:139` 已有 `item_type == 'ticket'` 判断
- **后台 `Goods.php`admin控制器中不存在 `item_type` 判断逻辑**——任务描述中关于此判断存在于后台的说法需要更正
---
## Q1JSON 编辑器复杂度评估FrontendDev
> [待 FrontendDev 填写]
---
## 最终推荐
### 推荐方案:**插件钩子注入 + JSON Schema 编辑器**
**理由**
1. **完全替换方案不可行**`plugins_view_admin_goods_save` 是注入点而非替换点,位于 base tab 的 `<div class="am-form-group">` 内。要完全替换需覆盖核心 `saveinfo.html`,代价是失去 ShopXO 升级兼容性。
2. **注入 + 隐藏策略可行但脆弱**:通过钩子注入 HTML + JS 隐藏标准字段能实现视觉替换,但依赖 DOM 操作,维护成本高。
3. **Save() 数据流完全可控**`plugins_service_goods_save_handle` 钩子在事务前以引用方式接收 `$data`,插件有两条清晰路径(填最小字段走标准流,或自行处理返回)完成数据保存。
4. **与 JSON 编辑器方案互补**:插件钩子注入 ticket 专属表单 + JSON Schema 编辑器处理 `seat_map` 嵌套数据,可以完整覆盖票务商品编辑场景。
### 备选:走插件独立路由
`admin/goods/saveinfo` → 重定向到插件自己的控制器方法(如 `plugins/vr_ticket/admin/goods/Save`),完全独立实现票务编辑器。不经过 ShopXO 的 `Goods::SaveInfo()``Goods::Save()`,干净隔离但需要开发独立的菜单和控制器。
---
## 总结
| 维度 | 完全替换 | 钩子注入(推荐) | 独立路由 |
|------|----------|----------------|----------|
| 实现复杂度 | 高(需覆盖核心模板) | 中 | 高(重写全套) |
| 升级兼容性 | 差 | 好 | 好 |
| 数据保存可控性 | 好 | 好 | 好 |
| 开发工作量 | ~2人天 | ~1人天 | ~3人天 |