fix(vr_ticket): 修复幽灵 spec 问题 (Issue #15 + #16)

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
council/ProductManager
Council 2026-04-20 22:42:41 +08:00
parent 44120a7e2c
commit 2311f17b90
2 changed files with 46 additions and 25 deletions

View File

@ -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();

View File

@ -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 字符串)