From 2311f17b90a2f335624cef1a30f1ad59d4d5888a Mon Sep 17 00:00:00 2001 From: Council Date: Mon, 20 Apr 2026 22:42:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(vr=5Fticket):=20=E4=BF=AE=E5=A4=8D=E5=B9=BD?= =?UTF-8?q?=E7=81=B5=20spec=20=E9=97=AE=E9=A2=98=20(Issue=20#15=20+=20#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #15 — AdminGoodsSaveHandle.php: 1. 读取优先级调换:data['vr_goods_config'] 优先,DB 兜底(从源头避免脏数据) 2. 模板不存在时 unset($configs[$i]) 移除无效 config 块(防御层) 3. array_values 重排索引 + 写回前判空(防御层) Issue #16 — SeatSkuService.php GetGoodsViewData: 1. 多模板模式:遍历所有配置块过滤有效块 2. 模板不存在时清理无效块并写回有效配置(而非覆盖) 参考:reports/GHOST_SPEC_INVESTIGATION_REPORT.md 参考:docs/PLAN_GHOST_SPEC_FIX.md --- .../vr_ticket/hook/AdminGoodsSaveHandle.php | 24 ++++++---- .../vr_ticket/service/SeatSkuService.php | 47 +++++++++++++------ 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php b/shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php index 5638af7..d8d8e1a 100644 --- a/shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php +++ b/shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php @@ -58,11 +58,11 @@ class AdminGoodsSaveHandle if ($goodsId > 0 && ($data['item_type'] ?? '') === 'ticket') { // 直接从数据库读 vr_goods_config(全量查询,不加 field 限制,避免 ThinkPHP 软删除过滤导致查不到) - $goodsRow = Db::name('Goods')->find($goodsId); - $rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : ''; - // 如果 DB 里没有( goodsRow 为空或 vr_goods_config 字段为空),fallback 到 params[data] - if (empty($rawConfig)) { - $rawConfig = $data['vr_goods_config'] ?? ''; + // 前端已过滤无效 template_id,优先使用 data。若无前端数据再 fallback 到 DB + if (!empty($data['vr_goods_config'])) { + $rawConfig = $data['vr_goods_config']; + } else { + $rawConfig = is_array($goodsRow) ? ($goodsRow['vr_goods_config'] ?? '') : ''; } if (!empty($rawConfig)) { @@ -83,9 +83,10 @@ class AdminGoodsSaveHandle $template = Db::name('vr_seat_templates')->find($templateId); // 模板不存在时(硬删除场景): - // - 跳过 snapshot 重建,保持 template_id=null 状态 + // - 移除无效 config 块,避免脏数据写回 // - 前端下次打开时将看到选单为空,用户可重新选择或清空配置 if (empty($template)) { + unset($configs[$i]); // 移除无效 config 块 continue; } @@ -143,11 +144,14 @@ class AdminGoodsSaveHandle } } unset($config); // 解除引用,避免后续误改 + $configs = array_values($configs); // 重排数组索引 - // 将填充后的完整 config 写回 goods 表 - Db::name('Goods')->where('id', $goodsId)->update([ - 'vr_goods_config' => json_encode($configs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), - ]); + // 将填充后的完整 config 写回 goods 表(仅在有有效配置时) + if (!empty($configs)) { + Db::name('Goods')->where('id', $goodsId)->update([ + 'vr_goods_config' => json_encode($configs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + ]); + } // a) 清空原生规格数据 —— 避免列偏移 Db::name('GoodsSpecType')->where('goods_id', $goodsId)->delete(); diff --git a/shopxo/app/plugins/vr_ticket/service/SeatSkuService.php b/shopxo/app/plugins/vr_ticket/service/SeatSkuService.php index 11d1c24..fc7c978 100644 --- a/shopxo/app/plugins/vr_ticket/service/SeatSkuService.php +++ b/shopxo/app/plugins/vr_ticket/service/SeatSkuService.php @@ -365,8 +365,22 @@ class SeatSkuService extends BaseService return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null]; } - // 取第一个配置块(单模板模式) - $config = $vrGoodsConfig[0]; + // 过滤有效配置块(多模板模式) + $validConfigs = []; + foreach ($vrGoodsConfig as $cfg) { + $tid = intval($cfg['template_id'] ?? 0); + if ($tid <= 0) continue; + $tpl = \think\facade\Db::name(self::table('seat_templates'))->where('id', $tid)->find(); + if (!empty($tpl)) { + $validConfigs[] = $cfg; + } + } + if (empty($validConfigs)) { + return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null]; + } + + // 取第一个有效配置块用于前端展示 + $config = $validConfigs[0]; $templateId = intval($config['template_id'] ?? 0); if ($templateId <= 0) { return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null]; @@ -377,20 +391,23 @@ class SeatSkuService extends BaseService ->where('id', $templateId) ->find(); - // 模板不存在时(硬删除场景): - // - 将 template_id 置 null,让前端选单显示为空 - // - 同时清掉 template_snapshot,下次保存时整块 config 干净地失效 + // 模板不存在时(硬删除场景):清理无效配置块,写回有效配置 if (empty($seatTemplate)) { - $config['template_id'] = null; - $config['template_snapshot'] = null; - \think\facade\Db::name('Goods')->where('id', $goodsId)->update([ - 'vr_goods_config' => json_encode([$config], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), - ]); - return [ - 'vr_seat_template' => null, - 'goods_spec_data' => [], - 'goods_config' => $config, - ]; + $validConfigs = array_filter($validConfigs, function ($cfg) use ($templateId) { + $tid = intval($cfg['template_id'] ?? 0); + if ($tid <= 0 || $tid === $templateId) return false; + $tpl = \think\facade\Db::name(self::table('seat_templates'))->where('id', $tid)->find(); + return !empty($tpl); + }); + $validConfigs = array_values($validConfigs); + if (!empty($validConfigs)) { + \think\facade\Db::name('Goods')->where('id', $goodsId)->update([ + 'vr_goods_config' => json_encode($validConfigs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + ]); + } else { + \think\facade\Db::name('Goods')->where('id', $goodsId)->update(['vr_goods_config' => '']); + } + return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null]; } // 解码 seat_map JSON(存储时是 JSON 字符串)