2026-04-15 11:58:48 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
|
|
|
* VR票务插件 - 座位 SKU 服务
|
|
|
|
|
|
*
|
2026-04-18 21:46:37 +00:00
|
|
|
|
* ShopXO 规格表结构:
|
|
|
|
|
|
* GoodsSpecType : id, goods_id, name, value(JSON数组), add_time
|
|
|
|
|
|
* GoodsSpecBase : id, goods_id, price, original_price, inventory, ...
|
|
|
|
|
|
* GoodsSpecValue : id, goods_id, goods_spec_base_id, value, md5_key, add_time
|
|
|
|
|
|
*
|
|
|
|
|
|
* 列对应关系由 GoodsEditSpecifications 决定:
|
|
|
|
|
|
* 它把每个 GoodsSpecValue.value 逐个在 GoodsSpecType.value(JSON) 的 name 字段中搜索,
|
|
|
|
|
|
* 匹配成功才显示在对应列。
|
|
|
|
|
|
* 因此:GoodsSpecType.value JSON 里必须包含我们写入的所有 value 字符串。
|
2026-04-15 11:58:48 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @package vr_ticket\service
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
namespace app\plugins\vr_ticket\service;
|
|
|
|
|
|
|
|
|
|
|
|
use think\facade\Db;
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
|
|
|
|
|
class SeatSkuService extends BaseService
|
|
|
|
|
|
{
|
2026-04-18 21:46:37 +00:00
|
|
|
|
const BATCH_SIZE = 200;
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-18 21:46:37 +00:00
|
|
|
|
* VR 规格维度名(顺序固定)
|
2026-04-15 11:58:48 +00:00
|
|
|
|
*/
|
2026-04-18 21:46:37 +00:00
|
|
|
|
const SPEC_DIMS = ['$vr-场馆', '$vr-分区', '$vr-座位号', '$vr-场次'];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 批量生成座位级 SKU
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $goodsId
|
|
|
|
|
|
* @param int $seatTemplateId
|
|
|
|
|
|
* @param array $selectedRooms 要生成的厅 id 列表,空=全部
|
|
|
|
|
|
* @param array $selectedSections 按 roomId 为 key 的分区 char 数组,空=全部
|
|
|
|
|
|
* @param array $sessions 场次数组 e.g. [["start"=>"08:00","end"=>"23:59"]]
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function BatchGenerate(
|
|
|
|
|
|
int $goodsId,
|
|
|
|
|
|
int $seatTemplateId,
|
|
|
|
|
|
array $selectedRooms = [],
|
|
|
|
|
|
array $selectedSections = [],
|
|
|
|
|
|
array $sessions = []
|
|
|
|
|
|
): array {
|
2026-04-15 11:58:48 +00:00
|
|
|
|
if ($goodsId <= 0 || $seatTemplateId <= 0) {
|
|
|
|
|
|
return ['code' => -1, 'msg' => '参数错误:goodsId 或 seatTemplateId 无效'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 加载座位模板
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$template = Db::name(self::table('seat_templates'))
|
2026-04-15 11:58:48 +00:00
|
|
|
|
->where('id', $seatTemplateId)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
if (empty($template)) {
|
|
|
|
|
|
return ['code' => -2, 'msg' => "座位模板 {$seatTemplateId} 不存在"];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 解析 seat_map
|
|
|
|
|
|
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$rooms = $seatMap['rooms'] ?? [];
|
2026-04-16 16:46:00 +00:00
|
|
|
|
if (empty($rooms)) {
|
2026-04-18 21:46:37 +00:00
|
|
|
|
return ['code' => -3, 'msg' => '座位模板 seat_map 无效(rooms 为空)'];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 使用模板表的短名称
|
|
|
|
|
|
$venueName = $template['name'] ?? '未命名场馆';
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 场次处理(默认兜底)
|
|
|
|
|
|
if (empty($sessions)) {
|
|
|
|
|
|
$sessions = [['start' => '08:00', 'end' => '23:59']];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$sessionStrings = [];
|
|
|
|
|
|
foreach ($sessions as $s) {
|
|
|
|
|
|
$start = is_array($s) ? ($s['start'] ?? '08:00') : '08:00';
|
|
|
|
|
|
$end = is_array($s) ? ($s['end'] ?? '23:59') : '23:59';
|
|
|
|
|
|
$sessionStr = "{$start}-{$end}";
|
|
|
|
|
|
if (!in_array($sessionStr, $sessionStrings)) {
|
|
|
|
|
|
$sessionStrings[] = $sessionStr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 确保 4 个 VR spec type 维度存在,同时收集要写入的所有唯一 value
|
|
|
|
|
|
// 我们先遍历座位图,收集全部规格值,再一次性写入 type.value JSON
|
|
|
|
|
|
$selectedRooms = array_values(array_filter($selectedRooms));
|
|
|
|
|
|
$selectedSections = array_filter($selectedSections);
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 按维度收集唯一值(用 有序列表 + 去重)
|
|
|
|
|
|
$dimUniqueValues = [
|
|
|
|
|
|
'$vr-场馆' => [],
|
|
|
|
|
|
'$vr-分区' => [],
|
|
|
|
|
|
'$vr-座位号' => [],
|
|
|
|
|
|
'$vr-场次' => [],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 遍历地图,收集所有座位信息
|
2026-04-16 16:46:00 +00:00
|
|
|
|
$seatsToInsert = [];
|
2026-04-18 21:46:37 +00:00
|
|
|
|
foreach ($rooms as $rIdx => $room) {
|
|
|
|
|
|
// 与前端 PHP 预处理保持一致:id 缺失时用 'room_{index}'
|
|
|
|
|
|
$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
|
2026-04-16 16:46:00 +00:00
|
|
|
|
$roomName = $room['name'] ?? '默认放映室';
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
|
|
|
|
|
if (!empty($selectedRooms) && !in_array($roomId, $selectedRooms)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// char → price 映射
|
2026-04-16 16:46:00 +00:00
|
|
|
|
$sectionPrices = [];
|
2026-04-18 21:46:37 +00:00
|
|
|
|
foreach (($room['sections'] ?? []) as $sec) {
|
|
|
|
|
|
if (!empty($sec['char'])) {
|
|
|
|
|
|
$sectionPrices[$sec['char']] = floatval($sec['price'] ?? 0);
|
|
|
|
|
|
}
|
2026-04-16 16:46:00 +00:00
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
|
|
|
|
|
$map = $room['map'] ?? [];
|
2026-04-16 16:46:00 +00:00
|
|
|
|
$seatsData = $room['seats'] ?? [];
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
2026-04-16 16:46:00 +00:00
|
|
|
|
foreach ($map as $rowIndex => $rowStr) {
|
|
|
|
|
|
$rowLabel = chr(65 + $rowIndex);
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$chars = mb_str_split($rowStr);
|
|
|
|
|
|
|
2026-04-16 16:46:00 +00:00
|
|
|
|
foreach ($chars as $colIndex => $char) {
|
|
|
|
|
|
if ($char === '_' || $char === '-' || !isset($seatsData[$char])) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
|
|
|
|
|
if (!empty($selectedSections[$roomId])
|
|
|
|
|
|
&& !in_array($char, $selectedSections[$roomId])) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 价格三级 fallback
|
|
|
|
|
|
$seatInfo = $seatsData[$char];
|
2026-04-16 16:46:00 +00:00
|
|
|
|
$seatPrice = floatval($seatInfo['price'] ?? 0);
|
2026-04-18 21:46:37 +00:00
|
|
|
|
if ($seatPrice == 0 && isset($sectionPrices[$char])) {
|
|
|
|
|
|
$seatPrice = $sectionPrices[$char];
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($seatPrice == 0) {
|
|
|
|
|
|
foreach (($room['sections'] ?? []) as $sec) {
|
|
|
|
|
|
if (($sec['char'] ?? '') === $char) {
|
|
|
|
|
|
$seatPrice = floatval($sec['price'] ?? 0);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$col = $colIndex + 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 维度值字符串(确保非空)
|
|
|
|
|
|
$val_venue = $venueName;
|
|
|
|
|
|
$val_section = $venueName . '-' . $roomName . '-' . $char;
|
|
|
|
|
|
$val_seat = $venueName . '-' . $roomName . '-' . $char . '-' . $rowLabel . $col;
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($sessionStrings as $sessionStr) {
|
|
|
|
|
|
$seatId = $roomId . '_' . $rowLabel . '_' . $col . '_' . md5($sessionStr);
|
|
|
|
|
|
|
|
|
|
|
|
$seatsToInsert[$seatId] = [
|
|
|
|
|
|
'price' => $seatPrice,
|
|
|
|
|
|
'spec_values' => [
|
|
|
|
|
|
$val_venue,
|
|
|
|
|
|
$val_section,
|
|
|
|
|
|
$val_seat,
|
|
|
|
|
|
$sessionStr,
|
|
|
|
|
|
],
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 收集唯一维度值(保持首次出现顺序)
|
|
|
|
|
|
if (!in_array($val_venue, $dimUniqueValues['$vr-场馆'])) {
|
|
|
|
|
|
$dimUniqueValues['$vr-场馆'][] = $val_venue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!in_array($val_section, $dimUniqueValues['$vr-分区'])) {
|
|
|
|
|
|
$dimUniqueValues['$vr-分区'][] = $val_section;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!in_array($val_seat, $dimUniqueValues['$vr-座位号'])) {
|
|
|
|
|
|
$dimUniqueValues['$vr-座位号'][] = $val_seat;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!in_array($sessionStr, $dimUniqueValues['$vr-场次'])) {
|
|
|
|
|
|
$dimUniqueValues['$vr-场次'][] = $sessionStr;
|
|
|
|
|
|
}
|
2026-04-16 16:46:00 +00:00
|
|
|
|
}
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
2026-04-16 16:46:00 +00:00
|
|
|
|
if (empty($seatsToInsert)) {
|
2026-04-18 21:46:37 +00:00
|
|
|
|
return ['code' => -4, 'msg' => '无有效座位可生成'];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 6. 保证 4 个 VR spec type 存在,并把已收集的 value 写入 type.value JSON
|
|
|
|
|
|
// 这样 GoodsEditSpecifications 的 name 匹配才能命中每条 GoodsSpecValue
|
|
|
|
|
|
self::ensureAndFillVrSpecTypes($goodsId, $dimUniqueValues);
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 7. 写入 GoodsSpecBase + GoodsSpecValue
|
|
|
|
|
|
$now = time();
|
2026-04-15 11:58:48 +00:00
|
|
|
|
$generatedCount = 0;
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$valueBatch = [];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
foreach ($seatsToInsert as $seatId => $s) {
|
|
|
|
|
|
$baseId = Db::name('GoodsSpecBase')->insertGetId([
|
|
|
|
|
|
'goods_id' => $goodsId,
|
|
|
|
|
|
'price' => $s['price'],
|
|
|
|
|
|
'original_price' => 0,
|
|
|
|
|
|
'inventory' => 1,
|
|
|
|
|
|
'buy_min_number' => 1,
|
|
|
|
|
|
'buy_max_number' => 1,
|
|
|
|
|
|
'weight' => 0,
|
|
|
|
|
|
'volume' => 0,
|
|
|
|
|
|
'coding' => '',
|
|
|
|
|
|
'barcode' => '',
|
|
|
|
|
|
'add_time' => $now,
|
|
|
|
|
|
]);
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
if (!$baseId) {
|
|
|
|
|
|
throw new \Exception("GoodsSpecBase 写入失败 (seat: {$seatId})");
|
|
|
|
|
|
}
|
2026-04-16 16:46:00 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 4 条 GoodsSpecValue,每条对应一个维度
|
|
|
|
|
|
foreach ($s['spec_values'] as $specVal) {
|
|
|
|
|
|
$valueBatch[] = [
|
|
|
|
|
|
'goods_id' => $goodsId,
|
|
|
|
|
|
'goods_spec_base_id' => $baseId,
|
|
|
|
|
|
'value' => (string)$specVal,
|
|
|
|
|
|
'md5_key' => md5((string)$specVal),
|
|
|
|
|
|
'add_time' => $now,
|
|
|
|
|
|
];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$generatedCount++;
|
|
|
|
|
|
|
|
|
|
|
|
if (count($valueBatch) >= self::BATCH_SIZE) {
|
|
|
|
|
|
Db::name('GoodsSpecValue')->insertAll($valueBatch);
|
|
|
|
|
|
$valueBatch = [];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
}
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
if (!empty($valueBatch)) {
|
|
|
|
|
|
Db::name('GoodsSpecValue')->insertAll($valueBatch);
|
2026-04-16 16:46:00 +00:00
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'code' => 0,
|
|
|
|
|
|
'msg' => '生成成功',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'total' => count($seatsToInsert),
|
|
|
|
|
|
'generated' => $generatedCount,
|
|
|
|
|
|
],
|
|
|
|
|
|
];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 幂等确保 4 个 VR 维度存在,并把本次收集的所有唯一值合并写入 type.value JSON
|
|
|
|
|
|
*
|
|
|
|
|
|
* 关键:GoodsEditSpecifications 通过 type.value JSON 里的 name 做匹配,
|
|
|
|
|
|
* 所以每条 GoodsSpecValue.value 都必须在对应 type.value 的某个 name 里找到。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $goodsId
|
|
|
|
|
|
* @param array $dimUniqueValues ['$vr-场馆' => [...], '$vr-分区' => [...], ...]
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function ensureAndFillVrSpecTypes(int $goodsId, array $dimUniqueValues = []): void
|
2026-04-15 11:58:48 +00:00
|
|
|
|
{
|
|
|
|
|
|
$now = time();
|
|
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 读取已存在的 VR 维度(按顺序)
|
|
|
|
|
|
$existing = Db::name('GoodsSpecType')
|
2026-04-16 16:46:00 +00:00
|
|
|
|
->where('goods_id', $goodsId)
|
2026-04-18 21:46:37 +00:00
|
|
|
|
->whereIn('name', self::SPEC_DIMS)
|
|
|
|
|
|
->order('id', 'asc')
|
|
|
|
|
|
->select()
|
|
|
|
|
|
->toArray();
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
$existingByName = array_column($existing, null, 'name');
|
|
|
|
|
|
|
|
|
|
|
|
foreach (self::SPEC_DIMS as $dimName) {
|
|
|
|
|
|
// 构建该维度的 value JSON(把所有唯一值合并,含已有值)
|
|
|
|
|
|
$newItems = [];
|
|
|
|
|
|
$existingItems = [];
|
|
|
|
|
|
if (isset($existingByName[$dimName])) {
|
|
|
|
|
|
$existingItems = json_decode($existingByName[$dimName]['value'] ?? '[]', true);
|
|
|
|
|
|
if (!is_array($existingItems)) $existingItems = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 把现有 JSON 中的 name 提取出来
|
|
|
|
|
|
$existingNames = array_column($existingItems, 'name');
|
2026-04-16 16:46:00 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
// 合并本次新增的值
|
|
|
|
|
|
$toAdd = $dimUniqueValues[$dimName] ?? [];
|
|
|
|
|
|
foreach ($toAdd as $val) {
|
|
|
|
|
|
if (!in_array($val, $existingNames)) {
|
|
|
|
|
|
$existingItems[] = ['name' => (string)$val, 'images' => ''];
|
|
|
|
|
|
$existingNames[] = $val;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$valueJson = json_encode($existingItems, JSON_UNESCAPED_UNICODE);
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($existingByName[$dimName])) {
|
|
|
|
|
|
// 更新已有维度的 value JSON
|
|
|
|
|
|
Db::name('GoodsSpecType')
|
|
|
|
|
|
->where('id', $existingByName[$dimName]['id'])
|
|
|
|
|
|
->update(['value' => $valueJson]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 插入缺失维度(保持 SPEC_DIMS 顺序,ID 递增)
|
|
|
|
|
|
Db::name('GoodsSpecType')->insert([
|
2026-04-15 11:58:48 +00:00
|
|
|
|
'goods_id' => $goodsId,
|
2026-04-18 21:46:37 +00:00
|
|
|
|
'name' => $dimName,
|
|
|
|
|
|
'value' => $valueJson,
|
2026-04-15 11:58:48 +00:00
|
|
|
|
'add_time' => $now,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-18 21:46:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重新计算商品基础信息(价格区间、总库存)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function refreshGoodsBase(int $goodsId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$bases = Db::name('GoodsSpecBase')
|
|
|
|
|
|
->where('goods_id', $goodsId)
|
|
|
|
|
|
->select()
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($bases)) {
|
|
|
|
|
|
return ['code' => -1, 'msg' => '无 GoodsSpecBase'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$prices = array_column($bases, 'price');
|
|
|
|
|
|
$minPrice = min($prices);
|
|
|
|
|
|
$maxPrice = max($prices);
|
|
|
|
|
|
$inventory = array_sum(array_column($bases, 'inventory'));
|
|
|
|
|
|
|
|
|
|
|
|
$priceDisplay = ($minPrice != $maxPrice && $maxPrice > 0)
|
|
|
|
|
|
? $minPrice . '-' . $maxPrice : $minPrice;
|
|
|
|
|
|
|
|
|
|
|
|
Db::name('Goods')->where('id', $goodsId)->update([
|
|
|
|
|
|
'min_price' => $minPrice,
|
|
|
|
|
|
'max_price' => $maxPrice,
|
|
|
|
|
|
'price' => $priceDisplay,
|
|
|
|
|
|
'min_original_price' => 0,
|
|
|
|
|
|
'max_original_price' => 0,
|
|
|
|
|
|
'original_price' => 0,
|
|
|
|
|
|
'inventory' => $inventory,
|
|
|
|
|
|
'is_exist_many_spec' => 1,
|
|
|
|
|
|
'buy_min_number' => 1,
|
|
|
|
|
|
'buy_max_number' => 1,
|
|
|
|
|
|
'upd_time' => time(),
|
|
|
|
|
|
]);
|
2026-04-15 11:58:48 +00:00
|
|
|
|
|
2026-04-18 21:46:37 +00:00
|
|
|
|
return ['code' => 0];
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|
2026-04-19 21:22:07 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取商品前端展示数据(供 ticket_detail.html 模板使用)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $goodsId
|
|
|
|
|
|
* @return array ['vr_seat_template' => [...], 'goods_spec_data' => [...]]
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function GetGoodsViewData(int $goodsId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
// 读取 vr_goods_config
|
|
|
|
|
|
$goods = \think\facade\Db::name('goods')->find($goodsId);
|
|
|
|
|
|
$vrGoodsConfig = json_decode($goods['vr_goods_config'] ?? '', true);
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($vrGoodsConfig) || !is_array($vrGoodsConfig)) {
|
|
|
|
|
|
return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取第一个配置块(单模板模式)
|
|
|
|
|
|
$config = $vrGoodsConfig[0];
|
|
|
|
|
|
$templateId = intval($config['template_id'] ?? 0);
|
|
|
|
|
|
if ($templateId <= 0) {
|
|
|
|
|
|
return ['vr_seat_template' => null, 'goods_spec_data' => [], 'goods_config' => null];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取座位模板(包含 seat_map 和 spec_base_id_map)
|
|
|
|
|
|
$seatTemplate = \think\facade\Db::name(self::table('seat_templates'))
|
|
|
|
|
|
->where('id', $templateId)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
2026-04-20 06:32:38 +00:00
|
|
|
|
// 模板不存在时(硬删除场景):
|
|
|
|
|
|
// - 将 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,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-19 21:22:07 +00:00
|
|
|
|
// 解码 seat_map JSON(存储时是 JSON 字符串)
|
|
|
|
|
|
if (!empty($seatTemplate['seat_map'])) {
|
|
|
|
|
|
$decoded = json_decode($seatTemplate['seat_map'], true);
|
|
|
|
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
|
|
|
|
$seatTemplate['seat_map'] = $decoded;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解码 spec_base_id_map JSON
|
|
|
|
|
|
if (!empty($seatTemplate['spec_base_id_map'])) {
|
|
|
|
|
|
$decoded = json_decode($seatTemplate['spec_base_id_map'], true);
|
|
|
|
|
|
if (json_last_error() === JSON_ERROR_NONE) {
|
|
|
|
|
|
$seatTemplate['spec_base_id_map'] = $decoded;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建场次列表(goods_spec_data)
|
|
|
|
|
|
$sessions = $config['sessions'] ?? [];
|
|
|
|
|
|
$goodsSpecData = [];
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($sessions as $session) {
|
|
|
|
|
|
$start = $session['start'] ?? '';
|
|
|
|
|
|
$end = $session['end'] ?? '';
|
|
|
|
|
|
$timeRange = $start && $end ? "{$start}-{$end}" : ($start ?: $end);
|
|
|
|
|
|
|
|
|
|
|
|
// 查找该场次对应的 spec_base_id
|
|
|
|
|
|
$specValue = \think\facade\Db::name('goods_spec_value')
|
|
|
|
|
|
->alias('sv')
|
|
|
|
|
|
->join('goods_spec_base sb', 'sb.id = sv.goods_spec_base_id')
|
|
|
|
|
|
->where('sv.goods_id', $goodsId)
|
|
|
|
|
|
->where('sv.value', $timeRange)
|
|
|
|
|
|
->where('sb.price', '>', 0)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
$goodsSpecData[] = [
|
|
|
|
|
|
'spec_id' => $specValue['goods_spec_base_id'] ?? 0,
|
|
|
|
|
|
'spec_name' => $timeRange,
|
|
|
|
|
|
'price' => $specValue['price'] ?? floatval($goods['price'] ?? 0),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有从配置读取到场次,尝试从数据库直接读取场次类规格值
|
|
|
|
|
|
if (empty($goodsSpecData)) {
|
|
|
|
|
|
$sessionValues = \think\facade\Db::name('goods_spec_value')
|
|
|
|
|
|
->alias('sv')
|
|
|
|
|
|
->join('goods_spec_base sb', 'sb.id = sv.goods_spec_base_id')
|
|
|
|
|
|
->field('sv.goods_spec_base_id as spec_id, sv.value as spec_name, sb.price')
|
|
|
|
|
|
->where('sv.goods_id', $goodsId)
|
|
|
|
|
|
->where('sb.price', '>', 0)
|
|
|
|
|
|
->order('sb.id asc')
|
|
|
|
|
|
->select()->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($sessionValues as $sv) {
|
|
|
|
|
|
if (preg_match('/^\d{2}:\d{2}-\d{2}:\d{2}$/', $sv['spec_name'])) {
|
|
|
|
|
|
$goodsSpecData[] = [
|
|
|
|
|
|
'spec_id' => $sv['spec_id'],
|
|
|
|
|
|
'spec_name' => $sv['spec_name'],
|
|
|
|
|
|
'price' => floatval($sv['price']),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'vr_seat_template' => $seatTemplate ?: null,
|
|
|
|
|
|
'goods_spec_data' => $goodsSpecData,
|
|
|
|
|
|
'goods_config' => $config,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2026-04-15 11:58:48 +00:00
|
|
|
|
}
|