vr-shopxo-plugin/reviews/council-phase2-assessment.md

8.8 KiB
Raw Blame History

Council Phase 2 Technical Assessment — VR 演唱会票务小程序

日期2026-04-21 | Agentcouncil/FrontendDev执笔汇总 依据BackendArchitect 进度Round 1 report、FrontendDev Issues 2/3/4 findings、源码分析


Issue 1 (P0) — 购物车/购买提交格式错误

根因分析

当前 submit() 实际走的是购买流程BuyController不是购物车GoodsCartService

ticket_detail.html:440 当前代码:

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

// 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 数组中每个元素的期望结构:

// BuyService.php:86-108
[
    'goods_id' => int,
    'spec'    => array,  // 或 spec_base_id 在 GoodsService::GoodsSpecDetail 中匹配
    'stock'   => int
]

当前 submit() 构造的 goodsParamsList 格式ticket_detail.html:413-436

{
    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_datasubmit() 发的是 goods_paramsBuyController 期望 goods_data

推荐修复FrontendDev 实施)

修改 submit() 发送 goods_database64 编码)到 index/buy/index

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)  // 补充字段
    };

    // 方式APOST 到 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) — 缩放时舞台元素不跟随

根因分析

<!-- ticket_detail.html:141-144 -->
<div class="vr-seat-map-wrapper">
    <div class="vr-stage">舞 台</div>      <!-- 平级 sibling -->
    <div class="vr-seat-rows" id="seatRows"></div>  <!-- 平级 sibling -->
</div>

.vr-stage.vr-seat-rows 是平级元素。对 .vr-seat-rows 应用 CSS transform: scale() 时,座位缩放,舞台不动。

修复方案FrontendDev 实施)

引入 .vr-zoom-container 包裹舞台和座位:

<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>
.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 只需操作 #zoomContainertransform: scale()

风险:舞台 border-radius: 50% 50% 0 0 / 20px 20px 0 0 在缩放后会变形,需要调整。


Issue 3 (P1) — spec 加载问题(已回滚)

根因分析

ticket_detail.html:375-383

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_dataspec 数组),不含 images
    • .vr-goods-photos 已定义样式但从未被调用
  • .goods-detail-content CSS⚠️ 缺失,导致内容可能样式混乱

建议

如需展示商品图片,在模板中添加:

<?php if (!empty($goods['images'])): ?>
<div class="vr-goods-info">
    <div class="vr-goods-photos">
        <?php foreach(array_slice(explode(',', $goods['images']), 0, 5) as $img): ?>
        <img src="<?php echo ResourcesService::AttachmentPathHandle($img); ?>">
        <?php endforeach; ?>
    </div>
</div>
<?php endif; ?>

第一性原则综合分析

多座位提交是否需要走购物车?

核心问题:票务选座后,是否必须经过购物车?

当前设计:选座 → 直接进入购买确认页(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 表:

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