9.3 KiB
9.3 KiB
VR-ShopXO-Plugin 删除逻辑审查报告
审查时间: 2026-04-20 审查人: 西莉雅审查助手 审查文件:
shopxo/app/plugins/vr_ticket/admin/Admin.phpshopxo/app/plugins/vr_ticket/view/venue/list.html
一、发现的问题列表
🔴 问题 1:SeatTemplateDelete 中使用了不存在的 is_delete 列(严重)
| 属性 | 值 |
|---|---|
| 文件 | Admin.php |
| 方法 | SeatTemplateDelete (第 227 行) |
| 行号 | 第 249-251 行 |
| 问题 | Goods 查询使用 is_delete = 0,但 ShopXO 的 Goods 表使用 is_delete_time 进行软删除判断 |
// ❌ 错误代码(第 249-251 行)
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete', 0) // ← ShopXO 没有 is_delete 列!
->find();
错误信息:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'is_delete' in 'where clause'
根本原因: 复制粘贴自 ShopXO 原型代码时遗留的错误字段名。ShopXO 的商品软删除使用 is_delete_time(时间戳),is_delete 列根本不存在。
🔴 问题 2:VenueDelete 中同样使用了不存在的 is_delete 列
| 属性 | 值 |
|---|---|
| 文件 | Admin.php |
| 方法 | VenueDelete (第 857 行) |
| 行号 | 第 885-889 行 |
| 问题 | 与 SeatTemplateDelete 相同的问题 |
// ❌ 错误代码(第 885-889 行)
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete', 0) // ← 同样错误!
->find();
🟡 问题 3:list.html 残留了旧的 custom JS 删除弹窗代码
| 属性 | 值 |
|---|---|
| 文件 | view/venue/list.html |
| 行号 | 第 137-168 |
| 问题 | 删除按钮已改用 submit-ajax(正确),但旧的 custom JS 弹窗和 .btn-open-delete-confirm 处理器仍残留,造成代码污染 |
删除按钮已正确更新为:
<!-- ✅ 正确:已使用 submit-ajax -->
<button class="am-btn am-btn-danger am-btn-xs am-radius am-margin-left-xs submit-ajax"
data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'VenueDelete')}}"
data-id="{{$v.id}}"
data-value="hard"
data-view="reload"
data-msg="确定要删除此场馆?删除后关联商品的场馆信息将被自动清除。">
<i class="am-icon-trash-o"></i> 删除
</button>
但页面底部仍残留:
#venue-confirm-delete-modal弹窗 HTML(第 137-151 行).btn-open-delete-confirm点击处理器(第 155-158 行).btn-do-real-delete弹窗确认处理器(第 160-175 行)
🟢 确认正常:方法命名问题(非 bug)
| 观察 | 说明 |
|---|---|
vr_seat_templates 表 |
实际上存储的就是"场馆/场地模板"数据,不是"座位模板" |
VenueDelete / SeatTemplateDelete |
两个方法名不同,但都操作同一张表 vr_seat_templates |
| 结论 | 表名和方法名的命名不一致是历史遗留的设计问题,但不影响功能 |
二、根本原因分析
为什么会发生?
- 复制粘贴错误 — 两个方法从 ShopXO 原型代码复制过来时,
Goods表的软删除字段用了错误的名称is_delete,而 ShopXO 实际使用is_delete_time - 残留代码未清理 — 前端删除按钮从 custom JS 方式迁移到
submit-ajax后,旧的弹窗 HTML 和 JS 事件处理器没有一起移除 - 缺少 Code Review — 没有在 PR 阶段发现字段名错误
软删除 vs 硬删除逻辑梳理
| 方法 | 软删除触发条件 | 硬删除触发条件 | 表 |
|---|---|---|---|
SeatTemplateDelete |
hard_delete=0(默认) |
hard_delete=1 或 value=hard |
vr_seat_templates |
VenueDelete |
hard_delete=0(默认) |
hard_delete=1 或 value=hard |
vr_seat_templates |
VerifierDelete |
仅软删除(无硬删除选项) | 无 | vr_verifiers |
注意: VerifierDelete 只有软删除(设置 status=0),这是正确的业务设计。
三、修复代码(diff 格式)
修复 1:SeatTemplateDelete 的 is_delete 错误
文件: Admin.php 第 249-251 行
if ($hardDelete) {
- $goods = \think\facade\Db::name('Goods')
- ->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
- ->where('is_delete', 0)
+ // 检查是否有关联商品(ShopXO 使用 is_delete_time 做软删除判断)
+ $goods = \think\facade\Db::name('Goods')
+ ->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
+ ->where('is_delete_time', 0)
->find();
修复 2:VenueDelete 的 is_delete 错误
文件: Admin.php 第 885-889 行
if ($hardDelete) {
- $goods = \think\facade\Db::name('Goods')
- ->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
- ->where('is_delete', 0)
+ // 检查是否有关联商品(ShopXO 使用 is_delete_time 做软删除判断)
+ $goods = \think\facade\Db::name('Goods')
+ ->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
+ ->where('is_delete_time', 0)
->find();
修复 3:清理 list.html 残留的 old custom JS 代码
文件: view/venue/list.html 第 137-175 行
删除以下残留代码块:
- <!-- 全局删除确认弹窗(单个,供所有行共用) -->
- <div class="am-modal am-modal-confirm" id="venue-confirm-delete-modal">
- <div class="am-modal-dialog">
- ...
- </div>
- </div>
-
- <script>
- $(function() {
- // 删除按钮:打开弹窗并记录当前行 ID
- $(document).on('click', '.btn-open-delete-confirm', function() {
- ...
- });
- // 弹窗确认删除:构造 hard_delete=1 参数并提交
- $(document).on('click', '.btn-do-real-delete', function() {
- ...
- });
- });
- </script>
四、验证步骤
Step 1: 本地验证
# 1. 应用修复
# - Admin.php 第 249-251 行:is_delete → is_delete_time
# - Admin.php 第 885-889 行:is_delete → is_delete_time
# - list.html 删除残留的 modal 和 JS 代码
# 2. 清除 ShopXO 缓存
cd /path/to/shopxo
php think clear
Step 2: 功能测试
测试用例 1:场馆硬删除(有关联商品)
- 创建一个商品,关联到某场馆
- 进入场馆列表,点击"删除"按钮
- 确认弹窗出现,点击"确认删除"
- 预期: 删除成功,页面 reload,商品
vr_goods_config中的场馆信息被清除 - 验证 SQL 无报错
测试用例 2:场馆硬删除(无关联商品)
- 找一个没有任何商品关联的场馆
- 点击删除 → 确认
- 预期: 删除成功,场馆记录从表中移除
测试用例 3:场馆软删除(禁用)
- 点击"禁用"按钮
- 预期: 场馆
status变为 0,页面 reload 后显示"禁用"状态 - 验证: 禁用后,删除按钮仍然显示(不再因条件判断消失)
测试用例 4:场馆启用
- 在已禁用的场馆行点击"启用"
- 预期:
status变为 1,页面 reload
测试用例 5:Verifier 删除
- 进入核销员列表,点击某核销员的删除
- 预期: 软删除,
status变为 0,无 SQL 报错
Step 3: 数据库验证
-- 验证硬删除后商品表中的 vr_goods_config 已被清理
SELECT id, name, vr_goods_config FROM vrt_goods WHERE vr_goods_config LIKE '%template_id":X%';
-- 验证场馆软删除后 status 正确
SELECT id, name, status FROM vrt_vr_seat_templates WHERE id = X;
五、修复状态汇总
| # | 问题 | 严重度 | 状态 |
|---|---|---|---|
| 1 | SeatTemplateDelete 使用错误的 is_delete 列 |
🔴 严重 | 待修复 |
| 2 | VenueDelete 使用错误的 is_delete 列 |
🔴 严重 | 待修复 |
| 3 | list.html 残留 old custom JS 代码 |
🟡 轻微 | 待清理 |
注意: 大头通过 antigravity 手动修复了前端删除按钮和 VenueDelete 的 value=hard 参数支持,这些部分已正确。但 is_delete → is_delete_time 的修复仍需应用。
六、完整修复后代码对照
SeatTemplateDelete 硬删除块(修复后)
if ($hardDelete) {
// 检查是否有关联商品(ShopXO 使用 is_delete_time 做软删除判断)
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete_time', 0)
->find();
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->delete();
// ... audit log
}
VenueDelete 硬删除块(修复后)
if ($hardDelete) {
// 检查是否有关联商品(ShopXO 使用 is_delete_time 做软删除判断)
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete_time', 0)
->find();
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->delete();
// ... audit log
}