vr-shopxo-plugin/docs/04_IMPLEMENTATION_ROADMAP.md

422 lines
12 KiB
Markdown
Raw Permalink Normal View History

# 实施路线图
> 规划时间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 4QR 生成)+ Phase 7联调+ 文档整合 |
---
## 十一、里程碑
| 里程碑 | 日期 | 交付物 |
|---|---|---|
| M1 | 第 1 周 | 插件跑通、数据库就绪 |
| M2 | 第 2 周 | 场次管理 + 购票流程 + QR 票发放 |
| M3 | 第 3 周 | B 端核销 + 票夹 + 联调测试 |
| M4 | 第 4 周 | 微信审核 + 正式上线 |