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']);