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

12 KiB
Raw Permalink Blame 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 周 MVP3 周完整流程


二、Phase 0 — 环境搭建

目标

本地跑通 ShopXO + shopxo-uniapp 开发环境

任务

  1. Docker 部署 ShopXO(参考 DEPLOYMENT.md

    • PHP 8.0+ / MySQL 5.7+ / nginx
    • 或使用虚拟主机安装包
  2. 安装 shopxo-uniapp

    • HBuilderX 导入项目
    • 配置 request_urlstatic_url
    • 本地 H5 预览验证
  3. 创建插件骨架

    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 — 数据库设计

任务

创建插件迁移文件:

-- 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 注入观演人表单:

<!-- 观演人信息收集(票务商品专用)-->
<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 钩子实现

// 插件 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 任务

// 插件 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 实现):

<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. 插件 APIApi/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-uniappHBuilderX 发行 → 微信审核

十、Agent 分工建议

Agent 负责任务
李狗蛋MacBook Pro VM Phase 0 + Phase 2场次 CRUD
妮可Intel MacBook Phase 1数据库迁移脚本
小老DProxmox Linux Phase 3 + Phase 6钩子 + 核销)
西莉娅(本地 Hub Phase 4QR 生成)+ Phase 7联调+ 文档整合

十一、里程碑

里程碑 日期 交付物
M1 第 1 周 插件跑通、数据库就绪
M2 第 2 周 场次管理 + 购票流程 + QR 票发放
M3 第 3 周 B 端核销 + 票夹 + 联调测试
M4 第 4 周 微信审核 + 正式上线