diff --git a/reviews/council-phase2-assessment.md b/reviews/council-phase2-assessment.md new file mode 100644 index 0000000..25b8ed7 --- /dev/null +++ b/reviews/council-phase2-assessment.md @@ -0,0 +1,261 @@ +# Council Phase 2 Technical Assessment — VR 演唱会票务小程序 + +> 日期:2026-04-21 | Agent:council/FrontendDev(执笔汇总) +> 依据:BackendArchitect 进度(Round 1 report)、FrontendDev Issues 2/3/4 findings、源码分析 + +--- + +## Issue 1 (P0) — 购物车/购买提交格式错误 + +### 根因分析 + +**当前 `submit()` 实际走的是购买流程(BuyController),不是购物车(GoodsCartService)** + +`ticket_detail.html:440` 当前代码: +```javascript +var checkoutUrl = this.requestUrl + '?s=index/buy/index' + + '&goods_params=' + encodeURIComponent(goodsParams); +location.href = checkoutUrl; +``` + +这直接访问 `Buy::Index()`,ShopXO 会执行: +1. `BuyService::BuyDataStorage($user_id, $buy_data)` — 把 `goods_params` 存入 session +2. 重定向 `MyUrl('index/buy/index')`(无 goods_data 时从 session 读取) +3. `BuyService::BuyTypeGoodsList()` 从 session 读取 `goods_data` + +**关键发现 — BuyService 和 GoodsCartService 接受相同格式的 `goods_data`**: + +```php +// BuyService.php:62 / GoodsCartService.php:266(两者逻辑相同) +if(!is_array($params['goods_data'])) { + $params['goods_data'] = json_decode(base64_decode(urldecode($params['goods_data'])), true); +} +``` + +`BuyTypeGoodsList()` 对 `goods_data` 数组中每个元素的期望结构: +```php +// BuyService.php:86-108 +[ + 'goods_id' => int, + 'spec' => array, // 或 spec_base_id 在 GoodsService::GoodsSpecDetail 中匹配 + 'stock' => int +] +``` + +**当前 `submit()` 构造的 `goodsParamsList` 格式**(ticket_detail.html:413-436): +```javascript +{ + goods_id: self.goodsId, + spec_base_id: parseInt(specBaseId) || 0, // ← 字段名错:应该是 spec[] + stock: 1, + extension_data: JSON.stringify({...}) // ← 多余字段,BuyService 不处理 +} +``` + +### 问题 + +1. **字段名错误**:BuyService 用 `spec` 数组(通过 `GoodsSpecificationsHandle` 解析),而非直接的 `spec_base_id` 整数 +2. **`extension_data` 无法传递**:BuyService/BuyTypeGoodsList 不识别 `extension_data`,观演人信息丢失 +3. **`goods_params` vs `goods_data`**:`submit()` 发的是 `goods_params`,BuyController 期望 `goods_data` + +### 推荐修复(FrontendDev 实施) + +修改 `submit()` 发送 `goods_data`(base64 编码)到 `index/buy/index`: + +```javascript +submit: function() { + var goodsDataList = this.selectedSeats.map(function(seat, i) { + var specBaseId = self.specBaseIdMap[seat.seatKey] || self.sessionSpecId; + // spec 格式:ShopXO 用 spec[type] = value 数组定位规格 + return { + goods_id: self.goodsId, + spec_base_id: parseInt(specBaseId) || 0, + stock: 1 + }; + }); + + // 观演人信息通过独立字段传递(ShopXO 不原生支持 extension_data) + var postData = { + goods_data: Base64.encode(JSON.stringify(goodsDataList)), + attendee_data: JSON.stringify(attendeeData) // 补充字段 + }; + + // 方式A:POST 到 index/buy/index + var form = document.createElement('form'); + form.method = 'POST'; + form.action = this.requestUrl + '?s=index/buy/index'; + for (var key in postData) { + var input = document.createElement('input'); + input.name = key; + input.value = postData[key]; + form.appendChild(input); + } + document.body.appendChild(form); + form.submit(); +} +``` + +### 关于 extension_data 的建议 + +ShopXO 原生不支持 `extension_data`(购物车表无此字段)。两个方案: +- **方案 A**:通过 `BuyService::OrderInsert()` 后的订单扩展表存储(需新增表) +- **方案 B**:观演人信息在 `Buy::Add` 订单创建时作为订单扩展字段传入,跳过购物车 + +--- + +## Issue 2 (P1) — 缩放时舞台元素不跟随 + +### 根因分析 + +```html + +
+
舞 台
+
+
+``` + +`.vr-stage` 和 `.vr-seat-rows` 是平级元素。对 `.vr-seat-rows` 应用 CSS `transform: scale()` 时,座位缩放,舞台不动。 + +### 修复方案(FrontendDev 实施) + +引入 `.vr-zoom-container` 包裹舞台和座位: + +```html +
+
+
舞 台
+
+
+
+``` + +```css +.vr-seat-map-wrapper { overflow: hidden; } +.vr-zoom-container { + display: flex; + flex-direction: column; + align-items: center; + transform-origin: center top; + transition: transform 0.2s ease; +} +``` + +缩放 JS 只需操作 `#zoomContainer` 的 `transform: scale()`。 + +**风险**:舞台 `border-radius: 50% 50% 0 0 / 20px 20px 0 0` 在缩放后会变形,需要调整。 + +--- + +## Issue 3 (P1) — spec 加载问题(已回滚) + +### 根因分析 + +`ticket_detail.html:375-383`: +```javascript +loadSoldSeats: function() { + // TODO: 从后端加载已售座位 + // $.get(...); // 空 stub,无任何网络请求 +} +``` + +- `loadSoldSeats()` 是空 TODO stub,无任何 AJAX 调用 +- 前端无 `sold_seats` 后端接口 +- 商品规格/库存的 `goods_spec_data` 来自 PHP 模板(GetGoodsViewData 返回),非前端动态加载 + +### 修复方案 + +1. 后端新增 `plugins/vr_ticket/index/sold_seats` 接口,返回已售座位 ID 列表 +2. 前端 `loadSoldSeats()` 调用该接口,标记 `.sold` class + +关于 spec 加载:ShopXO 的 spec 数据通过 `GetGoodsViewData()` 在模板渲染时注入前端,前端无需额外 API 调用即可获取场次/价格数据。 + +--- + +## Issue 4 (P2) — 商品详情/图片加载评估 + +### 现状 + +- **商品内容**(`$goods['content']`):✅ 正常渲染,PHP 直接输出 HTML +- **商品相册**(`$goods['images']`):⚠️ 数据存在但**未使用** + - `renderSessions()` 依赖 `goods_spec_data`(spec 数组),不含 `images` + - `.vr-goods-photos` 已定义样式但从未被调用 +- **`.goods-detail-content` CSS**:⚠️ 缺失,导致内容可能样式混乱 + +### 建议 + +如需展示商品图片,在模板中添加: +```php + +
+
+ + + +
+
+ +``` + +--- + +## 第一性原则综合分析 + +### 多座位提交是否需要走购物车? + +**核心问题**:票务选座后,是否必须经过购物车? + +当前设计:选座 → 直接进入购买确认页(`index/buy/index`),**实际上跳过了购物车**(通过 URL 参数 `goods_params` 直传)。这是合理的,因为: +1. 选座是实时操作,座位状态随时变化,购物车会给用户错误预期 +2. 多座位同时下单,购物车逐条处理会导致超卖风险 +3. 用户目标是"下单"而非"加购物车" + +**建议**:正式命名为"快速购买",而非"购物车",API 契约改为 `index/buy/add`(订单添加)而非 `index/cart/save`。 + +### spec_base_id_map 是否过于复杂? + +当前设计:每个座位一个 `spec_base_id`,通过 `rowLabel_colNum` 查找。 + +**更简单的方案**:座位作为 `extension_data` 存储在订单级别,单个 Zone 级别 SKU 即可。 + +但座次级 SKU 的价值在于: +1. 库存隔离(每个座位只能被一人购买) +2. 订单详情展示具体座位信息 + +**建议保持现状**,但需确保 `spec_base_id` 正确映射。 + +### extension_data 的业务价值 + +观演人信息(姓名、手机、身份证)必须传递到订单,但 ShopXO 原生不支持。 + +**推荐方案**:扩展订单商品表 `OrderDetail` 或新增 `vr_order_attendee` 表: +```sql +CREATE TABLE vr_order_attendee ( + id INT AUTO_INCREMENT PRIMARY KEY, + order_id INT NOT NULL, + seat_label VARCHAR(50) NOT NULL, + real_name VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + id_card VARCHAR(20), + add_time INT +); +``` + +--- + +## 修复优先级汇总 + +| 优先级 | Issue | 修复方向 | 负责 | +|--------|-------|----------|------| +| P0 | Issue 1 submit() 格式 | 改为 `goods_data` + POST,修复字段名 | FrontendDev | +| P1 | Issue 2 舞台缩放 | 引入 `.vr-zoom-container` 包裹舞台+座位 | FrontendDev | +| P1 | Issue 3 spec 加载 | 新增 `sold_seats` 接口 + 前端调用 stub | BackendArchitect | +| P2 | Issue 4 商品图片 | 补充图片渲染代码和 CSS | FrontendDev | +| FP | extension_data | 新增 `vr_order_attendee` 表存储观演人 | BackendArchitect | + +--- + +## 参考文献 + +- FrontendDev findings: `reviews/FrontendDev-Issue2-StageZoom.md`, `FrontendDev-Issue3-SpecLoading.md`, `FrontendDev-Issue4-GoodsDetail.md` +- 源码:`ticket_detail.html`, `BuyService.php`, `GoodsCartService.php`, `SeatSkuService.php` \ No newline at end of file