diff --git a/shopxo/app/plugins/vr_ticket/Hook.php b/shopxo/app/plugins/vr_ticket/Hook.php index b81d7b5..89df8b9 100644 --- a/shopxo/app/plugins/vr_ticket/Hook.php +++ b/shopxo/app/plugins/vr_ticket/Hook.php @@ -19,7 +19,9 @@ class Hook // 订单支付成功处理 case 'plugins_service_order_pay_success_handle_end': + BaseService::log('Hook::handle triggered', ['order_id' => $params['order_id'] ?? $params['business_id'] ?? 'unknown'], 'info'); $ret = TicketService::onOrderPaid($params); + BaseService::log('Hook::handle result', ['ret' => $ret], 'info'); break; case 'plugins_service_order_detail_page_info': @@ -27,6 +29,11 @@ class Hook $ret = $this->InjectTicketCard($params); break; + case 'plugins_view_user_various_bottom': + // C端用户中心底部挂载票夹入口 + $ret = $this->InjectWalletLink($params); + break; + case 'plugins_service_order_delete_success': // 如果有删除拦截等 break; @@ -122,7 +129,9 @@ class Hook return; } - $userId = session('user_id'); + // 获取当前登录用户(ShopXO 标准方式) + $user = \app\service\UserService::LoginUserInfo(); + $userId = empty($user) ? null : $user['id']; if (empty($userId)) { return; } @@ -190,7 +199,7 @@ class Hook // JS $js = ''; $params['page_data']['ticket_js'] = $js; } + + /** + * 在用户中心底部挂载票夹入口链接 + */ + public function InjectWalletLink(&$params) + { + $hostUrl = \think\facade\Config::get('shopxo.host_url'); + + // 票夹入口 HTML - 直接返回 HTML 字符串 + // 正确的插件路由格式:?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=index&pluginsaction=wallet + $walletLink = '
' . + '' . + '🎫 我的电子票' . + '
'; + + return $walletLink; + } } ?> \ No newline at end of file diff --git a/shopxo/app/plugins/vr_ticket/service/TicketService.php b/shopxo/app/plugins/vr_ticket/service/TicketService.php index 07ea6d8..4fa7d54 100644 --- a/shopxo/app/plugins/vr_ticket/service/TicketService.php +++ b/shopxo/app/plugins/vr_ticket/service/TicketService.php @@ -9,6 +9,8 @@ namespace app\plugins\vr_ticket\service; +require_once __DIR__ . '/BaseService.php'; + class TicketService extends BaseService { /** @@ -22,7 +24,8 @@ class TicketService extends BaseService */ public static function onOrderPaid($params = []) { - $order_id = $params['business_id'] ?? ($params['business_ids'][0] ?? 0); + $order_id = $params['order_id'] ?? $params['business_id'] ?? ($params['business_ids'][0] ?? 0); + BaseService::log('onOrderPaid: called', ['order_id' => $order_id, 'params_keys' => array_keys($params)], 'info'); if (empty($order_id)) { BaseService::log('onOrderPaid: empty order_id', $params, 'warning'); return false; @@ -38,7 +41,7 @@ class TicketService extends BaseService // 查询订单明细(规格信息存储在 spec JSON 字段) $order_goods = \think\facade\Db::name('order_detail') ->where('order_id', $order_id) - ->select(); + ->select()->toArray(); if (empty($order_goods)) { BaseService::log('onOrderPaid: no order detail', ['order_id' => $order_id], 'error'); @@ -57,31 +60,50 @@ class TicketService extends BaseService $spec_name = ''; $spec_base_id = 0; + // 完整解析 5 维规格 + $parsed = [ + 'session' => '', + 'venue' => '', + 'studio' => '', + 'section' => '', + 'seat' => '', + ]; if (is_array($spec_list)) { - // 优先取座位号,其次分区名 foreach ($spec_list as $spec_item) { - $type = $spec_item['type'] ?? ''; + $type = $spec_item['type'] ?? ''; $value = $spec_item['value'] ?? ''; - if ($type === '$vr-座位号') { - $spec_name = $value; - break; - } elseif ($type === '$vr-分区' && !$spec_name) { - $spec_name = $value; + switch ($type) { + case '$vr-场次': $parsed['session'] = $value; break; + case '$vr-场馆': $parsed['venue'] = $value; break; + case '$vr-演播室': $parsed['studio'] = $value; break; + case '$vr-分区': $parsed['section'] = $value; break; + case '$vr-座位号': $parsed['seat'] = $value; break; } } } + // seat_info 格式:场次|场馆|演播室|分区|座位号(WalletService::parseSeatInfo 依赖此格式) + $seat_info = implode('|', [ + $parsed['session'], + $parsed['venue'], + $parsed['studio'], + $parsed['section'], + $parsed['seat'], + ]); + // goods_snapshot 完整快照(含全部 5 维) + $goods_snapshot = json_encode([ + 'goods_name' => $og['title'] ?? '', + 'item_type' => 'ticket', + 'session' => $parsed['session'], + 'venue' => $parsed['venue'], + 'studio' => $parsed['studio'], + 'section' => $parsed['section'], + 'seat' => $parsed['seat'], + 'price' => $og['price'] ?? 0, + ], JSON_UNESCAPED_UNICODE); - // 尝试通过座位名反向查找 spec_base_id - if ($spec_name) { - $spec_base = \think\facade\Db::name('goods_spec_value') - ->where('goods_id', $og['goods_id']) - ->where('value', $spec_name) - ->find(); - $spec_base_id = $spec_base['goods_spec_base_id'] ?? 0; - } - - $og['_parsed_spec_name'] = $spec_name; - $og['_parsed_spec_base_id'] = $spec_base_id; + $og['_parsed_spec_name'] = $parsed['seat'] ?: $parsed['section']; + $og['_parsed_seat_info'] = $seat_info; + $og['_parsed_goods_snapshot'] = $goods_snapshot; } unset($og); @@ -113,6 +135,12 @@ class TicketService extends BaseService { $spec_name = $og['_parsed_spec_name'] ?? ''; $spec_base_id = $og['_parsed_spec_base_id'] ?? 0; + $seat_info = $og['_parsed_seat_info'] ?? $spec_name; + $goods_snapshot = $og['_parsed_goods_snapshot'] ?? json_encode([ + 'goods_name' => $og['title'] ?? '', + 'spec_name' => $spec_name, + 'price' => $og['price'] ?? 0, + ], JSON_UNESCAPED_UNICODE); // P0-1 幂等保护:同一订单+同一座位名只发一张票 $existing = \think\facade\Db::name(BaseService::table('tickets')) @@ -135,15 +163,11 @@ class TicketService extends BaseService 'order_id' => $order['id'], 'order_no' => $order['order_no'], 'goods_id' => $og['goods_id'], - 'goods_snapshot' => json_encode([ - 'goods_name' => $og['title'] ?? '', - 'spec_name' => $spec_name, - 'price' => $og['price'] ?? 0, - ], JSON_UNESCAPED_UNICODE), + 'goods_snapshot' => $goods_snapshot, 'user_id' => $order['user_id'], 'ticket_code' => $ticket_code, 'qr_data' => '', // 占位,生成后更新 - 'seat_info' => $spec_name, + 'seat_info' => $seat_info, 'spec_base_id' => $spec_base_id, 'real_name' => '', 'phone' => '', diff --git a/shopxo/app/plugins/vr_ticket/service/WalletService.php b/shopxo/app/plugins/vr_ticket/service/WalletService.php index 21c9c65..6fc3a71 100644 --- a/shopxo/app/plugins/vr_ticket/service/WalletService.php +++ b/shopxo/app/plugins/vr_ticket/service/WalletService.php @@ -27,15 +27,10 @@ class WalletService extends BaseService */ public static function getUserTickets(int $userId): array { - // 查询该用户的所有票(关联订单) + // 直接查询 tickets 表(user_id 已存在) $tickets = \think\facade\Db::name('vr_tickets') - ->alias('t') - ->join('order o', 't.order_id = o.id', 'LEFT') - ->where('o.user_id', $userId) - ->where('o.pay_status', 1) // 已支付 - ->where('o.status', '<>', 3) // 未删除 - ->field('t.*') - ->order('t.issued_at', 'desc') + ->where('user_id', $userId) + ->order('issued_at', 'desc') ->select() ->toArray(); @@ -58,8 +53,33 @@ class WalletService extends BaseService // 生成短码 $shortCode = self::shortCodeEncode($ticket['goods_id'], $ticket['id']); - // 解析座位信息(从 seat_info 中提取场次/场馆) + // 优先从 seat_info 解析(5维 pipe 格式),兜底从 goods_snapshot 解析 $seatInfo = self::parseSeatInfo($ticket['seat_info'] ?? ''); + $snapshot = json_decode($ticket['goods_snapshot'] ?? '{}', true); + $snapshotKeys = array_filter(['session' => $snapshot['session'] ?? '', 'venue' => $snapshot['venue'] ?? '', 'studio' => $snapshot['studio'] ?? '', 'section' => $snapshot['section'] ?? '', 'seat' => $snapshot['seat'] ?? '']); + if (empty($seatInfo['session']) && !empty($snapshotKeys)) { + $seatInfo = array_merge($seatInfo, $snapshotKeys); + } + + // goods_snapshot 里没有 session/venue 时,从商品表补全 + if (empty($seatInfo['session']) || empty($seatInfo['venue'])) { + $goodsTitle = $goodsMap[$ticket['goods_id']] ?? '已下架商品'; + $goods = \think\facade\Db::name('Goods')->where('id', $ticket['goods_id'])->find(); + $vrConfig = json_decode($goods['vr_goods_config'] ?? '', true); + if (!empty($vrConfig[0]['template_id'])) { + $template = \think\facade\Db::name('vr_seat_templates') + ->where('id', $vrConfig[0]['template_id'])->find(); + $seatMap = json_decode($template['seat_map'] ?? '{}', true); + if (empty($seatInfo['venue'])) $seatInfo['venue'] = $template['name'] ?? ''; + if (empty($seatInfo['session'])) { + $sessions = $vrConfig[0]['sessions'] ?? []; + $seatInfo['session'] = !empty($sessions[0]['start']) && !empty($sessions[0]['end']) + ? ($sessions[0]['start'] . '-' . $sessions[0]['end']) : ''; + } + } + } + if (empty($seatInfo['venue'])) $seatInfo['venue'] = $snapshot['venue'] ?? ''; + if (empty($seatInfo['session'])) $seatInfo['session'] = $snapshot['session'] ?? ''; $result[] = [ 'id' => $ticket['id'], @@ -88,12 +108,10 @@ class WalletService extends BaseService */ public static function getTicketDetail(int $ticketId, int $userId): ?array { + // 直接查询 tickets 表(包含 user_id) $ticket = \think\facade\Db::name('vr_tickets') - ->alias('t') - ->join('order o', 't.order_id = o.id', 'LEFT') - ->where('t.id', $ticketId) - ->where('o.user_id', $userId) - ->field('t.*') + ->where('id', $ticketId) + ->where('user_id', $userId) ->find(); if (empty($ticket)) { @@ -103,6 +121,24 @@ class WalletService extends BaseService // 获取商品信息 $goods = \think\facade\Db::name('Goods')->find($ticket['goods_id']); $seatInfo = self::parseSeatInfo($ticket['seat_info'] ?? ''); + $snapshot = json_decode($ticket['goods_snapshot'] ?? '{}', true); + + // 兜底补全:从 snapshot 补 seat_info 缺失字段 + if (empty($seatInfo['venue']) || empty($seatInfo['session'])) { + $vrConfig = json_decode($goods['vr_goods_config'] ?? '', true); + if (!empty($vrConfig[0]['template_id'])) { + $template = \think\facade\Db::name('vr_seat_templates') + ->where('id', $vrConfig[0]['template_id'])->find(); + if (empty($seatInfo['venue'])) $seatInfo['venue'] = $template['name'] ?? ''; + if (empty($seatInfo['session'])) { + $sessions = $vrConfig[0]['sessions'] ?? []; + $seatInfo['session'] = !empty($sessions[0]['start']) && !empty($sessions[0]['end']) + ? ($sessions[0]['start'] . '-' . $sessions[0]['end']) : ''; + } + } + } + if (empty($seatInfo['venue'])) $seatInfo['venue'] = $snapshot['venue'] ?? ''; + if (empty($seatInfo['session'])) $seatInfo['session'] = $snapshot['session'] ?? ''; // 生成短码 $shortCode = self::shortCodeEncode($ticket['goods_id'], $ticket['id']);