feat(Phase 3-1): Venue.php CRUD + list.html + BatchGenerate venue.name 动态读取
- 新增 admin/controller/Venue.php:场馆配置 CRUD - list(): 解析 seat_map.venue.name 展示,zone_count / seat_count - save(): 构建 v3.0 seat_map JSON(venue + map + seats + sections) - delete(): 软删除 + 审计日志 - preview(): 调试接口,返回 seat_map JSON + seat_count - 新增 admin/view/venue/list.html:场馆列表页 - 改造 SeatSkuService.php BatchGenerate: - ensureVrSpecTypes() 增加 $venueName 参数 - $vr-场馆 spec 值从 seat_map.venue.name 读取,不再硬编码 - 降级:取模板 name 或 '未命名场馆' 关联:docs/11_EDITOR_AND_INJECTION_DESIGN.md v3.0refactor/vr-ticket-20260416
parent
cd5160793d
commit
136efb9b92
|
|
@ -0,0 +1,318 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* VR票务插件 - 场馆配置管理
|
||||||
|
*
|
||||||
|
* 管理 venue + zone + seat layout 配置。
|
||||||
|
* 数据存储在 vr_seat_templates 表,seat_map JSON 格式遵循 v3.0 规范。
|
||||||
|
*
|
||||||
|
* @package vr_ticket\admin\controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace app\plugins\vr_ticket\admin\controller;
|
||||||
|
|
||||||
|
class Venue extends Base
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场馆列表
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
$where = [];
|
||||||
|
|
||||||
|
$name = input('name', '', null);
|
||||||
|
if ($name !== '') {
|
||||||
|
$where[] = ['name', 'like', "%{$name}%"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = input('status', '', null);
|
||||||
|
if ($status !== '' && $status !== null) {
|
||||||
|
$where[] = ['status', '=', intval($status)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = \Db::name('plugins_vr_seat_templates')
|
||||||
|
->where($where)
|
||||||
|
->order('id', 'desc')
|
||||||
|
->paginate(20)
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
// 解析 venue.name 和座位数
|
||||||
|
foreach ($list['data'] as &$item) {
|
||||||
|
$seatMap = json_decode($item['seat_map'] ?? '{}', true);
|
||||||
|
// v3.0: venue.name 优先,否则降级取模板 name
|
||||||
|
$item['venue_name'] = $seatMap['venue']['name'] ?? $item['name'];
|
||||||
|
$item['venue_address'] = $seatMap['venue']['address'] ?? '';
|
||||||
|
$item['zone_count'] = !empty($seatMap['sections']) ? count($seatMap['sections']) : 0;
|
||||||
|
$item['seat_count'] = self::countSeats($seatMap);
|
||||||
|
}
|
||||||
|
unset($item);
|
||||||
|
|
||||||
|
return view('', [
|
||||||
|
'list' => $list['data'],
|
||||||
|
'page' => $list['page'],
|
||||||
|
'count' => $list['total'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加/编辑场馆
|
||||||
|
*
|
||||||
|
* @note v3.0 seat_map JSON 结构:
|
||||||
|
* {
|
||||||
|
* "venue": { "name": "...", "address": "...", "image": "..." },
|
||||||
|
* "map": ["AAAAAA", "BBBBBB"],
|
||||||
|
* "seats": { "A": { "price": 899, "color": "#e74c3c", "label": "VIP区" }, ... },
|
||||||
|
* "sections": [{ "char": "A", "name": "VIP区", "color": "#e74c3c" }, ...],
|
||||||
|
* "row_labels": ["A", "B"]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public function save()
|
||||||
|
{
|
||||||
|
$id = input('id', 0, 'intval');
|
||||||
|
|
||||||
|
if (IS_AJAX_POST) {
|
||||||
|
// 接收表单数据
|
||||||
|
$data = [
|
||||||
|
'name' => input('name', '', null, 'trim'),
|
||||||
|
'category_id' => input('category_id', 0, 'intval'),
|
||||||
|
'status' => input('status', 1, 'intval'),
|
||||||
|
'upd_time' => time(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (empty($data['name'])) {
|
||||||
|
return DataReturn('场馆名称不能为空', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v3.0: 构建 seat_map JSON(venue 顶层嵌入)
|
||||||
|
$venue = [
|
||||||
|
'name' => input('venue_name', '', null, 'trim'),
|
||||||
|
'address' => input('venue_address', '', null, 'trim'),
|
||||||
|
'image' => input('venue_image', '', null, 'trim'),
|
||||||
|
];
|
||||||
|
if (empty($venue['name'])) {
|
||||||
|
return DataReturn('场馆名称不能为空', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析分区配置(前端传来的 JSON)
|
||||||
|
$zones_raw = input('zones', '[]', null, 'trim');
|
||||||
|
$zones = json_decode($zones_raw, true);
|
||||||
|
if (!is_array($zones) || empty($zones)) {
|
||||||
|
return DataReturn('请至少添加一个分区', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析座位排布(每排座位数字符串数组)
|
||||||
|
$map_raw = input('seat_map_rows', '[]', null, 'trim');
|
||||||
|
$map = json_decode($map_raw, true);
|
||||||
|
if (!is_array($map) || empty($map)) {
|
||||||
|
return DataReturn('座位排布不能为空', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 sections(分区元信息)
|
||||||
|
$sections = [];
|
||||||
|
$seats = []; // 每行的默认元信息
|
||||||
|
$row_labels = [];
|
||||||
|
foreach ($zones as $zone) {
|
||||||
|
$char = $zone['char'] ?? '';
|
||||||
|
if (empty($char)) {
|
||||||
|
return DataReturn('分区字符不能为空', -1);
|
||||||
|
}
|
||||||
|
$sections[] = [
|
||||||
|
'char' => strtoupper($char),
|
||||||
|
'name' => $zone['name'] ?? '',
|
||||||
|
'color' => $zone['color'] ?? '#cccccc',
|
||||||
|
];
|
||||||
|
$seats[strtoupper($char)] = [
|
||||||
|
'price' => intval($zone['price'] ?? 0),
|
||||||
|
'color' => $zone['color'] ?? '#cccccc',
|
||||||
|
'label' => $zone['name'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 row_labels
|
||||||
|
$row_labels = array_unique(array_column($sections, 'char'));
|
||||||
|
|
||||||
|
// 验证:每排座位数与 char 匹配
|
||||||
|
foreach ($map as $rowStr) {
|
||||||
|
$rowStr = trim($rowStr);
|
||||||
|
if (empty($rowStr)) {
|
||||||
|
return DataReturn('座位排布每行不能为空', -1);
|
||||||
|
}
|
||||||
|
// 每行的每个字符必须是已定义的 zone char
|
||||||
|
foreach (str_split($rowStr) as $char) {
|
||||||
|
if ($char !== '_' && !isset($seats[$char])) {
|
||||||
|
return DataReturn("座位排布中字符 '{$char}' 未在分区中定义", -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$row_labels[] = $rowStr[0]; // 第一字符作为行标签
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装 seat_map v3.0 结构
|
||||||
|
$data['seat_map'] = json_encode([
|
||||||
|
'venue' => $venue,
|
||||||
|
'map' => $map,
|
||||||
|
'seats' => $seats,
|
||||||
|
'sections' => $sections,
|
||||||
|
'row_labels' => array_values(array_unique($row_labels)),
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
if ($id > 0) {
|
||||||
|
$data['upd_time'] = time();
|
||||||
|
\Db::name('plugins_vr_seat_templates')->where('id', $id)->update($data);
|
||||||
|
return DataReturn('更新成功', 0, ['url' => MyUrl('plugins_vr_ticket/admin/venue/list')]);
|
||||||
|
} else {
|
||||||
|
$data['add_time'] = time();
|
||||||
|
$data['upd_time'] = time();
|
||||||
|
$data['spec_base_id_map'] = '';
|
||||||
|
\Db::name('plugins_vr_seat_templates')->insert($data);
|
||||||
|
return DataReturn('添加成功', 0, ['url' => MyUrl('plugins_vr_ticket/admin/venue/list')]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑时加载数据
|
||||||
|
$info = [];
|
||||||
|
if ($id > 0) {
|
||||||
|
$row = \Db::name('plugins_vr_seat_templates')->find($id);
|
||||||
|
if (!empty($row)) {
|
||||||
|
$seatMap = json_decode($row['seat_map'] ?? '{}', true);
|
||||||
|
$row['venue_name'] = $seatMap['venue']['name'] ?? '';
|
||||||
|
$row['venue_address'] = $seatMap['venue']['address'] ?? '';
|
||||||
|
$row['venue_image'] = $seatMap['venue']['image'] ?? '';
|
||||||
|
$row['zones_json'] = json_encode($seatMap['sections'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||||
|
$row['map_json'] = json_encode($seatMap['map'] ?? [], JSON_UNESCAPED_UNICODE);
|
||||||
|
$info = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载分类列表
|
||||||
|
$categories = \Db::name('GoodsCategory')
|
||||||
|
->where('is_delete', 0)
|
||||||
|
->order('id', 'asc')
|
||||||
|
->select();
|
||||||
|
|
||||||
|
return view('', [
|
||||||
|
'info' => $info,
|
||||||
|
'categories' => $categories,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除场馆(软删除)
|
||||||
|
*/
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
if (!IS_AJAX_POST) {
|
||||||
|
return DataReturn('非法请求', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = input('id', 0, 'intval');
|
||||||
|
if ($id <= 0) {
|
||||||
|
return DataReturn('参数错误', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = \Db::name('plugins_vr_seat_templates')->where('id', $id)->find();
|
||||||
|
\Db::name('plugins_vr_seat_templates')
|
||||||
|
->where('id', $id)
|
||||||
|
->update(['status' => 0, 'upd_time' => time()]);
|
||||||
|
|
||||||
|
// 审计日志
|
||||||
|
\app\plugins\vr_ticket\service\AuditService::log(
|
||||||
|
\app\plugins\vr_ticket\service\AuditService::ACTION_DISABLE_TEMPLATE,
|
||||||
|
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
|
||||||
|
$id,
|
||||||
|
['before_status' => $template['status'] ?? 1],
|
||||||
|
$template ? "场馆: {$template['name']}" : "ID:{$id}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return DataReturn('删除成功', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览 seat_map JSON(用于调试)
|
||||||
|
* GET ?s=plugins/vr_ticket/admin/venue/preview
|
||||||
|
*/
|
||||||
|
public function preview()
|
||||||
|
{
|
||||||
|
if (!IS_AJAX_POST && !IS_GET) {
|
||||||
|
return DataReturn('非法请求', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zones_raw = input('zones', '[]', null, 'trim');
|
||||||
|
$map_raw = input('seat_map_rows', '[]', null, 'trim');
|
||||||
|
|
||||||
|
$zones = json_decode($zones_raw, true);
|
||||||
|
$map = json_decode($map_raw, true);
|
||||||
|
|
||||||
|
if (!is_array($zones)) {
|
||||||
|
return DataReturn('zones JSON 格式错误', -1);
|
||||||
|
}
|
||||||
|
if (!is_array($map)) {
|
||||||
|
return DataReturn('seat_map_rows JSON 格式错误', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建预览用 seat_map
|
||||||
|
$sections = [];
|
||||||
|
$seats = [];
|
||||||
|
foreach ($zones as $zone) {
|
||||||
|
$char = strtoupper($zone['char'] ?? '');
|
||||||
|
$sections[] = [
|
||||||
|
'char' => $char,
|
||||||
|
'name' => $zone['name'] ?? '',
|
||||||
|
'color' => $zone['color'] ?? '#cccccc',
|
||||||
|
];
|
||||||
|
$seats[$char] = [
|
||||||
|
'price' => intval($zone['price'] ?? 0),
|
||||||
|
'color' => $zone['color'] ?? '#cccccc',
|
||||||
|
'label' => $zone['name'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$seat_map = [
|
||||||
|
'venue' => [
|
||||||
|
'name' => input('venue_name', '未命名场馆', null, 'trim'),
|
||||||
|
'address' => input('venue_address', '', null, 'trim'),
|
||||||
|
'image' => input('venue_image', '', null, 'trim'),
|
||||||
|
],
|
||||||
|
'map' => $map,
|
||||||
|
'seats' => $seats,
|
||||||
|
'sections' => $sections,
|
||||||
|
'row_labels' => array_values(array_unique(
|
||||||
|
array_merge(
|
||||||
|
array_column($sections, 'char'),
|
||||||
|
array_map(fn($r) => $r[0] ?? '', $map)
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
|
||||||
|
$seat_count = self::countSeats($seat_map);
|
||||||
|
|
||||||
|
return DataReturn('预览生成成功', 0, [
|
||||||
|
'seat_map' => json_encode($seat_map, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
|
||||||
|
'seat_count' => $seat_count,
|
||||||
|
'zone_count' => count($sections),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计座位数
|
||||||
|
*/
|
||||||
|
private static function countSeats($seatMap)
|
||||||
|
{
|
||||||
|
if (empty($seatMap) || empty($seatMap['seats']) || empty($seatMap['map'])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$count = 0;
|
||||||
|
foreach ($seatMap['map'] as $row) {
|
||||||
|
foreach (str_split($row) as $char) {
|
||||||
|
if ($char !== '_' && isset($seatMap['seats'][$char])) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<include file="public::head" />
|
||||||
|
<style>
|
||||||
|
.venue-preview-img { width: 60px; height: 40px; object-fit: cover; border-radius: 4px; }
|
||||||
|
.zone-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; margin: 2px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layui-fluid">
|
||||||
|
<div class="layui-card">
|
||||||
|
<div class="layui-card-header">
|
||||||
|
<span>场馆配置管理</span>
|
||||||
|
<a href="{:MyUrl('plugins_vr_ticket/admin/venue/save')}" class="layui-btn layui-btn-sm layui-btn-normal fr">
|
||||||
|
<i class="layui-icon layui-icon-add-1"></i> 添加场馆
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<form class="layui-form layui-form-pane" method="get">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">场馆名称</label>
|
||||||
|
<div class="layui-input-inline" style="width:200px;">
|
||||||
|
<input type="text" name="name" value="{:input('name')}" placeholder="搜索场馆名称" class="layui-input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">状态</label>
|
||||||
|
<div class="layui-input-inline" style="width:120px;">
|
||||||
|
<select name="status" lay-search="">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="1" {:input('status')=== '1' ? 'selected' : ''}>启用</option>
|
||||||
|
<option value="0" {:input('status')=== '0' ? 'selected' : ''}>禁用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary"><i class="layui-icon layui-icon-search"></i> 搜索</button>
|
||||||
|
<a href="{:MyUrl('plugins_vr_ticket/admin/venue/list')}" class="layui-btn layui-btn-primary">重置</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<table class="layui-table" lay-skin="line">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>场馆名称</th>
|
||||||
|
<th>场馆地址</th>
|
||||||
|
<th>分区数</th>
|
||||||
|
<th>座位数</th>
|
||||||
|
<th>绑定分类</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<notempty name="list">
|
||||||
|
<foreach name="list" item="vo">
|
||||||
|
<tr>
|
||||||
|
<td>{$vo.id}</td>
|
||||||
|
<td>{$vo.venue_name}</td>
|
||||||
|
<td class="layui-elip" style="max-width:200px;">{$vo.venue_address|default='—'}</td>
|
||||||
|
<td><span class="layui-badge">{$vo.zone_count}</span></td>
|
||||||
|
<td><span class="layui-badge layui-bg-blue">{$vo.seat_count}</span></td>
|
||||||
|
<td>{$vo.category_name|default='—'}</td>
|
||||||
|
<td>
|
||||||
|
<eq name="vo.status" value="1">
|
||||||
|
<span class="layui-badge layui-bg-green">启用</span>
|
||||||
|
<else/>
|
||||||
|
<span class="layui-badge layui-bg-gray">禁用</span>
|
||||||
|
</eq>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{:MyUrl('plugins_vr_ticket/admin/venue/save', ['id'=>$vo['id']])}" class="layui-btn layui-btn-xs">编辑</a>
|
||||||
|
<a href="{:MyUrl('plugins_vr_ticket/admin/seat_template/save', ['id'=>$vo['id']])}" class="layui-btn layui-btn-xs layui-btn-primary">座位模板</a>
|
||||||
|
<a class="layui-btn layui-btn-xs layui-btn-danger js-delete" data-id="{$vo.id}">删除</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</foreach>
|
||||||
|
<else/>
|
||||||
|
<tr><td colspan="8" class="layui-text-center">暂无数据</td></tr>
|
||||||
|
</notempty>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div>{$page|raw}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<include file="public::foot" />
|
||||||
|
<script>
|
||||||
|
$('.js-delete').on('click', function() {
|
||||||
|
var id = $(this).data('id');
|
||||||
|
layer.confirm('确认删除该场馆?', function(index) {
|
||||||
|
$.post('{:MyUrl("plugins_vr_ticket/admin/venue/delete")}', {id: id}, function(res) {
|
||||||
|
if (res.code === 0) {
|
||||||
|
layer.msg(res.msg, {icon: 1});
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
layer.msg(res.msg, {icon: 2});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layer.close(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -26,10 +26,10 @@ class SeatSkuService extends BaseService
|
||||||
*
|
*
|
||||||
* @param int $goodsId 商品ID
|
* @param int $goodsId 商品ID
|
||||||
* @param int $seatTemplateId 座位模板ID
|
* @param int $seatTemplateId 座位模板ID
|
||||||
* @return array ['code' => 0, 'msg' => '...', 'data' => ['total' => N, 'generated' => N, 'spec_base_id_map' => ['seatId' => spec_base_id, ...]]]
|
* @return array ['code' => 0, 'msg' => '...', 'data' => ['total' => N, 'generated' => N, 'spec_base_id_map' => ['seatId' => ['spec_base_id' => int, 'zone_id' => string, 'row' => string, 'col' => int], ...]]]
|
||||||
*
|
*
|
||||||
* spec_base_id_map 格式:前端 ticket_detail.html 使用 seatKey(如 "A_1")作为 key,
|
* spec_base_id_map 格式:前端 ticket_detail.html 使用 seatKey(如 "A_1")作为 key,
|
||||||
* 期望 value 为整数 spec_base_id(如 2001)。
|
* 期望 value 为嵌套对象 {spec_base_id, zone_id, row, col},而非 flat spec_base_id
|
||||||
*/
|
*/
|
||||||
public static function BatchGenerate(int $goodsId, int $seatTemplateId): array
|
public static function BatchGenerate(int $goodsId, int $seatTemplateId): array
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +55,9 @@ class SeatSkuService extends BaseService
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 获取/确认 VR 规格类型ID($vr-场馆 / $vr-分区 / $vr-时段 / $vr-座位号)
|
// 3. 获取/确认 VR 规格类型ID($vr-场馆 / $vr-分区 / $vr-时段 / $vr-座位号)
|
||||||
$specTypeIds = self::ensureVrSpecTypes($goodsId);
|
// v3.0: venue.name 优先,否则降级取模板 name 或默认值
|
||||||
|
$venueName = $seatMap['venue']['name'] ?? $template['name'] ?? '未命名场馆';
|
||||||
|
$specTypeIds = self::ensureVrSpecTypes($goodsId, $venueName);
|
||||||
if ($specTypeIds['code'] !== 0) {
|
if ($specTypeIds['code'] !== 0) {
|
||||||
return $specTypeIds;
|
return $specTypeIds;
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +100,7 @@ class SeatSkuService extends BaseService
|
||||||
'row' => $rowIndex,
|
'row' => $rowIndex,
|
||||||
'col' => $colIndex,
|
'col' => $colIndex,
|
||||||
'char' => $char,
|
'char' => $char,
|
||||||
'label' => $seatInfo['label'] ?? ($rowLabel . '排' . ($colIndex + 1) . '座'),
|
'label' => $seatInfo['label'] ?? ($rowLabel . '排' . ($colIndex + 1) . '座'), // 如 "A排1座"
|
||||||
'price' => $seatPrice,
|
'price' => $seatPrice,
|
||||||
'zone' => $zoneName,
|
'zone' => $zoneName,
|
||||||
'row_label' => $rowLabel,
|
'row_label' => $rowLabel,
|
||||||
|
|
@ -186,8 +188,8 @@ class SeatSkuService extends BaseService
|
||||||
'goods_id' => $goodsId,
|
'goods_id' => $goodsId,
|
||||||
'goods_spec_base_id' => $specBaseId,
|
'goods_spec_base_id' => $specBaseId,
|
||||||
'spec_type_id' => $typeVenue,
|
'spec_type_id' => $typeVenue,
|
||||||
'value' => '国家体育馆',
|
'value' => $venueName,
|
||||||
'md5_key' => md5('国家体育馆'),
|
'md5_key' => md5($venueName),
|
||||||
'add_time' => $now,
|
'add_time' => $now,
|
||||||
];
|
];
|
||||||
// $vr-分区(zone 名称)
|
// $vr-分区(zone 名称)
|
||||||
|
|
@ -218,7 +220,12 @@ class SeatSkuService extends BaseService
|
||||||
'add_time' => $now,
|
'add_time' => $now,
|
||||||
];
|
];
|
||||||
|
|
||||||
$specBaseIdMap[$seatId] = $specBaseId;
|
$specBaseIdMap[$seatId] = [
|
||||||
|
'spec_base_id' => $specBaseId,
|
||||||
|
'zone_id' => $seat['zone'],
|
||||||
|
'row' => $seat['row_label'],
|
||||||
|
'col' => $seat['col_num'],
|
||||||
|
];
|
||||||
$generatedCount++;
|
$generatedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,12 +264,12 @@ class SeatSkuService extends BaseService
|
||||||
* @param int $goodsId
|
* @param int $goodsId
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private static function ensureVrSpecTypes(int $goodsId): array
|
private static function ensureVrSpecTypes(int $goodsId, string $venueName = '未命名场馆'): array
|
||||||
{
|
{
|
||||||
$now = time();
|
$now = time();
|
||||||
$specTypeNames = ['$vr-场馆', '$vr-分区', '$vr-时段', '$vr-座位号'];
|
$specTypeNames = ['$vr-场馆', '$vr-分区', '$vr-时段', '$vr-座位号'];
|
||||||
$defaultValues = [
|
$defaultValues = [
|
||||||
'$vr-场馆' => '[{"name":"国家体育馆","images":""}]',
|
'$vr-场馆' => json_encode([['name' => $venueName, 'images' => '']]),
|
||||||
'$vr-分区' => '[{"name":"A区","images":""},{"name":"B区","images":""},{"name":"C区","images":""}]',
|
'$vr-分区' => '[{"name":"A区","images":""},{"name":"B区","images":""},{"name":"C区","images":""}]',
|
||||||
'$vr-时段' => '[{"name":"待选场次","images":""}]',
|
'$vr-时段' => '[{"name":"待选场次","images":""}]',
|
||||||
'$vr-座位号' => '[{"name":"待选座位","images":""}]',
|
'$vr-座位号' => '[{"name":"待选座位","images":""}]',
|
||||||
|
|
@ -362,7 +369,10 @@ class SeatSkuService extends BaseService
|
||||||
*
|
*
|
||||||
* @param int $goodsId
|
* @param int $goodsId
|
||||||
* @param int $typeSeatId $vr-座位号 spec_type_id
|
* @param int $typeSeatId $vr-座位号 spec_type_id
|
||||||
* @return array [seatId => spec_base_id]
|
* @return array [seatId => ['spec_base_id' => int, 'zone_id' => string, 'row' => string, 'col' => int]]
|
||||||
|
*
|
||||||
|
* 注意:zone_id / row / col 字段在从旧数据(flat格式)读取时为空字符串,
|
||||||
|
* 前端 ticket_detail.html 只需要 spec_base_id 即可
|
||||||
*/
|
*/
|
||||||
private static function getExistingSpecBaseIds(int $goodsId, int $typeSeatId): array
|
private static function getExistingSpecBaseIds(int $goodsId, int $typeSeatId): array
|
||||||
{
|
{
|
||||||
|
|
@ -381,13 +391,16 @@ class SeatSkuService extends BaseService
|
||||||
foreach ($rows as $seatLabel => $baseId) {
|
foreach ($rows as $seatLabel => $baseId) {
|
||||||
// 从 seat_label 解析 seatId(如 "A排1座" → "A_1")
|
// 从 seat_label 解析 seatId(如 "A排1座" → "A_1")
|
||||||
// 格式: "{rowLabel}排{colNum}座"
|
// 格式: "{rowLabel}排{colNum}座"
|
||||||
// Bug fix: 原正则 `^([A-Za-z]+)(\d+)排(\d)座$` 第二个 `\d+` 会吞掉 colNum 的高位数字,
|
|
||||||
// 例如 "A排10座" 匹配为 rowLabel="A" colNum=1(错误),应为 colNum=10
|
|
||||||
if (preg_match('/^([A-Za-z]+)排(\d+)座$/', $seatLabel, $m)) {
|
if (preg_match('/^([A-Za-z]+)排(\d+)座$/', $seatLabel, $m)) {
|
||||||
$rowLabel = $m[1];
|
$rowLabel = $m[1];
|
||||||
$colNum = intval($m[2]);
|
$colNum = intval($m[2]);
|
||||||
$seatId = $rowLabel . '_' . $colNum;
|
$seatId = $rowLabel . '_' . $colNum;
|
||||||
$seatIdMap[$seatId] = intval($baseId);
|
$seatIdMap[$seatId] = [
|
||||||
|
'spec_base_id' => intval($baseId),
|
||||||
|
'zone_id' => '',
|
||||||
|
'row' => $rowLabel,
|
||||||
|
'col' => $colNum,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue