422 lines
12 KiB
Markdown
422 lines
12 KiB
Markdown
# 实施路线图
|
||
|
||
> 规划时间:2026-04-14
|
||
> 目标:基于 ShopXO 的 VR 演唱会票务 MVP
|
||
|
||
---
|
||
|
||
## 一、整体时间估算
|
||
|
||
| 阶段 | 内容 | 人天 | 可并行 | 累计 |
|
||
|---|---|---|---|---|
|
||
| Phase 0 | 环境搭建 + 插件骨架 | 2天 | — | 2天 |
|
||
| Phase 1 | 数据库设计 + 迁移 | 2天 | ✅ Phase 0 | 2天 |
|
||
| Phase 2 | 场次管理 CRUD + API | 3天 | ✅ Phase 1 | 3天 |
|
||
| Phase 3 | 下单钩子 + 观演人收集 | 3天 | ✅ Phase 2 | 4天 |
|
||
| Phase 4 | 支付回调 + QR 票生成 | 2天 | ✅ Phase 3 | 5天 |
|
||
| Phase 5 | uni-app 票务页面 | 3天 | ✅ Phase 3 | 6天 |
|
||
| Phase 6 | B 端核销页 + API | 2天 | ✅ Phase 4 | 6天 |
|
||
| Phase 7 | 联调 + 测试 + 部署 | 3天 | 需串行 | 9天 |
|
||
|
||
**预估**:Agent 集群并行 **1-2 周 MVP**,**3 周完整流程**
|
||
|
||
---
|
||
|
||
## 二、Phase 0 — 环境搭建
|
||
|
||
### 目标
|
||
|
||
本地跑通 ShopXO + shopxo-uniapp 开发环境
|
||
|
||
### 任务
|
||
|
||
1. **Docker 部署 ShopXO**(参考 `DEPLOYMENT.md`)
|
||
- PHP 8.0+ / MySQL 5.7+ / nginx
|
||
- 或使用虚拟主机安装包
|
||
|
||
2. **安装 shopxo-uniapp**
|
||
- HBuilderX 导入项目
|
||
- 配置 `request_url` 和 `static_url`
|
||
- 本地 H5 预览验证
|
||
|
||
3. **创建插件骨架**
|
||
```bash
|
||
mkdir -p app/plugins/vr_ticket/
|
||
cp plugin.json app/plugins/vr_ticket/
|
||
mkdir -p app/plugins/vr_ticket/{service,view,Admin/Controller,Api/Controller}
|
||
mkdir -p static/vr_ticket/
|
||
mkdir -p database/migrations/
|
||
```
|
||
|
||
4. **在 ShopXO 后台安装插件**
|
||
- 访问 `/admin/plugins/index`
|
||
- 上传插件 zip 或手动放置到 `app/plugins/vr_ticket/`
|
||
- 点击安装
|
||
|
||
### 验收
|
||
|
||
- ShopXO H5 前端正常访问
|
||
- shopxo-uniapp H5 预览正常
|
||
- 插件在后台可见
|
||
|
||
---
|
||
|
||
## 三、Phase 1 — 数据库设计
|
||
|
||
### 任务
|
||
|
||
创建插件迁移文件:
|
||
|
||
```sql
|
||
-- database/migrations/001_create_vr_events.sql
|
||
CREATE TABLE `vr_events` (
|
||
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
`goods_id` int UNSIGNED NOT NULL COMMENT 'ShopXO商品ID',
|
||
`name` varchar(255) NOT NULL COMMENT '活动名称',
|
||
`venue` varchar(255) COMMENT '场馆',
|
||
`cover_image` varchar(255) COMMENT '封面图',
|
||
`status` tinyint DEFAULT 1 COMMENT '状态(0禁用, 1启用)',
|
||
`created_at` int UNSIGNED DEFAULT 0,
|
||
`updated_at` int UNSIGNED DEFAULT 0,
|
||
KEY `goods_id` (`goods_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- database/migrations/002_create_vr_sessions.sql
|
||
CREATE TABLE `vr_sessions` (
|
||
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
`event_id` int UNSIGNED NOT NULL,
|
||
`session_time` datetime NOT NULL COMMENT '场次时间',
|
||
`total_stock` int UNSIGNED DEFAULT 0 COMMENT '总库存',
|
||
`available_stock` int UNSIGNED DEFAULT 0 COMMENT '可用库存',
|
||
`price` decimal(10,2) UNSIGNED DEFAULT 0 COMMENT '票价',
|
||
`status` tinyint DEFAULT 1,
|
||
`created_at` int UNSIGNED DEFAULT 0,
|
||
`updated_at` int UNSIGNED DEFAULT 0,
|
||
KEY `event_id` (`event_id`),
|
||
KEY `session_time` (`session_time`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- database/migrations/003_create_vr_tickets.sql
|
||
CREATE TABLE `vr_tickets` (
|
||
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
`order_id` int UNSIGNED NOT NULL COMMENT '订单ID',
|
||
`order_no` char(60) NOT NULL,
|
||
`goods_id` int UNSIGNED NOT NULL,
|
||
`user_id` int UNSIGNED NOT NULL,
|
||
`event_id` int UNSIGNED NOT NULL,
|
||
`session_id` int UNSIGNED NOT NULL,
|
||
`ticket_code` char(36) NOT NULL COMMENT 'UUID票码',
|
||
`qr_data` text COMMENT '加密QR内容',
|
||
`seat_info` varchar(255) COMMENT '座位信息',
|
||
`real_name` varchar(60) COMMENT '观演人姓名',
|
||
`phone` char(15) COMMENT '手机号',
|
||
`verify_status` tinyint DEFAULT 0 COMMENT '0未核销, 1已核销',
|
||
`verify_time` int UNSIGNED DEFAULT 0,
|
||
`verifier_id` int UNSIGNED DEFAULT 0,
|
||
`issued_at` int UNSIGNED DEFAULT 0,
|
||
`created_at` int UNSIGNED DEFAULT 0,
|
||
`updated_at` int UNSIGNED DEFAULT 0,
|
||
UNIQUE KEY `ticket_code` (`ticket_code`),
|
||
KEY `order_id` (`order_id`),
|
||
KEY `user_id` (`user_id`),
|
||
KEY `verify_status` (`verify_status`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- database/migrations/004_create_vr_verifiers.sql
|
||
CREATE TABLE `vr_verifiers` (
|
||
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
`user_id` int UNSIGNED NOT NULL COMMENT 'ShopXO用户ID',
|
||
`name` varchar(60) NOT NULL,
|
||
`status` tinyint DEFAULT 1,
|
||
`created_at` int UNSIGNED DEFAULT 0,
|
||
KEY `user_id` (`user_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
|
||
-- database/migrations/005_create_vr_verifications.sql
|
||
CREATE TABLE `vr_verifications` (
|
||
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||
`ticket_id` int UNSIGNED NOT NULL,
|
||
`ticket_code` char(36) NOT NULL,
|
||
`verifier_id` int UNSIGNED NOT NULL,
|
||
`verifier_name` varchar(60),
|
||
`event_id` int UNSIGNED,
|
||
`created_at` int UNSIGNED DEFAULT 0,
|
||
KEY `ticket_id` (`ticket_id`),
|
||
KEY `created_at` (`created_at`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||
```
|
||
|
||
### AI 参与度
|
||
|
||
✅ **90% AI 可生成**(妮可 agent 主导)
|
||
|
||
---
|
||
|
||
## 四、Phase 2 — 场次管理 CRUD
|
||
|
||
### 任务
|
||
|
||
1. **管理端页面**(后台)
|
||
- `Admin/Controller/EventController.php` — 活动管理
|
||
- `Admin/Controller/SessionController.php` — 场次管理
|
||
- `Admin/View/event_list.html` — 活动列表
|
||
- `Admin/View/session_edit.html` — 场次编辑
|
||
|
||
2. **C 端 API**
|
||
- `Api/Controller/EventController.php` — 活动详情
|
||
- `Api/Controller/SessionController.php` — 可用场次列表
|
||
|
||
### API 设计
|
||
|
||
```
|
||
GET /?s=admin/vrticket/event/list
|
||
POST /?s=admin/vrticket/event/save
|
||
DELETE /?s=admin/vrticket/event/delete
|
||
|
||
GET /?s=admin/vrticket/session/list&event_id=8
|
||
POST /?s=admin/vrticket/session/save
|
||
DELETE /?s=admin/vrticket/session/delete
|
||
|
||
GET /?s=api/vrticket/event/detail&id=8
|
||
GET /?s=api/vrticket/session/available&goods_id=123
|
||
```
|
||
|
||
### AI 参与度
|
||
|
||
✅ **85% AI 可生成**(标准 CRUD,可套模板)
|
||
|
||
---
|
||
|
||
## 五、Phase 3 — 下单钩子 + 观演人收集
|
||
|
||
### 5.1 目标
|
||
|
||
在 ShopXO 下单流程中收集观演人信息
|
||
|
||
### 5.2 方案
|
||
|
||
利用 ShopXO 的「订单商品扩展表单」机制(`ordergoodsform` 插件的思路):
|
||
|
||
在 `plugins_view_goods_detail_base_sku_top` 注入观演人表单:
|
||
|
||
```html
|
||
<!-- 观演人信息收集(票务商品专用)-->
|
||
<div id="vr-ticket-attendee-form" class="vr-form">
|
||
<view class="form-title">观演人信息</view>
|
||
<view v-for="(item, index) in attendeeList" :key="index">
|
||
<input type="text" placeholder="姓名" v-model="item.real_name" />
|
||
<input type="idcard" placeholder="身份证号(选填)" v-model="item.id_card" />
|
||
<input type="phone" placeholder="手机号" v-model="item.phone" />
|
||
</view>
|
||
<view class="add-btn" @tap="addAttendee">+ 添加观演人</view>
|
||
</div>
|
||
```
|
||
|
||
### 5.3 数据收集流程
|
||
|
||
1. 用户在前端填写观演人信息
|
||
2. 前端将观演人数据存入本地(`uni.setStorage`)
|
||
3. 点击购买时,将观演人数据通过插件 API 暂存
|
||
4. 插件在 `plugins_service_buy_order_insert_begin` 钩子中将观演人数据写入 `vr_tickets` 表
|
||
|
||
### 5.4 钩子实现
|
||
|
||
```php
|
||
// 插件 Service/TicketService.php
|
||
public static function OnBeforeOrderInsert(&$params, &$order_data) {
|
||
// 检查是否有票务商品
|
||
$items = $order_data['items'] ?? [];
|
||
foreach ($items as $item) {
|
||
if (self::IsTicketGoods($item['goods_id'])) {
|
||
// 收集观演人信息,生成票码
|
||
$attendees = self::CollectAttendees($item);
|
||
self::CreateTickets($order_data['order_id'], $item, $attendees);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### AI 参与度
|
||
|
||
✅ **80% AI 可生成**(逻辑稍复杂,需与 ShopXO 订单流程对接)
|
||
|
||
---
|
||
|
||
## 六、Phase 4 — 支付回调 + QR 票生成
|
||
|
||
### 6.1 目标
|
||
|
||
支付成功后自动发放 QR 电子票
|
||
|
||
### 6.2 触发点
|
||
|
||
ShopXO 支付成功 → `plugins_service_buy_order_insert_success` 钩子
|
||
|
||
### 6.3 任务
|
||
|
||
```php
|
||
// 插件 Service/TicketService.php
|
||
public static function OnOrderPaid($order_id, $order_no) {
|
||
// 1. 查询该订单的所有票务商品
|
||
$tickets = self::GetPendingTickets($order_id);
|
||
|
||
foreach ($tickets as $ticket) {
|
||
// 2. 生成加密票码
|
||
$ticket_code = self::GenerateTicketCode(); // UUID v4
|
||
|
||
// 3. 加密 QR 内容
|
||
$qr_data = self::EncryptQrData([
|
||
'id' => $ticket['id'],
|
||
'code' => $ticket_code,
|
||
'event' => $ticket['event_id'],
|
||
'exp' => time() + 86400 * 30, // 30天有效期
|
||
]);
|
||
|
||
// 4. 更新数据库
|
||
Db::name('vr_tickets')
|
||
->where('id', $ticket['id'])
|
||
->update([
|
||
'ticket_code' => $ticket_code,
|
||
'qr_data' => $qr_data,
|
||
'issued_at' => time(),
|
||
]);
|
||
}
|
||
|
||
// 5. 发送通知(可选)
|
||
self::NotifyUser($order_id);
|
||
}
|
||
```
|
||
|
||
### AI 参与度
|
||
|
||
✅ **90% AI 可生成**(标准业务逻辑)
|
||
|
||
---
|
||
|
||
## 七、Phase 5 — uni-app 票务页面
|
||
|
||
### 7.1 任务
|
||
|
||
| 页面 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| 选座 + 购票 | `pages/ticket-buy/ticket-buy.vue` | 参考 goods-detail.vue |
|
||
| 座位选择组件 | `pages/ticket-buy/components/seat-selector.vue` | SVG/Canvas 座位图 |
|
||
| 观演人表单 | `pages/ticket-buy/components/attendee-form.vue` | 动态表单项 |
|
||
| 票夹 | `pages/ticket-wallet/ticket-wallet.vue` | 参考 user/order-list |
|
||
|
||
### 7.2 关键组件实现
|
||
|
||
**座位选择器**(最简单的 SVG 实现):
|
||
```vue
|
||
<template>
|
||
<view class="seat-map">
|
||
<svg viewBox="0 0 800 600" class="seat-svg">
|
||
<g v-for="seat in seats" :key="seat.id">
|
||
<rect
|
||
:x="seat.x"
|
||
:y="seat.y"
|
||
width="30"
|
||
height="30"
|
||
:fill="getSeatColor(seat.status)"
|
||
@tap="onSeatTap(seat)"
|
||
class="seat-rect"
|
||
/>
|
||
<text :x="seat.x + 15" :y="seat.y + 20" class="seat-label">
|
||
{{ seat.label }}
|
||
</text>
|
||
</g>
|
||
</svg>
|
||
<view class="seat-legend">
|
||
<view class="legend-item"><view class="dot available"></view>可选</view>
|
||
<view class="legend-item"><view class="dot selected"></view>已选</view>
|
||
<view class="legend-item"><view class="dot sold"></view>已售</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
```
|
||
|
||
### 7.3 接入商品详情页
|
||
|
||
修改 `pages/goods-detail/goods-detail.vue`:
|
||
- 检测 `goods.item_type === 'ticket'`
|
||
- 跳转到 `pages/ticket-buy/ticket-buy?goods_id=xxx`
|
||
|
||
或通过插件钩子注入选座 UI,覆盖原有的规格选择器。
|
||
|
||
### AI 参与度
|
||
|
||
✅ **90% AI 可生成**(标准 Vue 组件)
|
||
|
||
---
|
||
|
||
## 八、Phase 6 — B 端核销页
|
||
|
||
### 任务
|
||
|
||
1. **插件 API**:`Api/Controller/TicketController.php`
|
||
- `verify()` — 核销验证
|
||
|
||
2. **uni-app 核销页**:
|
||
- Fork `pages/plugins/realstore/check/check.vue`
|
||
- 改造成 `pages/plugins/vr-ticket-verify/check/check.vue`
|
||
- 调整 API 路径和返回处理
|
||
|
||
3. **后台核销统计**:
|
||
- `Admin/Controller/TicketController.php`
|
||
- `Admin/View/verification_list.html`
|
||
|
||
### AI 参与度
|
||
|
||
✅ **90% AI 可生成**(核心逻辑已参考 realstore 完整实现)
|
||
|
||
---
|
||
|
||
## 九、Phase 7 — 联调 + 测试 + 部署
|
||
|
||
### 9.1 联调清单
|
||
|
||
- [ ] 活动创建 → 商品关联
|
||
- [ ] 场次库存 → 商品 SKU 映射
|
||
- [ ] 前端选座 → 后端扣库存
|
||
- [ ] 微信支付 → 回调 → QR 票生成
|
||
- [ ] 票夹显示 → QR 码展示
|
||
- [ ] B 端扫码 → 核销状态更新
|
||
- [ ] C 端状态实时刷新
|
||
|
||
### 9.2 测试用例
|
||
|
||
| 用例 | 预期 |
|
||
|---|---|
|
||
| 正常购票流程 | 支付成功 → 收到 QR 票 |
|
||
| 并发抢票 | 库存不超卖 |
|
||
| 核销同一张票两次 | 第二次报错「已核销」 |
|
||
| QR 码过期 | 核销时报「票已过期」 |
|
||
| 退款后票失效 | 票状态更新为已退款 |
|
||
|
||
### 9.3 部署
|
||
|
||
- **PHP 虚拟主机**:上传插件 zip → 后台安装
|
||
- **shopxo-uniapp**:HBuilderX 发行 → 微信审核
|
||
|
||
---
|
||
|
||
## 十、Agent 分工建议
|
||
|
||
| Agent | 负责任务 |
|
||
|---|---|
|
||
| **李狗蛋**(MacBook Pro VM) | Phase 0 + Phase 2(场次 CRUD) |
|
||
| **妮可**(Intel MacBook) | Phase 1(数据库迁移脚本) |
|
||
| **小老D**(Proxmox Linux) | Phase 3 + Phase 6(钩子 + 核销) |
|
||
| **西莉娅**(本地 Hub) | Phase 4(QR 生成)+ Phase 7(联调)+ 文档整合 |
|
||
|
||
---
|
||
|
||
## 十一、里程碑
|
||
|
||
| 里程碑 | 日期 | 交付物 |
|
||
|---|---|---|
|
||
| M1 | 第 1 周 | 插件跑通、数据库就绪 |
|
||
| M2 | 第 2 周 | 场次管理 + 购票流程 + QR 票发放 |
|
||
| M3 | 第 3 周 | B 端核销 + 票夹 + 联调测试 |
|
||
| M4 | 第 4 周 | 微信审核 + 正式上线 |
|