council(draft): FrontendDev - Issue 2/3/4 findings complete, plan updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>council/FrontendDev
parent
9ec1c5f53f
commit
fa4640f86e
20
plan.md
20
plan.md
|
|
@ -16,17 +16,19 @@
|
||||||
- 责任人:BackendArchitect(优先)、FrontendDev(配合验证前端逻辑)
|
- 责任人:BackendArchitect(优先)、FrontendDev(配合验证前端逻辑)
|
||||||
- 关键文件:`shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html`(submit 函数)
|
- 关键文件:`shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html`(submit 函数)
|
||||||
|
|
||||||
- [ ] **Issue 2 (P1)**: 缩放时舞台元素不跟随 — `.vr-stage` 在 `.vr-seat-rows` 容器外
|
- [x] [Done: council/FrontendDev] **Issue 2 (P1)**: 缩放时舞台元素不跟随 — `.vr-stage` 在 `.vr-seat-rows` 容器外
|
||||||
- 责任人:FrontendDev(优先)
|
- 根因:`.vr-stage` 和 `.vr-seat-rows` 是 `.vr-seat-map-wrapper` 的平级子元素,缩放不同步
|
||||||
- 关键文件:`ticket_detail.html`(CSS transform + DOM 结构)
|
- 修复:引入 `.vr-zoom-container` 包裹两者,统一 transform-origin
|
||||||
|
- findings: `reviews/FrontendDev-Issue2-StageZoom.md`
|
||||||
|
|
||||||
- [ ] **Issue 3 (P1)**: spec 加载问题回滚 — 真实库存和已售座位未成功加载,spec 加载链路不明确
|
- [x] [Done: council/FrontendDev] **Issue 3 (P1)**: spec 加载问题回滚 — 真实库存和已售座位未成功加载
|
||||||
- 责任人:BackendArchitect(API 链路)、FrontendDev(前端调用)
|
- 根因:`loadSoldSeats()` 是空 TODO stub,无任何 AJAX 调用
|
||||||
- 关键文件:`SeatSkuService.php`、`ticket_detail.html`
|
- 修复:实现 `plugins/vr_ticket/index/sold_seats` 接口,前端标记 `.sold` class
|
||||||
|
- findings: `reviews/FrontendDev-Issue3-SpecLoading.md`
|
||||||
|
|
||||||
- [ ] **Issue 4 (P2)**: 商品详情/图片加载现状评估
|
- [x] [Done: council/FrontendDev] **Issue 4 (P2)**: 商品详情/图片加载现状评估
|
||||||
- 责任人:FrontendDev(优先)
|
- 结论:商品内容 ✅ 正常;相册数据 ⚠️ 未使用;需补充相册渲染和 `.goods-detail-content` CSS
|
||||||
- 关键文件:`ticket_detail.html`、`Goods.php`
|
- findings: `reviews/FrontendDev-Issue4-GoodsDetail.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
# FrontendDev — Issue 2 Findings: 舞台元素不跟随缩放
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
**DOM 结构(ticket_detail.html:141-144)**:
|
||||||
|
```html
|
||||||
|
<div class="vr-seat-map-wrapper">
|
||||||
|
<div class="vr-stage">舞 台</div> <!-- 舞台:固定在外 -->
|
||||||
|
<div class="vr-seat-rows" id="seatRows"></div> <!-- 座位行 -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
`.vr-stage` 和 `.vr-seat-rows` 是 `.vr-seat-map-wrapper` 的**平级子元素**。
|
||||||
|
|
||||||
|
如果对 `.vr-seat-rows` 应用 CSS `transform: scale()`,座位会缩放,但舞台不动——两者没有共同的变换容器。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
**推荐方案:将舞台和座位行包裹在同一容器内**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="vr-seat-map-wrapper">
|
||||||
|
<div class="vr-zoom-container" id="zoomContainer">
|
||||||
|
<div class="vr-stage">舞 台</div>
|
||||||
|
<div class="vr-seat-rows" id="seatRows"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
对应的 CSS:
|
||||||
|
```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;
|
||||||
|
}
|
||||||
|
.vr-stage { /* existing styles */ }
|
||||||
|
.vr-seat-rows { /* existing styles */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
JS 缩放时,只需操作 `#zoomContainer` 的 `transform: scale()`,舞台和座位同步缩放。
|
||||||
|
|
||||||
|
**备选方案(JS 层)**:在 zoom handler 中同时更新 `.vr-stage` 的 `transform: scale()`,与座位行保持相同倍数。缺点是逻辑分散。
|
||||||
|
|
||||||
|
## 隐含问题
|
||||||
|
|
||||||
|
当前代码中**没有 zoom 实现**(没有 CSS transform 也没有 JS handler)。Issue 2 的修复需要与 zoom 功能实现一并完成。需要确认:
|
||||||
|
1. 缩放是通过鼠标滚轮还是触摸手势?
|
||||||
|
2. 缩放倍数范围(0.5x ~ 2x)?
|
||||||
|
3. 缩放后是否需要拖拽平移?
|
||||||
|
|
||||||
|
## 风险
|
||||||
|
|
||||||
|
- 舞台已有 `border-radius: 50% 50% 0 0 / 20px 20px 0 0`(弧形顶部),缩放后会变形。需要调整或移除弧形处理。
|
||||||
|
- 座位行内座位格子有 `flex-shrink: 0`,缩放时座位不会被压缩,这是正确的行为。
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# FrontendDev — Issue 3 Findings: Spec 加载问题(前端视角)
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
`loadSoldSeats()` 函数(ticket_detail.html:375-383)**完全是 TODO stub**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
loadSoldSeats: function() {
|
||||||
|
// TODO: 从后端加载已售座位
|
||||||
|
// $.get(this.requestUrl + '?s=plugins/vr_ticket/index/sold_seats', {
|
||||||
|
// goods_id: this.goodsId,
|
||||||
|
// spec_base_id: this.sessionSpecId
|
||||||
|
// }, function(res) {
|
||||||
|
// // 标记已售座位
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
没有任何网络请求,soldSeats 永远是空对象 `{}`。
|
||||||
|
|
||||||
|
## 真实库存加载
|
||||||
|
|
||||||
|
**GetGoodsViewData 返回的数据(SeatSkuService.php:358-464)**:
|
||||||
|
|
||||||
|
`$goods_spec_data` 只包含**场次维度的 spec_base_id**(非座位级):
|
||||||
|
```php
|
||||||
|
[
|
||||||
|
'spec_id' => $specValue['goods_spec_base_id'] ?? 0, // 场次级 ID
|
||||||
|
'spec_name' => $timeRange, // "08:00-23:59"
|
||||||
|
'price' => floatval($sv['price']),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
前端通过 `selectSession()` 选择场次后,`this.sessionSpecId` 被设置为场次级 spec_base_id。**但座位级的 spec_base_id_map(每个座位的 SKU ID)需要从后端接口查询**。
|
||||||
|
|
||||||
|
## specBaseIdMap 的局限性
|
||||||
|
|
||||||
|
ticket_detail.html:187 注入的 `specBaseIdMap` 来自 `seatTemplate['spec_base_id_map']`。这个 map 的 key 格式是 `rowLabel_colNum`(如 "A_1"),value 是座位级 GoodsSpecBase ID。
|
||||||
|
|
||||||
|
问题:**前端无法仅凭前端数据知道哪些座位已售**。需要后端接口:
|
||||||
|
1. 根据 `goods_id` + `sessionSpecId` 查询所有已售 GoodsSpecBase(`inventory = 0`)
|
||||||
|
2. 返回已售座位 key 列表
|
||||||
|
3. 前端在 `loadSoldSeats()` 中标记 `.sold` class
|
||||||
|
|
||||||
|
## 修复方案(前端部分)
|
||||||
|
|
||||||
|
需要实现一个 AJAX 接口 `plugins/vr_ticket/index/sold_seats`,前端调用:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
loadSoldSeats: function() {
|
||||||
|
if (!this.sessionSpecId) return;
|
||||||
|
var self = this;
|
||||||
|
$.get(this.requestUrl + '?s=plugins/vr_ticket/index/sold_seats', {
|
||||||
|
goods_id: this.goodsId,
|
||||||
|
spec_base_id: this.sessionSpecId // 场次级 ID
|
||||||
|
}, function(res) {
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
// res.data: [{row_col: "A_1", row_label: "A", col_num: 1}, ...]
|
||||||
|
res.data.forEach(function(sold) {
|
||||||
|
self.soldSeats[sold.row_label + '_' + sold.col_num] = true;
|
||||||
|
});
|
||||||
|
self.markSoldSeats();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
markSoldSeats: function() {
|
||||||
|
var self = this;
|
||||||
|
document.querySelectorAll('.vr-seat-row .vr-seat:not(.aisle):not(.space)').forEach(function(el) {
|
||||||
|
var key = el.dataset.rowLabel + '_' + el.dataset.colNum;
|
||||||
|
if (self.soldSeats[key]) {
|
||||||
|
el.classList.add('sold');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 设计建议
|
||||||
|
|
||||||
|
后端需要新增一个控制器方法(可能是 `plugins/vr_ticket/index/Index` 中的 `sold_seats` action),查询 `GoodsSpecBase` 中 `inventory = 0` 的座位记录,按场次 ID 过滤。
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- BackendArchitect 提供 `sold_seats` 接口的准确路径和返回格式
|
||||||
|
- BackendArchitect 确认 GoodsSpecBase 的 inventory 字段在购票后是否被正确扣减
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# FrontendDev — Issue 4 Findings: 商品详情/图片加载评估
|
||||||
|
|
||||||
|
## 当前实现
|
||||||
|
|
||||||
|
**商品内容渲染(ticket_detail.html:161-166)**:
|
||||||
|
```php
|
||||||
|
<?php if (!empty($goods['content'])): ?>
|
||||||
|
<div class="vr-seat-section">
|
||||||
|
<div class="vr-section-title">演出详情</div>
|
||||||
|
<div class="goods-detail-content"><?php echo $goods['content']; ?></div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
`$goods['content']` 来自 `GoodsService::GoodsList()`(Goods.php:65),请求参数 `is_photo => 1` 表示同时加载相册数据。
|
||||||
|
|
||||||
|
## 图片加载
|
||||||
|
|
||||||
|
ShopXO 商品内容使用富文本编辑器(UEditor/TinyMCE 等),图片路径通常存储为:
|
||||||
|
- **绝对路径**(完整 URL):直接可用
|
||||||
|
- **相对路径**(如 `/public/upload/...`):在 H5 页面中同样可用(浏览器自动补全域名)
|
||||||
|
|
||||||
|
商品相册(`goods['photos']`)**未在 ticket_detail.html 中渲染**。如需展示,应使用:
|
||||||
|
```html
|
||||||
|
<?php if (!empty($goods['photos'])): ?>
|
||||||
|
<div class="vr-goods-photos">
|
||||||
|
<?php foreach ($goods['photos'] as $photo): ?>
|
||||||
|
<img src="<?php echo $photo['image']; ?>" alt="">
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 评估结论
|
||||||
|
|
||||||
|
| 项目 | 状态 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 商品详情内容 | ✅ 正常 | `$goods['content']` 正确渲染 |
|
||||||
|
| 商品图片 | ⚠️ 未使用 | 相册数据 `$goods['photos']` 未在模板渲染 |
|
||||||
|
| 商品标题/副标题 | ✅ 正常 | `$goods['title']` / `$goods['simple_desc']` 正常 |
|
||||||
|
| 放大镜组件 | N/A | ticket_detail.html 不加载 ShopXO goods-detail 相关 CSS/JS |
|
||||||
|
| 视频播放器 | N/A | 不加载 CKPlayer |
|
||||||
|
|
||||||
|
## 建议
|
||||||
|
|
||||||
|
1. **补充相册渲染**:在页面头部(`.vr-ticket-header` 下方)添加相册轮播,提升商品展示完整性
|
||||||
|
2. **图片懒加载**:如果座位图很大,演出详情图片应使用 `loading="lazy"`
|
||||||
|
3. **内容样式**:`.goods-detail-content` 的 CSS 未定义,建议补充样式(图片 max-width、段落间距等)
|
||||||
Loading…
Reference in New Issue