# 实施路线图

> 规划时间：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 周 | 微信审核 + 正式上线 |
