council(execute): FrontendDev - Issue #9 P1 submit() refactor (seat-level goods_params)
- renderSeatMap(): add data-row-label + data-col-num attrs for specBaseIdMap key format - toggleSeat(): change seatKey from "0_0" (numeric) to "A_1" (label_colNum) to match specBaseIdMap - removeSeat(): use [data-row-label][data-col-num] selector - submit(): refactor from 1 goods_params (zone-level) to N entries (seat-level, stock=1) - Plan B fallback: if specBaseIdMap[key] missing, use sessionSpecId Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>refactor/vr-ticket-20260416
parent
1d7f600675
commit
93b70d4d50
19
plan.md
19
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 安全审计结果(保留,仅供参考)
|
||||
|
|
|
|||
|
|
@ -266,11 +266,11 @@
|
|||
var color = seatInfo.color || '#409eff';
|
||||
var price = seatInfo.price || 0;
|
||||
var label = seatInfo.label || '';
|
||||
var key = rowIndex + '_' + colIndex;
|
||||
|
||||
rowsHtml += '<div class="vr-seat '+(seatInfo.classes||'')+'" '+
|
||||
'style="background:'+color+'" '+
|
||||
'data-row="'+rowIndex+'" data-col="'+colIndex+'" '+
|
||||
'data-row-label="'+rowLabel+'" data-col-num="'+(colIndex+1)+'" '+
|
||||
'data-char="'+char+'" data-price="'+price+'" '+
|
||||
'data-seat-id="'+char+'" data-label="'+rowLabel+(colIndex+1)+'排'+(colIndex+1)+'座" '+
|
||||
'onclick="vrTicketApp.toggleSeat(this)"></div>';
|
||||
|
|
@ -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]);
|
||||
|
||||
// 【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
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// 构造订单扩展数据
|
||||
var extensionData = JSON.stringify({attendee: attendees, seats: this.selectedSeats});
|
||||
|
||||
// 跳转到 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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue