feat: 添加场馆和分区选择器 + specTypeList 支持
- SeatSkuService: 返回 specTypeList 包含所有4维规格 - Goods.php: 注入 specTypeList - ticket_detail.html: - 添加 venueSelector 和 sectionSelector HTML 容器 - 添加 renderAllSelectors() 渲染场次/场馆/分区 - 添加 selectVenue/selectSection/filterSeats 函数 - CSS: 添加规格选择器样式pull/19/head
parent
fc07c2ece6
commit
de9134773f
|
|
@ -143,6 +143,7 @@ class Goods extends Common
|
|||
'vr_seat_template' => $viewData['vr_seat_template'] ?? null,
|
||||
'goods_spec_data' => $viewData['goods_spec_data'] ?? [],
|
||||
'seatSpecMap' => $viewData['seatSpecMap'] ?? [],
|
||||
'specTypeList' => $viewData['specTypeList'] ?? [],
|
||||
]);
|
||||
// 使用绝对路径 + fetch() 方法,让 Think 驱动正确解析 include 路径
|
||||
$tplFile = ROOT . 'app' . DS . 'plugins' . DS . 'vr_ticket' . DS . 'view' . DS . 'goods' . DS . 'ticket_detail.html';
|
||||
|
|
|
|||
|
|
@ -422,8 +422,31 @@ class SeatSkuService extends BaseService
|
|||
}
|
||||
}
|
||||
|
||||
// ========== 新增:构建 seatSpecMap ==========
|
||||
$seatSpecMap = self::buildSeatSpecMap($goodsId, $seatTemplate);
|
||||
// ========== 构建规格类型列表(4维:场馆、分区、座位号、场次)==========
|
||||
// 从 GoodsSpecType 读取所有维度定义
|
||||
$specTypeList = [];
|
||||
$specTypes = \think\facade\Db::name('GoodsSpecType')
|
||||
->where('goods_id', $goodsId)
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($specTypes as $type) {
|
||||
$dimName = $type['name'] ?? '';
|
||||
$values = json_decode($type['value'] ?? '[]', true);
|
||||
$options = [];
|
||||
foreach ($values as $v) {
|
||||
if (isset($v['name'])) {
|
||||
$options[] = $v['name'];
|
||||
}
|
||||
}
|
||||
if (!empty($dimName) && !empty($options)) {
|
||||
$specTypeList[$dimName] = [
|
||||
'name' => $dimName,
|
||||
'options' => $options,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 构建场次列表(goods_spec_data)==========
|
||||
$sessions = $config['sessions'] ?? [];
|
||||
|
|
@ -450,7 +473,7 @@ class SeatSkuService extends BaseService
|
|||
}
|
||||
|
||||
$goodsSpecData[] = [
|
||||
'spec_id' => 0, // 不再需要 spec_id,前端用 seatSpecMap
|
||||
'spec_id' => 0,
|
||||
'spec_name' => $timeRange,
|
||||
'price' => $sessionPrice ?? floatval($goods['price'] ?? 0),
|
||||
'start' => $start,
|
||||
|
|
@ -484,6 +507,7 @@ class SeatSkuService extends BaseService
|
|||
'vr_seat_template' => $seatTemplate ?: null,
|
||||
'goods_spec_data' => $goodsSpecData,
|
||||
'seatSpecMap' => $seatSpecMap,
|
||||
'specTypeList' => $specTypeList, // 4维规格类型列表
|
||||
'goods_config' => $config,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,3 +102,16 @@
|
|||
.vr-goods-info { background: #fff; border: 1px solid #e8e8e8; border-radius: 8px; padding: 15px; margin-bottom: 20px; }
|
||||
.vr-goods-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 8px; margin-bottom: 15px; }
|
||||
.vr-goods-photos img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 4px; }
|
||||
|
||||
/* 规格选择器样式 */
|
||||
.vr-spec-selector { margin-bottom: 15px; }
|
||||
.vr-spec-label { font-size: 14px; font-weight: bold; color: #333; margin-bottom: 10px; }
|
||||
.vr-spec-options { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.vr-spec-option {
|
||||
border: 1px solid #ddd; border-radius: 6px; padding: 8px 16px;
|
||||
cursor: pointer; font-size: 13px; color: #333;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.vr-spec-option:hover { border-color: #409eff; }
|
||||
.vr-spec-option.selected { border-color: #409eff; background: #ecf5ff; color: #409eff; }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 场馆选择 -->
|
||||
<div class="vr-seat-section" id="venueSection">
|
||||
<div class="vr-section-title">选择场馆</div>
|
||||
<div id="venueSelector"><!-- 由 JS 动态渲染 --></div>
|
||||
</div>
|
||||
|
||||
<!-- 分区选择 -->
|
||||
<div class="vr-seat-section" id="sectionSection">
|
||||
<div class="vr-section-title">选择分区</div>
|
||||
<div id="sectionSelector"><!-- 由 JS 动态渲染 --></div>
|
||||
</div>
|
||||
|
||||
<!-- 座位图 -->
|
||||
<div class="vr-seat-section" id="seatSection" style="display:none">
|
||||
<div class="vr-section-title">选择座位 <span
|
||||
|
|
@ -80,6 +92,7 @@
|
|||
// vr_seat_template already contains the decoded seat_map (venue, rooms, sections, seats)
|
||||
seatMap: <?php echo json_encode($vr_seat_template ?? []); ?>,
|
||||
seatSpecMap: <?php echo json_encode($seatSpecMap ?? []); ?>,
|
||||
specTypeList: <?php echo json_encode($specTypeList ?? []); ?>, // 4维规格类型列表
|
||||
selectedSeats: [], // [{seatKey, price, rowLabel, colNum, section}]
|
||||
soldSeats: { }, // {seatKey: true}
|
||||
currentSession: null, // 当前选中场次 value (e.g. "15:00-16:59")
|
||||
|
|
@ -87,11 +100,123 @@
|
|||
currentSection: null, // 当前选中分区 char
|
||||
|
||||
init: function() {
|
||||
this.renderSessions();
|
||||
this.renderAllSelectors();
|
||||
this.bindEvents();
|
||||
},
|
||||
|
||||
// 渲染场次列表
|
||||
// 渲染所有选择器(场次、场馆、分区)
|
||||
renderAllSelectors: function() {
|
||||
var specTypeList = this.specTypeList;
|
||||
var goodsSpecData = <?php echo json_encode($goods_spec_data ?? []); ?> || [];
|
||||
|
||||
// 1. 渲染场次选择器
|
||||
var sessionHtml = '';
|
||||
if (goodsSpecData.length > 0) {
|
||||
goodsSpecData.forEach(function (spec) {
|
||||
sessionHtml += '<div class="vr-session-item" data-session="' + spec.spec_name + '" onclick="vrTicketApp.selectSession(this)">' +
|
||||
'<div class="date">' + spec.spec_name + '</div>' +
|
||||
'<div class="price">¥' + spec.price + '</div></div>';
|
||||
});
|
||||
} else if (specTypeList['$vr-场次']) {
|
||||
specTypeList['$vr-场次'].options.forEach(function(session) {
|
||||
sessionHtml += '<div class="vr-session-item" data-session="' + session + '" onclick="vrTicketApp.selectSession(this)">' +
|
||||
'<div class="date">' + session + '</div>' +
|
||||
'<div class="price">¥0</div></div>';
|
||||
});
|
||||
}
|
||||
document.getElementById('sessionGrid').innerHTML = sessionHtml;
|
||||
|
||||
// 2. 渲染场馆选择器
|
||||
var venueHtml = '<div class="vr-spec-selector"><div class="vr-spec-label">选择场馆</div><div class="vr-spec-options">';
|
||||
if (specTypeList['$vr-场馆']) {
|
||||
specTypeList['$vr-场馆'].options.forEach(function(venue) {
|
||||
venueHtml += '<div class="vr-spec-option" data-venue="' + venue + '" onclick="vrTicketApp.selectVenue(this)">' + venue + '</div>';
|
||||
});
|
||||
}
|
||||
venueHtml += '</div></div>';
|
||||
document.getElementById('venueSelector').innerHTML = venueHtml;
|
||||
|
||||
// 3. 渲染分区选择器
|
||||
var sectionHtml = '<div class="vr-spec-selector"><div class="vr-spec-label">选择分区</div><div class="vr-spec-options">';
|
||||
if (specTypeList['$vr-分区']) {
|
||||
specTypeList['$vr-分区'].options.forEach(function(section) {
|
||||
sectionHtml += '<div class="vr-spec-option" data-section="' + section + '" onclick="vrTicketApp.selectSection(this)">' + section + '</div>';
|
||||
});
|
||||
}
|
||||
sectionHtml += '</div></div>';
|
||||
document.getElementById('sectionSelector').innerHTML = sectionHtml;
|
||||
},
|
||||
|
||||
// 选择场馆
|
||||
selectVenue: function(el) {
|
||||
document.querySelectorAll('#venueSelector .vr-spec-option').forEach(function (item) {
|
||||
item.classList.remove('selected');
|
||||
});
|
||||
el.classList.add('selected');
|
||||
this.currentVenue = el.dataset.venue;
|
||||
this.filterSeats();
|
||||
},
|
||||
|
||||
// 选择分区
|
||||
selectSection: function(el) {
|
||||
document.querySelectorAll('#sectionSelector .vr-spec-option').forEach(function (item) {
|
||||
item.classList.remove('selected');
|
||||
});
|
||||
el.classList.add('selected');
|
||||
this.currentSection = el.dataset.section;
|
||||
this.filterSeats();
|
||||
},
|
||||
|
||||
// 根据选择过滤座位
|
||||
filterSeats: function() {
|
||||
var self = this;
|
||||
document.querySelectorAll('.vr-seat.available').forEach(function(el) {
|
||||
var seatKey = el.dataset.seatKey;
|
||||
var seatInfo = self.seatSpecMap[seatKey] || {};
|
||||
|
||||
var matchSession = true, matchVenue = true, matchSection = true;
|
||||
|
||||
if (self.currentSession) {
|
||||
matchSession = false;
|
||||
for (var i = 0; i < seatInfo.spec.length; i++) {
|
||||
if (seatInfo.spec[i].type === '$vr-场次' && seatInfo.spec[i].value === self.currentSession) {
|
||||
matchSession = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.currentVenue) {
|
||||
matchVenue = false;
|
||||
for (var i = 0; i < seatInfo.spec.length; i++) {
|
||||
if (seatInfo.spec[i].type === '$vr-场馆' && seatInfo.spec[i].value === self.currentVenue) {
|
||||
matchVenue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.currentSection) {
|
||||
matchSection = false;
|
||||
for (var i = 0; i < seatInfo.spec.length; i++) {
|
||||
if (seatInfo.spec[i].type === '$vr-分区' && seatInfo.spec[i].value === self.currentSection) {
|
||||
matchSection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchSession && matchVenue && matchSection && seatInfo.inventory > 0) {
|
||||
el.style.opacity = '1';
|
||||
el.style.pointerEvents = 'auto';
|
||||
} else {
|
||||
el.style.opacity = '0.3';
|
||||
el.style.pointerEvents = 'none';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 渲染场次列表(保留向后兼容)
|
||||
renderSessions: function() {
|
||||
var specData = <?php echo json_encode($goods_spec_data ?? []); ?> || [];
|
||||
var html = '';
|
||||
|
|
|
|||
Loading…
Reference in New Issue