260 lines
10 KiB
Markdown
260 lines
10 KiB
Markdown
|
|
# Phase 3 前端模板开发计划
|
|||
|
|
|
|||
|
|
> 日期:2026-04-20 | 状态:进行中
|
|||
|
|
> 背景:Council 调研结论 + CSS 样式机制确认 → Demo 快速落地
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、调研结论摘要(Council 651e0bf2)
|
|||
|
|
|
|||
|
|
### Q2 — 单订单多SKU(多座位选择的前提)
|
|||
|
|
|
|||
|
|
**结论:✅ 可行,走购物车路线**
|
|||
|
|
|
|||
|
|
ShopXO `BuyService.php:86` 循环处理 `goods_data` 数组,每行独立 `spec_base_id`。现有 `ticket_detail.html` Plan A 代码已写好,但 `submit()` 函数有 bug:只把第一个座位编码进 URL,后续座位丢失。
|
|||
|
|
|
|||
|
|
**最小改动(Demo 1天可上线):**
|
|||
|
|
- 修复 `submit()`:将 `goodsParamsList` 整体编码,POST 到购物车 `CartSave`,再跳转合并支付
|
|||
|
|
- 绕过 `OrderSplitService` 拆单风险(购物车结算路径不触发按仓库拆单)
|
|||
|
|
|
|||
|
|
### Q1 — ShopXO 自定义模板最佳实践
|
|||
|
|
|
|||
|
|
**结论:原生 PHP + 内联 JS,渐进增强**
|
|||
|
|
|
|||
|
|
- ShopXO view/goods/ 模板使用原生 PHP + 原生 JS,session/buy 控制器直接 render
|
|||
|
|
- 不走 DIY 设计器(只支持静态 HTML 区块,无法参数化)
|
|||
|
|
- H5 直接浏览器预览,无需构建
|
|||
|
|
|
|||
|
|
### Q3 — 第三方无代码构建服务
|
|||
|
|
|
|||
|
|
**结论:辅助有限,座位图等核心交互必须手写**
|
|||
|
|
|
|||
|
|
- 无代码服务适合静态展示区块(票务介绍、艺人信息图)
|
|||
|
|
- 座位图等高交互组件无法用无代码工具精确生成
|
|||
|
|
- 生成代码后需后处理:路径替换 + 变量注入
|
|||
|
|
|
|||
|
|
### Q4 — uni-app 兼容性技术栈选型
|
|||
|
|
|
|||
|
|
**结论:fork shopxo-uniapp,票务页面自研**
|
|||
|
|
|
|||
|
|
- fork `shopxo-uniapp` → `vr-shopxo-uniapp`
|
|||
|
|
- 票务页面(ticket-seat / ticket-wallet / ticket-verify)自研 Vue 3 组件
|
|||
|
|
- 商城标准页面复用 shopxo-uniapp 原生实现
|
|||
|
|
- CSS 一致(H5/小程序都基于 WebView)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、CSS 样式注入机制(ShopXO 官方能力)
|
|||
|
|
|
|||
|
|
### 三层注入体系
|
|||
|
|
|
|||
|
|
| 层级 | 机制 | 甲方操作入口 |
|
|||
|
|
|------|------|------------|
|
|||
|
|
| **CSS 变量** | `header_style_root.html` 定义 `:root` 变量,后台主题配置可改 | ShopXO 后台「主题配色」 |
|
|||
|
|
| **插件 CSS Hook** | `plugins_css_data` 钩子注入独立 CSS 文件 | 替换 `static/plugins/vr_ticket/css/ticket.css` |
|
|||
|
|
| **内联 `<style>`** | 当前 `.vr-ticket-page` 样式块,完全隔离 | 直接修改 `ticket_detail.html` |
|
|||
|
|
|
|||
|
|
### CSS 变量体系(ShopXO 官方)
|
|||
|
|
|
|||
|
|
`header_style_root.html` 定义了完整的 CSS 变量系统:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* 主色 */
|
|||
|
|
--color-main: #E22C08; /* 可在后台改为甲方品牌色 */
|
|||
|
|
--color-main-light: #ffe3de;
|
|||
|
|
--color-main-hover: #EA6B52;
|
|||
|
|
|
|||
|
|
/* 圆角 */
|
|||
|
|
--border-radius-sm: 0.2rem;
|
|||
|
|
--border-radius: 0.4rem;
|
|||
|
|
--border-radius-lg: 0.8rem;
|
|||
|
|
|
|||
|
|
/* 阴影 */
|
|||
|
|
--box-shadow: 0 5px 20px rgba(50,55,58,0.1);
|
|||
|
|
--box-shadow-sm: 0 2px 8px rgba(50,55,58,0.1);
|
|||
|
|
--box-shadow-lg: 0 8px 34px rgba(50,55,58,0.1);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
vr_ticket 模板内的 `.vr-ticket-page` 可以直接引用这些变量,实现主题色统一。例如:
|
|||
|
|
```css
|
|||
|
|
.vr-purchase-btn {
|
|||
|
|
background: var(--color-main); /* 继承 ShopXO 主题色 */
|
|||
|
|
border-radius: var(--border-radius-lg);
|
|||
|
|
box-shadow: var(--box-shadow-sm);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 插件 CSS Hook(推荐方案)
|
|||
|
|
|
|||
|
|
在插件 service 中注册 `plugins_css_data` 钩子,加载独立 CSS 文件:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// plugins/vr_ticket/hook/ViewGoodsSpiderCss.php
|
|||
|
|
public function handle()
|
|||
|
|
{
|
|||
|
|
return 'plugins/vr_ticket/css/ticket.css';
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
甲方样式微调时,只需替换 `static/plugins/vr_ticket/css/ticket.css`,不需要改 PHP 模板。
|
|||
|
|
|
|||
|
|
### 当前 ticket_detail.html 样式结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ticket_detail.html
|
|||
|
|
├── <style> 完全独立的内联样式块(.vr-ticket-page 等)
|
|||
|
|
├── HTML 结构(.vr-ticket-page #vrTicketApp)
|
|||
|
|
├── 内联 JS(vrTicketApp 对象)
|
|||
|
|
└── ModuleInclude('public/footer')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
样式完全隔离,不受 ShopXO 升级影响。甲方设计师可以专注修改 CSS,不需要理解 PHP 模板逻辑。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、Demo 交付计划(最小可行方案)
|
|||
|
|
|
|||
|
|
### 目标:1天内上线可演示的多座位下单 Demo
|
|||
|
|
|
|||
|
|
### 当前代码状态
|
|||
|
|
|
|||
|
|
- `ticket_detail.html` 已有 Plan A 代码(submit 函数存在 URL 编码 bug)
|
|||
|
|
- 座位图渲染正常(A/B/C 三排 + 舞台 + 颜色分区 + 选座 UI + 观演人表单)
|
|||
|
|
- `loadSoldSeats()` 是 TODO,需要后端配合
|
|||
|
|
|
|||
|
|
### Demo 交付清单
|
|||
|
|
|
|||
|
|
#### P0 — 必须完成(Demo 当天)
|
|||
|
|
|
|||
|
|
| 任务 | 文件 | 说明 | 优先级 |
|
|||
|
|
|------|------|------|--------|
|
|||
|
|
| **修复 submit() bug** | `ticket_detail.html` | 当前只传第一个座位,需整体编码 goodsParamsList | 🔴 P0 |
|
|||
|
|
| **购物车路由接通** | `ticket_detail.html` | 改用 `CartSave` API 提交多座位,跳转合并支付 | 🔴 P0 |
|
|||
|
|
| **场次切换重置已选座位** | `ticket_detail.html` | `selectSession()` 调用座位重置逻辑(已有代码未调用) | 🔴 P0 |
|
|||
|
|
| **座位类型图例** | `ticket_detail.html` | 已完成 ✅,确认正常显示 | ✅ 已完成 |
|
|||
|
|
| **购买栏按钮状态联动** | `ticket_detail.html` | 已实现 ✅,`disabled` 状态根据选座数量变化 | ✅ 已完成 |
|
|||
|
|
|
|||
|
|
#### P1 — Demo 当天完成后继续
|
|||
|
|
|
|||
|
|
| 任务 | 文件 | 说明 | 优先级 |
|
|||
|
|
|------|------|------|--------|
|
|||
|
|
| **loadSoldSeats() 实现** | `ticket_detail.html` + 后端 | AJAX 调用后端接口,标记已售座位 | 🟡 P1 |
|
|||
|
|
| **座位图缩放/拖拽交互** | `ticket_detail.html` | 原生 JS < 200 行实现 | 🟡 P1 |
|
|||
|
|
| **CSS 样式文件分离** | `static/vr_ticket/css/ticket.css` | 从内联 `<style>` 抽离,通过 `plugins_css_data` 钩子注册 | 🟡 P1 |
|
|||
|
|
| **甲方主题色变量接入** | `ticket_detail.html` <style> | 将硬编码色值改为 `var(--color-main)` 等变量 | 🟡 P1 |
|
|||
|
|
|
|||
|
|
#### P2 — 后续迭代
|
|||
|
|
|
|||
|
|
| 任务 | 说明 | 优先级 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| shopxo-uniapp fork | 建立 `vr-shopxo-uniapp` 项目骨架 | 🟢 P2 |
|
|||
|
|
| ticket-seat.vue | uni-app 选座核心组件 | 🟢 P2 |
|
|||
|
|
| B 端核销页 | 小程序扫码核销页面 | 🟢 P2 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、关键技术细节
|
|||
|
|
|
|||
|
|
### 4.1 submit() 修复方案
|
|||
|
|
|
|||
|
|
**当前 bug:** `location.href = checkoutUrl + '&goods_params=' + encodeURIComponent(goodsParams)`
|
|||
|
|
URL 方式只能传字符串,多座位数据会被截断或丢失。
|
|||
|
|
|
|||
|
|
**修复方案:** 走购物车 API + 合并支付
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
submit: function() {
|
|||
|
|
// 1. 收集所有座位数据(现有代码正常)
|
|||
|
|
var goodsParamsList = this.selectedSeats.map(function(seat, i) {
|
|||
|
|
var specBaseId = self.specBaseIdMap[seat.seatKey] || 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: { seatKey: seat.seatKey, label: seat.label, price: seat.price }
|
|||
|
|
})
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 2. 逐座提交到购物车
|
|||
|
|
var deferreds = goodsParamsList.map(function(params) {
|
|||
|
|
return $.post(__goods_cart_save_url__, {
|
|||
|
|
goods_id: params.goods_id,
|
|||
|
|
spec_id: params.spec_base_id,
|
|||
|
|
stock: params.stock,
|
|||
|
|
// extension_data 作为自定义字段存储
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 3. 全部成功后跳转合并支付
|
|||
|
|
$.when.apply($, deferreds).done(function() {
|
|||
|
|
location.href = __root__ + '?s=index/cart/index';
|
|||
|
|
}).fail(function() {
|
|||
|
|
alert('座位已被占用,请重新选择');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**为什么走购物车路线:**
|
|||
|
|
- `BuyCart` → `BuyTypeGoodsList` → 直接调用 `BuyGoods`,完美支持多 `goods_data` 行
|
|||
|
|
- 购物车结算路径不触发 `OrderSplitService` 按仓库拆单(只按商品拆)
|
|||
|
|
- `plugins_service_order_pay_success_handle_end` 钩子正常触发,QR 票生成不受影响
|
|||
|
|
|
|||
|
|
### 4.2 CSS 变量主题化方案
|
|||
|
|
|
|||
|
|
当前 `ticket_detail.html` 内联样式中的硬编码色值,全部改为 CSS 变量:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* 改造前 */
|
|||
|
|
.vr-purchase-btn { background: linear-gradient(135deg, #409eff, #3b8ef8); }
|
|||
|
|
|
|||
|
|
/* 改造后 */
|
|||
|
|
.vr-purchase-btn {
|
|||
|
|
background: linear-gradient(135deg, var(--color-main), var(--color-main-hover));
|
|||
|
|
border-radius: var(--border-radius-lg);
|
|||
|
|
box-shadow: var(--box-shadow-sm);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
甲方想要调整主题色时,有三条路:
|
|||
|
|
1. **后台改**:ShopXO 管理后台 → 主题配色 → 自动同步到所有 `var(--color-*)` 变量
|
|||
|
|
2. **文件改**:替换 `static/plugins/vr_ticket/css/ticket.css`
|
|||
|
|
3. **代码改**:直接修改 `ticket_detail.html` 的 `<style>` 块
|
|||
|
|
|
|||
|
|
### 4.3 specBaseIdMap 降级策略
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
var specBaseId = self.specBaseIdMap[seat.seatKey] || self.sessionSpecId;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 如果 `seatKey`(如 "A_1")在 `specBaseIdMap` 中有对应记录 → 座位级 SKU(精确到每个座位)
|
|||
|
|
- 如果 `specBaseIdMap` 中没有(如后台未批量创建座位规格)→ 降级到 `sessionSpecId`(Zone 级别,同一 zone 全部座位共享一个 SKU)
|
|||
|
|
|
|||
|
|
**后台需要提前创建座位规格**,vr_ticket 后台管理界面需增加「批量生成座位规格」功能。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、文件清单
|
|||
|
|
|
|||
|
|
| 文件 | 当前状态 | 下一步 |
|
|||
|
|
|------|---------|--------|
|
|||
|
|
| `shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html` | Plan A 代码有 bug | 修复 submit(),接购物车 |
|
|||
|
|
| `shopxo/app/plugins/vr_ticket/static/css/ticket.css` | 不存在 | 从 ticket_detail.html 抽离样式 |
|
|||
|
|
| `docs/council-research-output.md` | ✅ 已完成 | 无需修改 |
|
|||
|
|
| `shopxo/app/service/BuyService.php` | 参考 | 无需修改(走购物车路线) |
|
|||
|
|
| `shopxo/app/service/SeatSkuService.php` | 有缺陷(单模板模式) | 等待 Issue #15/#16 修复 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、技术风险
|
|||
|
|
|
|||
|
|
| 风险 | 严重程度 | 缓解 |
|
|||
|
|
|------|---------|------|
|
|||
|
|
| `submit()` 只传第一座位(已发现) | 🔴 高 | 修复 submit(),改走购物车 API |
|
|||
|
|
| OrderSplitService 拆单(多座位变多笔支付) | 🔴 高 | 购物车路线绕过 |
|
|||
|
|
| 座位级 SKU 后台未创建(specBaseIdMap 空) | 🟡 中 | 降级到 Zone 级别 + 后台增加批量生成功能 |
|
|||
|
|
| 甲方主题色调整后样式不一致 | 🟡 中 | CSS 变量化,所有色值引用 `var(--color-*)` |
|
|||
|
|
| shopxo-uniapp fork 官方更新同步成本 | 🟢 低 | 票务页面与商城页面目录隔离 |
|