2026-04-16 16:46:00 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
namespace app\plugins\vr_ticket;
|
|
|
|
|
|
|
|
|
|
|
|
use app\plugins\vr_ticket\service\TicketService;
|
|
|
|
|
|
|
|
|
|
|
|
class Hook
|
|
|
|
|
|
{
|
|
|
|
|
|
public function handle($params = [])
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!empty($params['hook_name']))
|
|
|
|
|
|
{
|
|
|
|
|
|
$ret = '';
|
|
|
|
|
|
switch($params['hook_name'])
|
|
|
|
|
|
{
|
|
|
|
|
|
// 后台左侧菜单钩子
|
|
|
|
|
|
case 'plugins_service_admin_menu_data':
|
|
|
|
|
|
$this->AdminSidebarInit($params['admin_left_menu']);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 订单支付成功处理
|
|
|
|
|
|
case 'plugins_service_order_pay_success_handle_end':
|
|
|
|
|
|
$ret = TicketService::onOrderPaid($params);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2026-04-23 05:44:48 +00:00
|
|
|
|
case 'plugins_service_order_detail_page_info':
|
|
|
|
|
|
// C端订单详情页注入票夹入口
|
|
|
|
|
|
$ret = $this->InjectTicketCard($params);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2026-04-16 16:46:00 +00:00
|
|
|
|
case 'plugins_service_order_delete_success':
|
|
|
|
|
|
// 如果有删除拦截等
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return $ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function AdminSidebarInit(&$params)
|
|
|
|
|
|
{
|
|
|
|
|
|
$params[] = [
|
|
|
|
|
|
'id' => 'plugins-vr_ticket',
|
|
|
|
|
|
'name' => 'VR票务',
|
|
|
|
|
|
'title' => 'VR票务',
|
|
|
|
|
|
'icon' => 'am-icon-ticket',
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'index',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'power' => 'vr_ticket-admin',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'index'),
|
|
|
|
|
|
'item' => [
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-venue',
|
|
|
|
|
|
'name' => '场馆配置',
|
|
|
|
|
|
'title' => '场馆配置',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'VenueList',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'VenueList'),
|
|
|
|
|
|
'power' => 'vr_ticket-venueList',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-seat',
|
|
|
|
|
|
'name' => '座位模板',
|
|
|
|
|
|
'title' => '座位模板',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'SeatTemplateList',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateList'),
|
|
|
|
|
|
'power' => 'vr_ticket-seatTemplateList',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-ticket',
|
|
|
|
|
|
'name' => '电子票',
|
|
|
|
|
|
'title' => '电子票',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'TicketList',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'TicketList'),
|
|
|
|
|
|
'power' => 'vr_ticket-ticketList',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-verifier',
|
|
|
|
|
|
'name' => '核销员',
|
|
|
|
|
|
'title' => '核销员',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'VerifierList',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'VerifierList'),
|
|
|
|
|
|
'power' => 'vr_ticket-verifierList',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-varification',
|
|
|
|
|
|
'name' => '核销记录',
|
|
|
|
|
|
'title' => '核销记录',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'VerificationList',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'VerificationList'),
|
|
|
|
|
|
'power' => 'vr_ticket-verificationList',
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
'id' => 'plugins-vr_ticket-setup',
|
|
|
|
|
|
'name' => '插件设置',
|
|
|
|
|
|
'title' => '插件设置',
|
|
|
|
|
|
'is_show' => 1,
|
|
|
|
|
|
'control' => 'admin',
|
|
|
|
|
|
'action' => 'Setup',
|
|
|
|
|
|
'url' => PluginsAdminUrl('vr_ticket', 'admin', 'Setup'),
|
|
|
|
|
|
'power' => 'vr_ticket-setup',
|
|
|
|
|
|
]
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2026-04-23 05:44:48 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* C端订单详情页注入票卡片
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function InjectTicketCard(&$params)
|
|
|
|
|
|
{
|
|
|
|
|
|
$order = $params['order'] ?? [];
|
|
|
|
|
|
if (empty($order) || ($order['pay_status'] ?? 0) != 1) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$userId = session('user_id');
|
|
|
|
|
|
if (empty($userId)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$tickets = \think\facade\Db::name('vr_tickets')
|
|
|
|
|
|
->where('order_id', $order['id'])
|
|
|
|
|
|
->select()
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($tickets)) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$token = session('user_token') ?: '';
|
|
|
|
|
|
$hostUrl = \think\facade\Config::get('shopxo.host_url');
|
|
|
|
|
|
|
|
|
|
|
|
$ticketCardsHtml = '';
|
|
|
|
|
|
foreach ($tickets as $ticket) {
|
|
|
|
|
|
$shortCode = \app\plugins\vr_ticket\service\BaseService::shortCodeEncode($ticket['goods_id'], $ticket['id']);
|
|
|
|
|
|
$statusMap = [0 => ['text' => '未核销', 'class' => 'unverified'], 1 => ['text' => '已核销', 'class' => 'verified'], 2 => ['text' => '已退款', 'class' => 'refunded']];
|
|
|
|
|
|
$status = $statusMap[$ticket['verify_status']] ?? $statusMap[0];
|
|
|
|
|
|
|
|
|
|
|
|
$ticketCardsHtml .= '<div class="vr-ticket-card" data-ticket-id="' . $ticket['id'] . '">' .
|
|
|
|
|
|
'<div class="vr-ticket-card-header">' .
|
|
|
|
|
|
'<div class="vr-ticket-goods-title">电子票</div>' .
|
|
|
|
|
|
'<div class="vr-ticket-status ' . $status['class'] . '">' . $status['text'] . '</div>' .
|
|
|
|
|
|
'</div>' .
|
|
|
|
|
|
'<div class="vr-ticket-info">' .
|
|
|
|
|
|
'<div class="vr-ticket-info-row"><span class="vr-ticket-info-icon">💺</span><span>' . htmlspecialchars($ticket['seat_info'] ?? '') . '</span></div>' .
|
|
|
|
|
|
'<div class="vr-ticket-info-row"><span class="vr-ticket-info-icon">👤</span><span>' . htmlspecialchars($ticket['real_name'] ?? '') . '</span></div>' .
|
|
|
|
|
|
'</div>' .
|
|
|
|
|
|
'<div class="vr-ticket-footer">' .
|
|
|
|
|
|
'<div class="vr-ticket-short-code">短码: ' . htmlspecialchars($shortCode) . '</div>' .
|
|
|
|
|
|
'<a href="javascript:;" class="vr-ticket-view-btn" onclick="VrTicketWallet.viewTicket(' . $ticket['id'] . ')">查看票码 →</a>' .
|
|
|
|
|
|
'</div>' .
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$style = '<style>
|
|
|
|
|
|
.vr-ticket-card { background: #fff; border-radius: 12px; padding: 16px; margin: 12px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.06); cursor: pointer; }
|
|
|
|
|
|
.vr-ticket-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.12); }
|
|
|
|
|
|
.vr-ticket-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
|
|
|
|
|
|
.vr-ticket-goods-title { font-size: 16px; font-weight: 600; color: #333; }
|
|
|
|
|
|
.vr-ticket-status { font-size: 12px; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
|
|
|
|
|
|
.vr-ticket-status.unverified { background: #e6f7ff; color: #1890ff; }
|
|
|
|
|
|
.vr-ticket-status.verified { background: #f6ffed; color: #52c41a; }
|
|
|
|
|
|
.vr-ticket-status.refunded { background: #fff1f0; color: #ff4d4f; }
|
|
|
|
|
|
.vr-ticket-info { font-size: 13px; color: #666; line-height: 1.6; }
|
|
|
|
|
|
.vr-ticket-info-row { display: flex; align-items: center; margin-bottom: 4px; }
|
|
|
|
|
|
.vr-ticket-info-icon { width: 16px; color: #999; margin-right: 6px; }
|
|
|
|
|
|
.vr-ticket-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding-top: 12px; border-top: 1px solid #f0f0f0; }
|
|
|
|
|
|
.vr-ticket-short-code { font-size: 14px; font-family: "Courier New", monospace; color: #333; font-weight: 600; letter-spacing: 1px; }
|
|
|
|
|
|
.vr-ticket-view-btn { font-size: 13px; color: #1890ff; text-decoration: none; }
|
|
|
|
|
|
.vr-ticket-view-btn:hover { text-decoration: underline; }
|
|
|
|
|
|
</style>';
|
|
|
|
|
|
|
|
|
|
|
|
$ticketHtml = '<div class="vr-order-ticket-section">' .
|
|
|
|
|
|
'<div style="font-size:16px;font-weight:600;margin-bottom:12px;">📋 我的电子票</div>' .
|
|
|
|
|
|
$ticketCardsHtml .
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
|
|
|
|
|
|
$params['page_data']['ticket_section'] = $ticketHtml;
|
|
|
|
|
|
$params['page_data']['ticket_css'] = $style;
|
|
|
|
|
|
|
|
|
|
|
|
// JS
|
|
|
|
|
|
$js = '<script>
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
var apiBase = "' . $hostUrl . '?s=api/plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=";
|
|
|
|
|
|
var token = "' . htmlspecialchars($token) . '";
|
|
|
|
|
|
window.VrTicketWallet = {
|
|
|
|
|
|
viewTicket: function(ticketId) {
|
|
|
|
|
|
var modal = document.getElementById("vrTicketModal") || createModal();
|
|
|
|
|
|
modal.classList.add("active");
|
|
|
|
|
|
var body = document.getElementById("vrTicketModalBody");
|
|
|
|
|
|
body.innerHTML = \'<div style="text-align:center;padding:40px;">加载中...</div>\';
|
|
|
|
|
|
$.ajax({
|
|
|
|
|
|
url: apiBase + "detail&id=" + ticketId,
|
|
|
|
|
|
headers: token ? {"X-Token": token} : {},
|
|
|
|
|
|
success: function(res) {
|
|
|
|
|
|
if (res.code === 0 && res.data.ticket) {
|
|
|
|
|
|
var t = res.data.ticket;
|
|
|
|
|
|
var statusMap = {0:{text:"未核销",class:"unverified"},1:{text:"已核销",class:"verified"},2:{text:"已退款",class:"refunded"}};
|
|
|
|
|
|
var status = statusMap[t.verify_status] || statusMap[0];
|
|
|
|
|
|
body.innerHTML = \'<div style="text-align:center;padding:20px;background:#fafafa;border-radius:12px;"><div id="vrQrcodeBox"></div></div>\' +
|
|
|
|
|
|
\'<div style="text-align:center;margin:16px 0;padding:12px;background:#f5f5f5;border-radius:8px;">\' +
|
|
|
|
|
|
\'<div style="font-size:12px;color:#999;margin-bottom:4px;">短码(人工核销)</div>\' +
|
|
|
|
|
|
\'<div style="font-size:20px;font-family:monospace;font-weight:700;letter-spacing:2px;">\' + t.short_code + \'</div></div>\' +
|
|
|
|
|
|
\'<div style="text-align:center;"><span class="vr-ticket-status \' + status.class + \'">\' + status.text + \'</span></div>\';
|
|
|
|
|
|
if (t.qr_payload) {
|
|
|
|
|
|
$("#vrQrcodeBox").qrcode({text: atob(t.qr_payload), width: 180, height: 180});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
closeModal: function() {
|
|
|
|
|
|
var modal = document.getElementById("vrTicketModal");
|
|
|
|
|
|
if (modal) modal.classList.remove("active");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
function createModal() {
|
|
|
|
|
|
var html = \'<div id="vrTicketModal" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;display:none;align-items:center;justify-content:center;">\' +
|
|
|
|
|
|
\'<div style="background:#fff;border-radius:16px;width:90%;max-width:400px;padding:24px;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">\' +
|
|
|
|
|
|
\'<div style="font-size:18px;font-weight:600;">电子票</div><button onclick="VrTicketWallet.closeModal()" style="width:28px;height:28px;border-radius:50%;background:#f0f0f0;border:none;cursor:pointer;">×</button></div>\' +
|
|
|
|
|
|
\'<div id="vrTicketModalBody"></div></div></div>\';
|
|
|
|
|
|
document.body.insertAdjacentHTML("beforeend", html);
|
|
|
|
|
|
var modal = document.getElementById("vrTicketModal");
|
|
|
|
|
|
modal.addEventListener("click", function(e) { if (e.target === modal) VrTicketWallet.closeModal(); });
|
|
|
|
|
|
return modal;
|
|
|
|
|
|
}
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>';
|
|
|
|
|
|
$params['page_data']['ticket_js'] = $js;
|
|
|
|
|
|
}
|
2026-04-16 16:46:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
?>
|