# vr-shopxo-plugin 前端代码评审报告 > 评审人:FrontendDev > 日期:2026-04-15 > 视角:HTML/CSS/JS 质量 / 座位图渲染逻辑 / 响应式设计 / 用户体验 / 观演人表单安全 > 交叉参考:已合并 SecurityEngineer 和 BackendArchitect 报告,两者发现高度一致,以下从前端视角补充独立发现 --- ## 一、执行摘要 vr-shopxo-plugin 的票务详情页(ticket_detail.html)承担了座位选择、场次切换、观演人信息收集等核心交互。作为用户购票流程的唯一入口,其代码质量直接影响用户体验和系统安全性。 经过全面评审,发现**2 个严重前端问题、4 个中等问题、5 项改进建议**。最关键的是**购票参数前端计算无服务端验签**,可导致价格篡改攻击;座位图渲染存在未处理的边界情况,CSS 缺乏响应式适配,移动端体验较差。 --- ## 二、票务详情页(ticket_detail.html)评审 ### 2.1 🔴 严重 — 购票参数前端计算,价格可被篡改 **位置:** 第 384-422 行 `submit()` 函数 ```javascript var checkoutUrl = this.requestUrl + '?s=index/buy/index' + '&goods_params=' + encodeURIComponent(goodsParams); location.href = checkoutUrl; ``` **问题分析:** 整个购票参数(goods_id、spec_base_id、stock、extension_data)由前端 JavaScript 计算后拼接 URL 跳转至 ShopXO 结算页。服务端**不重新计算价格**,完全信任客户端数据。 攻击者可通过以下步骤以 0.01 元购买任意座位: 1. 打开浏览器开发者工具 2. 在控制台执行: ```javascript // 修改座位价格为 0.01 vrTicketApp.selectedSeats.forEach(s => s.price = 0.01); vrTicketApp.submit(); ``` 3. 服务端收到 `goods_params` 中的 `stock` 和 `extension_data`,直接使用,不验价 **影响:** - 价格篡改漏洞(已由 BackendArchitect 标记,本报告从 JS 层面量化攻击路径) - 前端座位数量无服务端校验,可超购 - `extension_data` 中的 `seat_info` 可伪造(客户端直接写入 JSON) **修复建议:** ```javascript // 方案一:改为 POST 请求,服务端验价 $.post(this.requestUrl + '?s=plugins/vr_ticket/index/create_ticket_order', { goods_id: this.goodsId, spec_base_id: this.sessionSpecId, seats: JSON.stringify(this.selectedSeats), attendees: JSON.stringify(attendees) }, function(res) { if (res.code == 0) { location.href = res.data.checkout_url; } }); // 方案二:添加 HMAC 签名 var payload = JSON.stringify({ goods_id: this.goodsId, seats: this.selectedSeats, timestamp: Date.now() }); var sig = CryptoJS.HmacSHA256(payload, clientSecret); location.href = checkoutUrl + '&sig=' + sig; ``` ### 2.2 🟡 中等 — 座位图渲染缺乏边界情况处理 **位置:** 第 255-282 行 `renderSeatMap()` **问题一:座位图数据空值未处理** ```javascript map.map.forEach(function(rowStr, rowIndex) { var chars = rowStr.split(''); chars.forEach(function(char, colIndex) { if (char === '_' || char === '-') { // 空白座位处理 } else { var seatInfo = seats[char] || {}; // ⚠️ seats 字典可能为空 var price = seatInfo.price || 0; // 价格为 0 时无座可买 // ... } }); }); ``` **场景:** 后端 `seat_map` JSON 中 `seats` 字段缺失或为空,则所有字符都映射到空对象 `{}`,价格为 0。用户在 UI 上看到座位,但点击后价格显示 ¥0,提交时服务端可能拒绝或接受零价订单。 **问题二:座位类型图例颜色可能不匹配** ```javascript sections.forEach(function(sec) { var color = sec.color || '#409eff'; legendHtml += '