[...], // seatKey → {inventory, price, spec, ...} * 'goods_spec_data' => [...], // 场次列表(含最低价) * ] */ public static function GetSeatMap(int $goodsId): array { // 1. 读取 vr_goods_config(数组,取第一项) $vrConfigRaw = Db::name('Goods') ->where('id', $goodsId) ->value('vr_goods_config'); $configs = json_decode($vrConfigRaw ?? '', true); if (empty($configs) || !is_array($configs)) { return ['seatSpecMap' => [], 'goods_spec_data' => []]; } $config = $configs[0]; $templateId = intval($config['template_id'] ?? 0); if ($templateId <= 0) { return ['seatSpecMap' => [], 'goods_spec_data' => []]; } // 2. 获取座位模板(含 rooms[] / sections[] / map[]) $seatTemplate = self::getSeatTemplate($templateId); if (empty($seatTemplate)) { return ['seatSpecMap' => [], 'goods_spec_data' => []]; } // 3. 构建 seatSpecMap(含 inventory,实时读 DB,包含已售) $seatSpecMap = self::buildSeatSpecMap($goodsId, $seatTemplate); // 4. 构建场次列表(从 sessions[] + seatSpecMap 合并最低价) $goodsSpecData = self::buildGoodsSpecData($config['sessions'] ?? [], $seatSpecMap); return [ 'seatSpecMap' => $seatSpecMap, 'goods_spec_data' => $goodsSpecData, ]; } /** * 清除座位图缓存(订单支付成功后调用) * * @param int $goodsId * @return bool */ public static function ClearCache(int $goodsId): bool { return \think\facade\Cache::delete(self::CACHE_KEY_PREFIX . $goodsId); } // ───────────────────────────────────────────────────────── // 私有方法 // ───────────────────────────────────────────────────────── /** * 获取座位模板(走 ShopXO Cache,TTL 60s) * * @param int $templateId * @return array|null */ private static function getSeatTemplate(int $templateId) { $cacheKey = self::CACHE_KEY_PREFIX . $templateId; $cached = \think\facade\Cache::get($cacheKey); if ($cached !== null) { return $cached; } $row = Db::name('vr_seat_templates')->find($templateId); if (empty($row)) { return null; } $seatMap = json_decode($row['seat_map'] ?? '{}', true); if (empty($seatMap)) { return null; } // 缓存(TTL = self::CACHE_TTL) \think\facade\Cache::set($cacheKey, $seatMap, self::CACHE_TTL); return $seatMap; } /** * 构建座位规格映射表(含 inventory,实时读 DB) * * 遍历所有 GoodsSpecBase(含 inventory=0 的已售座位), * 与 GoodsSpecType + GoodsSpecValue 关联, * 输出 seatSpecMap。 * * @param int $goodsId * @param array $seatTemplate seat_map JSON(已解析) * @return array seatSpecMap */ private static function buildSeatSpecMap(int $goodsId, array $seatTemplate): array { $seatSpecMap = []; // 1. 查询当前商品所有 GoodsSpecBase(不过滤 inventory,获取所有座位含已售) $specs = Db::name('GoodsSpecBase') ->where('goods_id', $goodsId) ->select() ->toArray(); if (empty($specs)) { return $seatSpecMap; } // 2. 查询 GoodsSpecType 获取维度映射(name → index) $specTypes = Db::name('GoodsSpecType') ->where('goods_id', $goodsId) ->order('id', 'asc') ->select() ->toArray(); $dimIndexByName = []; $dimValuesByName = []; // name → [value1, value2, ...] foreach ($specTypes as $idx => $type) { $dimName = $type['name'] ?? ''; if (!empty($dimName)) { $dimIndexByName[$dimName] = $idx; $values = json_decode($type['value'] ?? '[]', true); $dimValuesByName[$dimName] = []; foreach ($values as $v) { if (isset($v['name'])) { $dimValuesByName[$dimName][] = $v['name']; } } } } // 3. 查询每个 spec_base_id 对应的 GoodsSpecValue $specBaseIds = array_column($specs, 'id'); $specValues = Db::name('GoodsSpecValue') ->whereIn('goods_spec_base_id', $specBaseIds) ->select() ->toArray(); // 4. 按 spec_base_id 分组,通过值匹配找到维度名 $specByBaseId = []; foreach ($specValues as $sv) { $baseId = $sv['goods_spec_base_id']; $value = $sv['value'] ?? ''; $dimName = ''; foreach ($dimValuesByName as $name => $values) { if (in_array($value, $values)) { $dimName = $name; break; } } $specByBaseId[$baseId][] = [ 'type' => $dimName, 'value' => $value, ]; } // 5. 解析座位模板中的 room 信息(用于提取 rowLabel, colNum, section 等) $rooms = $seatTemplate['rooms'] ?? []; $roomSeatInfo = []; // roomId → [rowLabel_colNum → [...]] foreach ($rooms as $rIdx => $room) { $roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx); $sections = $room['sections'] ?? []; $map = $room['map'] ?? []; $seatsData = $room['seats'] ?? []; foreach ($map as $rowIndex => $rowStr) { $rowLabel = chr(65 + $rowIndex); $chars = preg_split('//u', $rowStr, -1, PREG_SPLIT_NO_EMPTY); foreach ($chars as $colIndex => $char) { if ($char === '_' || $char === '-' || !isset($seatsData[$char])) { continue; } $colNum = $colIndex + 1; $sectionInfo = null; foreach ($sections as $sec) { if (($sec['char'] ?? '') === $char) { $sectionInfo = $sec; break; } } $roomSeatInfo[$roomId][$rowLabel . '_' . $colNum] = [ 'rowLabel' => $rowLabel, 'colNum' => $colNum, 'section' => $sectionInfo, 'char' => $char, ]; } } } // 6. 构建 seatSpecMap:seatKey → 完整规格 foreach ($specs as $spec) { $extends = json_decode($spec['extends'] ?? '{}', true); $seatKey = $extends['seat_key'] ?? ''; if (empty($seatKey)) continue; // 解析 seatKey 格式:roomId_rowLabel_colNum $parts = explode('_', $seatKey); if (count($parts) < 3) continue; $roomId = $parts[0]; $rowLabel = $parts[1]; $colNum = intval($parts[2]); // 提取各维度值 $venueName = ''; $sectionName = ''; $seatName = ''; $sessionName = ''; $roomName = ''; foreach ($specByBaseId[$spec['id']] ?? [] as $specItem) { $specType = $specItem['type'] ?? ''; $specVal = $specItem['value'] ?? ''; switch ($specType) { case '$vr-场次': $sessionName = $specVal; break; case '$vr-场馆': $venueName = $specVal; break; case '$vr-演播室': $roomName = $specVal; break; case '$vr-分区': $sectionName = $specVal; break; case '$vr-座位号': $seatName = $specVal; break; } } $seatMeta = $roomSeatInfo[$roomId][$rowLabel . '_' . $colNum] ?? [ 'rowLabel' => $rowLabel, 'colNum' => $colNum, 'section' => null, 'char' => '', ]; $seatSpecMap[$seatKey] = [ 'spec_base_id' => intval($spec['id']), 'price' => floatval($spec['price']), 'inventory' => intval($spec['inventory']), // ← 关键字段(0=已售) 'spec' => $specByBaseId[$spec['id']] ?? [], 'rowLabel' => $seatMeta['rowLabel'], 'colNum' => $seatMeta['colNum'], 'roomId' => $roomId, 'roomName' => $roomName, 'section' => $seatMeta['section'], 'venueName' => $venueName, 'sectionName' => $sectionName, 'seatName' => $seatName, 'sessionName' => $sessionName, ]; } return $seatSpecMap; } /** * 构建 goods_spec_data(场次列表,含最低价) * * @param array $sessions vr_goods_config.sessions[] * @param array $seatSpecMap * @return array */ private static function buildGoodsSpecData(array $sessions, array $seatSpecMap): array { if (empty($sessions)) { return []; } $result = []; foreach ($sessions as $session) { $specName = ($session['start'] ?? '') . '-' . ($session['end'] ?? ''); $minPrice = PHP_FLOAT_MAX; // 从 seatSpecMap 中找该场次的最低价 foreach ($seatSpecMap as $info) { if (($info['sessionName'] ?? '') === $specName) { $p = $info['price'] ?? PHP_FLOAT_MAX; if ($p < $minPrice) { $minPrice = $p; } } } $result[] = [ 'spec_name' => $specName, 'price' => $minPrice < PHP_FLOAT_MAX ? $minPrice : 0, 'start' => $session['start'] ?? '', 'end' => $session['end'] ?? '', ]; } return $result; } }