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

422 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 实施路线图
> 规划时间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 周 | 微信审核 + 正式上线 |