diff --git a/plan.md b/plan.md index cab820b..3063d9b 100644 --- a/plan.md +++ b/plan.md @@ -309,9 +309,9 @@ this.selectedSeats.forEach(function(seat) { ### 任务清单 -- [ ] **P0-A**: `BaseService::initGoodsSpecs()` — 修复商品 112 broken state `[Claimed: BackendArchitect]` -- [ ] **P0-B**: `SeatSkuService::BatchGenerate()` — 批量生成座位级 SKU `[Claimed: BackendArchitect]` -- [ ] **P1**: `ticket_detail.html` submit() 重构 — seat-level goods_params `[Claimed: FrontendDev]` +- [x] **P0-A**: `BaseService::initGoodsSpecs()` — 修复商品 112 broken state `[Claimed: BackendArchitect]` +- [x] **P0-B**: `SeatSkuService::BatchGenerate()` — 批量生成座位级 SKU `[Claimed: BackendArchitect]` +- [x] **P1**: `ticket_detail.html` submit() 重构 — seat-level goods_params `[Done: FrontendDev]` - [ ] **P1-Verification**: 前端实测验证(商品 112 购买流程) `[Claimed: FrontendDev]` ### 阶段划分 @@ -385,6 +385,19 @@ BackendArchitect 的 `BatchGenerate()` 返回值需包含: - Key: `row_col`(如 `"A_1"`) - Value: `{spec_base_id: number, zone_id: string, row: string, col: number}` +### P1 实现记录 + +**已完成的改动**(`ticket_detail.html`): + +1. **`renderSeatMap()`**:为每个座位 div 新增 `data-row-label` 和 `data-col-num` 属性,用于生成与 `specBaseIdMap` key 格式一致的 seatKey +2. **`toggleSeat()`**:将 seatKey 从 `rowIndex_colIndex`(如 `"0_0"`)改为 `rowLabel_colNum`(如 `"A_1"`),匹配 `specBaseIdMap` key 格式 +3. **`removeSeat()`**:改用 `[data-row-label][data-col-num]` 选择器查找座位元素 +4. **`submit()`**:从 Zone 级别提交(1 行 goods_params)改为座位级提交(N 行 goods_params,每座 stock=1,spec_base_id 从 `specBaseIdMap[seat.seatKey]` 获取);降级兜底:若 specBaseIdMap 无对应 key,使用 sessionSpecId + +**seatKey 格式约定**: +- 格式:`{rowLabel}_{colNum}`,例如 `"A_1"`, `"B_5"` +- 与 BackendArchitect `SeatSkuService::BatchGenerate()` 返回的 `spec_base_id_map` key 格式保持一致 + --- ## Round 3 安全审计结果(保留,仅供参考) diff --git a/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html b/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html index 85ff9c6..6a8f661 100644 --- a/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html +++ b/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html @@ -266,11 +266,11 @@ var color = seatInfo.color || '#409eff'; var price = seatInfo.price || 0; var label = seatInfo.label || ''; - var key = rowIndex + '_' + colIndex; rowsHtml += '
'; @@ -287,10 +287,15 @@ var row = el.dataset.row; var col = el.dataset.col; - var key = row + '_' + col; + var rowLabel = el.dataset.rowLabel; + var colNum = el.dataset.colNum; + var seatKey = rowLabel + '_' + colNum; // e.g. "A_1" — matches specBaseIdMap key format var seat = { row: parseInt(row), col: parseInt(col), + rowLabel: rowLabel, + colNum: parseInt(colNum), + seatKey: seatKey, // 用于 specBaseIdMap 查找 char: el.dataset.char, price: parseFloat(el.dataset.price), label: el.dataset.label, @@ -301,7 +306,7 @@ // 取消选中 el.classList.remove('selected'); this.selectedSeats = this.selectedSeats.filter(function(s) { - return s.row !== seat.row || s.col !== seat.col; + return s.seatKey !== seatKey; }); } else { // 选中 @@ -341,7 +346,7 @@ var seat = this.selectedSeats[index]; if (seat) { var el = document.querySelector( - '[data-row="'+seat.row+'"][data-col="'+seat.col+'"]' + '[data-row-label="'+seat.rowLabel+'"][data-col-num="'+seat.colNum+'"]' ); if (el) el.classList.remove('selected'); this.selectedSeats.splice(index, 1); @@ -392,8 +397,7 @@ return; } - // 收集观演人信息 - var attendees = []; + // 收集观演人信息(按座位顺序索引) var inputs = document.querySelectorAll('#attendeeList input'); var attendeeData = {}; inputs.forEach(function(input) { @@ -402,20 +406,35 @@ if (!attendeeData[idx]) attendeeData[idx] = {}; attendeeData[idx][field] = input.value; }); - for (var k in attendeeData) { - attendees.push(attendeeData[k]); - } - // 构造订单扩展数据 - var extensionData = JSON.stringify({attendee: attendees, seats: this.selectedSeats}); + // 【Plan A】每座一行 goods_params,逐座提交 + // spec_base_id 从 specBaseIdMap[seatKey] 获取;若未生成 SKU(Plan B 过渡期),降级用 sessionSpecId + var self = this; + var goodsParamsList = this.selectedSeats.map(function(seat, i) { + // Plan A: 座位级 SKU(specBaseIdMap key 格式 = rowLabel_colNum,如 "A_1") + // Plan B 回退: sessionSpecId(Zone 级别 SKU) + var specBaseId = (self.specBaseIdMap[seat.seatKey] || {}).spec_base_id || self.sessionSpecId; + var seatAttendee = attendeeData[i] || {}; + return { + goods_id: self.goodsId, + spec_base_id: parseInt(specBaseId) || 0, + stock: 1, + extension_data: JSON.stringify({ + attendee: seatAttendee, + seat: { + row: seat.row, + col: seat.col, + rowLabel: seat.rowLabel, + colNum: seat.colNum, + seatKey: seat.seatKey, + label: seat.label, + price: seat.price + } + }) + }; + }); - // 跳转到 ShopXO 结算页,附加扩展数据 - var goodsParams = JSON.stringify([{ - goods_id: this.goodsId, - spec_base_id: this.sessionSpecId, - stock: this.selectedSeats.length, - extension_data: extensionData - }]); + var goodsParams = JSON.stringify(goodsParamsList); var checkoutUrl = this.requestUrl + '?s=index/buy/index' + '&goods_params=' + encodeURIComponent(goodsParams);