vr-shopxo-plugin/shopxo/app/plugins/vr_ticket/admin/Admin.php

955 lines
33 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* VR票务插件 - 后台管理主控制器admin/Admin.php 模式)
*
* 路由机制Plugins/Index → PluginsService::PluginsControlCall:
* sidebar URL: /plugins/vr_ticket/admin/seatTemplateList
* → pluginsname=vr_ticket, pluginscontrol=admin, pluginsaction=seatTemplateList
* → class = \app\plugins\vr_ticket\admin\Admin (ucfirst('admin') = 'Admin')
* → method = ucfirst('seatTemplateList') = 'SeatTemplateList'
* → app/plugins/vr_ticket/admin/Admin.php::SeatTemplateList() ✓
*
* ThinkPHP PSR-4 autoload: app\ → app/
* 所以 \app\plugins\vr_ticket\admin\Admin
* → app/plugins/vr_ticket/admin/Admin.php ✓
*
* 旧结构 admin/controller/SeatTemplate.php (namespace 含 controller 子目录)
* 会产生类路径 \app\plugins\vr_ticket\admin\SeatTemplate无 controller 子目录)
* → app/plugins/vr_ticket/admin/SeatTemplate.php ✗ (不存在)
* 这就是路由失败的根因!
*
* @package vr_ticket\admin
*/
namespace app\plugins\vr_ticket\admin;
define('START_TIME', microtime(true));
use app\admin\controller\Common;
/**
* 所有后台控制器方法都在此类中实现
* 直接继承 ShopXO Common 控制器以获得 IsLogin + IsPower + ViewInit
*/
class Admin extends Common
{
public function __construct()
{
parent::__construct();
}
/**
* 初始化方法 - 自愈性安装检查
*/
protected function initialize()
{
parent::initialize();
// 增加缓存锁,避免每次请求都进行数据库结构自检(这是导致加载缓慢的潜在原因)
$cache_key = 'vr_ticket_table_checked_v2';
if (\think\facade\Cache::get($cache_key) !== 1) {
try {
$this->checkAndInstallTables();
\think\facade\Cache::set($cache_key, 1, 3600); // 1小时检查一次
} catch (\Exception $e) {
// 静默失败
}
}
}
/**
* 自动建表逻辑
* 如果发现核心表不存在,则从 install.sql 读取 SQL 并执行
*/
private function checkAndInstallTables()
{
$prefix = \think\facade\Config::get('database.connections.mysql.prefix', 'vrt_');
$tableName = $prefix . 'vr_seat_templates';
// 检查表是否存在
$res = \think\facade\Db::query("SHOW TABLES LIKE '{$tableName}'");
if (empty($res)) {
$sqlFile = dirname(__DIR__) . '/install.sql';
if (file_exists($sqlFile)) {
$sqlContent = file_get_contents($sqlFile);
// 替换前缀
$sqlContent = str_replace('{{prefix}}', $prefix, $sqlContent);
// 拆分 SQL 语句执行
$sqls = explode(';', $sqlContent);
foreach ($sqls as $sql) {
$sql = trim($sql);
if (!empty($sql)) {
try {
\think\facade\Db::execute($sql);
} catch (\Exception $e) {
// 记录日志或忽略错误
}
}
}
}
}
// 兼容性修补:如果已存在表且存在 uk_category_id 唯一索引,则将其删除
// 场馆现在使用自由配置,不再强制要求绑定唯一的产品分类
try {
$indexCheck = \think\facade\Db::query("SHOW INDEX FROM `{$tableName}` WHERE Key_name = 'uk_category_id'");
if (!empty($indexCheck)) {
try {
\think\facade\Db::execute("ALTER TABLE `{$tableName}` DROP INDEX `uk_category_id`");
} catch (\Exception $e) {
// 如果已经手动删除或报错则忽略
}
}
} catch (\Exception $e) {
// 忽略由于键不存在导致的异常
}
}
public function index()
{
return $this->VenueList();
}
// ============================================================
// 座位模板SeatTemplate
// 视图: admin/view/seat_template/{action}.html
// ============================================================
/**
* 座位模板列表
* URL: /plugins/vr_ticket/admin/seatTemplateList
* → PluginsService ucfirst('admin')=Admin + ucfirst('seatTemplateList')=SeatTemplateList
*/
public function SeatTemplateList()
{
$where = [];
$name = input('name', '', null);
if ($name !== '') {
$where[] = ['name', 'like', "%{$name}%"];
}
$status = input('status', '', null);
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', intval($status)];
}
$list = \think\facade\Db::name('vr_seat_templates')
->where($where)
->order('id', 'desc')
->paginate(20);
$list_data = $list->toArray();
// 关联分类名
$category_ids = array_filter(array_column($list_data['data'], 'category_id'));
if (!empty($category_ids)) {
$categories = \think\facade\Db::name('GoodsCategory')
->where('id', 'in', $category_ids)
->column('name', 'id');
foreach ($list_data['data'] as &$item) {
$item['category_name'] = $categories[$item['category_id']] ?? '未知分类';
$item['seat_count'] = $this->countSeats($item['seat_map']);
}
unset($item);
}
// Leading / = ThinkPHP absolute path resolved from app/admin/view/default/
// Files are at: app/admin/view/default/plugins/view/vr_ticket/admin/view/seat_template/list.html
return MyView('../../../plugins/vr_ticket/admin/view/seat_template/list', [
'list' => $list_data['data'],
'page' => $list->render() ?: '',
'count' => $list_data['total'],
]);
}
/**
* 添加/编辑座位模板
*/
public function SeatTemplateSave()
{
$id = input('id', 0, 'intval');
if ((request()->isAjax() && request()->isPost())) {
$data = [
'name' => input('name', '', null, 'trim'),
'category_id' => input('category_id', 0, 'intval'),
'seat_map' => input('seat_map', '', null, 'trim'),
'spec_base_id_map' => input('spec_base_id_map', '', null, 'trim'),
'status' => input('status', 1, 'intval'),
'upd_time' => time(),
];
if (empty($data['name'])) {
return DataReturn('模板名称不能为空', -1);
}
if (empty($data['category_id'])) {
return DataReturn('请选择绑定的分类', -1);
}
// 验证 seat_map 为合法 JSON
$seat_map = json_decode($data['seat_map'], true);
if (empty($seat_map) && $data['seat_map'] !== '[]' && $data['seat_map'] !== '{}') {
return DataReturn('座位地图JSON格式错误', -1);
}
if ($id > 0) {
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->update($data);
return DataReturn('更新成功', 0);
} else {
$data['add_time'] = time();
$data['upd_time'] = time();
\think\facade\Db::name('vr_seat_templates')->insert($data);
return DataReturn('添加成功', 0);
}
}
// 编辑时加载数据
$info = [];
if ($id > 0) {
$info = \think\facade\Db::name('vr_seat_templates')->find($id);
}
// 加载分类列表(用于下拉选择)
$categories = \think\facade\Db::name('GoodsCategory')
->where('is_enable', 1)
->order('id', 'asc')
->select();
return MyView('../../../plugins/vr_ticket/admin/view/seat_template/save', [
'info' => $info,
'categories' => $categories,
]);
}
/**
* 删除座位模板(软删除)
*/
public function SeatTemplateDelete()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
$template = \think\facade\Db::name('vr_seat_templates')->where('id', $id)->find();
\think\facade\Db::name('vr_seat_templates')
->where('id', $id)
->update(['status' => 0, 'upd_time' => time()]);
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_DISABLE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['before_status' => $template['status'] ?? 1],
$template ? "模板: {$template['name']}" : "ID:{$id}"
);
return DataReturn('删除成功', 0);
}
// ============================================================
// 电子票Ticket
// 视图: admin/view/ticket/{action}.html
// ============================================================
/**
* 电子票列表
*/
public function TicketList()
{
$where = [];
$keywords = input('keywords', '', null, 'trim');
if (!empty($keywords)) {
$where[] = ['order_no|ticket_code|real_name|phone', 'like', "%{$keywords}%"];
}
$verify_status = input('verify_status', '', null);
if ($verify_status !== '' && $verify_status !== null) {
$where[] = ['verify_status', '=', intval($verify_status)];
}
$goods_id = input('goods_id', 0, 'intval');
if ($goods_id > 0) {
$where[] = ['goods_id', '=', $goods_id];
}
$list = \think\facade\Db::name('vr_tickets')
->where($where)
->order('id', 'desc')
->paginate(20);
$list_data = $list->toArray();
// 补充商品名称
$goods_ids = array_filter(array_column($list_data['data'], 'goods_id'));
if (!empty($goods_ids)) {
$goods_map = \think\facade\Db::name('Goods')
->where('id', 'in', $goods_ids)
->column('title', 'id');
foreach ($list_data['data'] as &$item) {
$item['goods_title'] = $goods_map[$item['goods_id']] ?? '已删除商品';
$item['qr_code_url'] = \app\plugins\vr_ticket\service\TicketService::getQrCodeUrl($item['ticket_code']);
}
unset($item);
}
$status_map = [
0 => ['text' => '未核销', 'color' => 'blue'],
1 => ['text' => '已核销', 'color' => 'green'],
2 => ['text' => '已退款', 'color' => 'red'],
];
return MyView('../../../plugins/vr_ticket/admin/view/ticket/list', [
'list' => $list_data['data'],
'page' => $list->render() ?: '',
'count' => $list_data['total'],
'status_map' => $status_map,
]);
}
/**
* 票详情
*/
public function TicketDetail()
{
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
$ticket = \think\facade\Db::name('vr_tickets')->find($id);
if (empty($ticket)) {
return DataReturn('票不存在', -1);
}
$goods = \think\facade\Db::name('Goods')->find($ticket['goods_id']);
$verifier = [];
if ($ticket['verifier_id'] > 0) {
$verifier = \think\facade\Db::name('vr_verifiers')->find($ticket['verifier_id']);
}
$ticket['qr_code_url'] = \app\plugins\vr_ticket\service\TicketService::getQrCodeUrl($ticket['ticket_code']);
$verifiers = \think\facade\Db::name('vr_verifiers')
->where('status', 1)
->order('id', 'asc')
->select();
return MyView('../../../plugins/vr_ticket/admin/view/ticket/detail', [
'ticket' => $ticket,
'goods' => $goods,
'verifier' => $verifier,
'verifiers' => $verifiers,
]);
}
/**
* 手动核销票JSON API
*/
public function TicketVerify()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$ticket_code = input('ticket_code', '', null, 'trim');
$verifier_id = input('verifier_id', 0, 'intval');
if (empty($ticket_code)) {
return DataReturn('票码不能为空', -1);
}
if ($verifier_id <= 0) {
return DataReturn('请选择核销员', -1);
}
$result = \app\plugins\vr_ticket\service\TicketService::verifyTicket($ticket_code, $verifier_id);
return DataReturn($result['msg'], $result['code'], $result['data'] ?? []);
}
/**
* 导出票列表CSV
*/
public function TicketExport()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$where = [];
$goods_id = input('goods_id', 0, 'intval');
if ($goods_id > 0) {
$where[] = ['goods_id', '=', $goods_id];
}
$header = ['ID', '订单号', '票码', '观演人', '手机', '座位', '核销状态', '发放时间'];
$rows = \think\facade\Db::name('vr_tickets')
->where($where)
->order('id', 'desc')
->cursor();
$data = [];
foreach ($rows as $item) {
$status_text = $item['verify_status'] == 0 ? '未核销' : ($item['verify_status'] == 1 ? '已核销' : '已退款');
$data[] = [
$item['id'],
$item['order_no'],
$item['ticket_code'],
$item['real_name'],
$item['phone'],
$item['seat_info'],
$status_text,
date('Y-m-d H:i:s', $item['issued_at']),
];
}
\app\plugins\vr_ticket\service\AuditService::logExport($goods_id, ['verify_status' => null], count($data));
ExportCsv($header, $data, 'vr_tickets_' . date('Ymd'));
return;
}
// ============================================================
// 核销员Verifier
// 视图: admin/view/verifier/{action}.html
// ============================================================
/**
* 核销员列表
*/
public function VerifierList()
{
$where = [];
$keywords = input('keywords', '', null, 'trim');
if (!empty($keywords)) {
$where[] = ['name|user_id', 'like', "%{$keywords}%"];
}
$status = input('status', '', null);
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', intval($status)];
}
$list = \think\facade\Db::name('vr_verifiers')
->where($where)
->order('id', 'desc')
->paginate(20);
$list_data = $list->toArray();
// 关联 ShopXO 用户信息
$user_ids = array_filter(array_column($list_data['data'], 'user_id'));
if (!empty($user_ids)) {
$users_raw = \think\facade\Db::name('User')
->where('id', 'in', $user_ids)
->select();
$users = [];
foreach ($users_raw as $u) {
$users[$u['id']] = ($u['nickname'] ?: '') . '/' . ($u['username'] ?: '');
}
foreach ($list_data['data'] as &$item) {
$item['user_name'] = $users[$item['user_id']] ?? '已删除用户';
}
unset($item);
}
return MyView('../../../plugins/vr_ticket/admin/view/verifier/list', [
'list' => $list_data['data'],
'page' => $list->render() ?: '',
'count' => $list_data['total'],
]);
}
/**
* 添加/编辑核销员
*/
public function VerifierSave()
{
$id = input('id', 0, 'intval');
if ((request()->isAjax() && request()->isPost())) {
$user_id = input('user_id', 0, 'intval');
$name = input('name', '', null, 'trim');
$status = input('status', 1, 'intval');
if ($user_id <= 0) {
return DataReturn('请选择关联用户', -1);
}
if (empty($name)) {
return DataReturn('核销员名称不能为空', -1);
}
$exist = \think\facade\Db::name('vr_verifiers')
->where('user_id', $user_id)
->where('id', '<>', $id)
->find();
if ($exist) {
return DataReturn('该用户已是核销员', -1);
}
if ($id > 0) {
\think\facade\Db::name('vr_verifiers')
->where('id', $id)
->update(['name' => $name, 'status' => $status]);
return DataReturn('更新成功', 0);
} else {
\think\facade\Db::name('vr_verifiers')->insert([
'user_id' => $user_id,
'name' => $name,
'status' => $status,
'created_at' => time(),
]);
return DataReturn('添加成功', 0);
}
}
$info = [];
if ($id > 0) {
$info = \think\facade\Db::name('vr_verifiers')->find($id);
}
$users = \think\facade\Db::name('User')
->where('status', '<>', 3) // 3 usually means deleted/disabled in some versions, but to be safe:
->field('id, nickname, username')
->order('id', 'desc')
->select();
return MyView('../../../plugins/vr_ticket/admin/view/verifier/save', [
'info' => $info,
'users' => $users,
]);
}
/**
* 删除核销员(软删除:禁用)
*/
public function VerifierDelete()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
$verifier = \think\facade\Db::name('vr_verifiers')->where('id', $id)->find();
\think\facade\Db::name('vr_verifiers')
->where('id', $id)
->update(['status' => 0]);
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_DISABLE_VERIFIER,
\app\plugins\vr_ticket\service\AuditService::TARGET_VERIFIER,
$id,
['before_status' => $verifier['status'] ?? 1],
$verifier ? "核销员: {$verifier['name']}" : "ID:{$id}"
);
return DataReturn('已禁用', 0);
}
// ============================================================
// 场馆配置Venue
// 视图: admin/view/venue/{action}.html
// 注意admin/controller/Venue.php 的旧控制器仍在使用,
// 但新路由走 Admin.php 的 VenueList/VenueSave。
// ============================================================
/**
* 场馆列表
* URL: /plugins/vr_ticket/admin/venueList
*/
public function VenueList()
{
$where = [];
$name = input('name', '', null);
if ($name !== '') {
$where[] = ['name', 'like', "%{$name}%"];
}
$status = input('status', '', null);
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', intval($status)];
}
$list = \think\facade\Db::name('vr_seat_templates')
->where($where)
->order('id', 'desc')
->paginate(20);
$list_data = $list->toArray();
// 解析 venue.name 和座位数v3.0 格式seat_map.venue.name
foreach ($list_data['data'] as &$item) {
$seatMap = json_decode($item['seat_map'] ?? '{}', true);
$item['venue_name'] = $seatMap['venue']['name'] ?? $item['name'];
$item['venue_address'] = $seatMap['venue']['address'] ?? '';
$item['rooms'] = $seatMap['rooms'] ?? [];
$zoneCount = 0;
if (!empty($item['rooms'])) {
foreach ($item['rooms'] as $rm) {
$zoneCount += count($rm['sections'] ?? []);
}
} else {
$zoneCount = !empty($seatMap['sections']) ? count($seatMap['sections']) : 0;
}
$item['zone_count'] = $zoneCount;
$item['seat_count'] = $this->countSeatsV2($seatMap);
}
unset($item);
return MyView('../../../plugins/vr_ticket/view/venue/list', [
'list' => $list_data['data'],
'page' => $list->render() ?: '',
'count' => $list_data['total'],
'data_req' => input(),
'debug_time' => microtime(true) - START_TIME,
]);
}
/**
* 添加/编辑场馆
* URL: /plugins/vr_ticket/admin/venueSave
*/
public function VenueSave()
{
$id = input('id', 0, 'intval');
if ((request()->isAjax() && request()->isPost())) {
$data = [
'name' => input('name', '', null, 'trim'),
'category_id' => 0, // 分类绑定已弃用,强置为 0
'status' => input('status', 1, 'intval'),
'upd_time' => time(),
];
if (empty($data['name'])) {
return DataReturn('场馆名称不能为空', -1);
}
// 使用 request()->post() 获取原始字符串,并兼容 Base64 编码绕过净化
$seat_map_raw = request()->post('seat_map_raw', '{}');
$decoded_json = $seat_map_raw;
if(!empty($seat_map_raw) && $seat_map_raw !== '{}') {
// 判断是否是 Base64 字符串 (如果是以 ey 开头通常是 JSON 的 Base64)
if(preg_match('/^[a-zA-Z0-9\/\+=]+$/', $seat_map_raw) && strlen($seat_map_raw) % 4 == 0) {
$decoded_json = base64_decode($seat_map_raw);
} else {
// 如果不是 Base64 则按原样处理并进行 htmlspecialchars_decode
$decoded_json = htmlspecialchars_decode($seat_map_raw);
}
}
$seat_map = json_decode($decoded_json, true);
if (empty($seat_map) || !is_array($seat_map)) {
return DataReturn('场馆配置数据无效或解析失败', -1);
}
// 基本验证
if (empty($seat_map['venue']['name'])) {
return DataReturn('场馆详情名称不能为空', -1);
}
$rooms = $seat_map['rooms'] ?? [];
if (!is_array($rooms) || empty($rooms)) {
return DataReturn('请至少添加一个放映室/展厅', -1);
}
foreach ($rooms as &$room) {
if (empty($room['name'])) {
return DataReturn('放映室名称不能为空', -1);
}
// 生成 room.id兜底保证每个房间有唯一 id支持前端按 id 引用)
if (empty($room['id'])) {
$room['id'] = sprintf('%08x-%04x-%04x-%04x-%04x%08x',
time(), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffffffff));
}
// --- 自动补全 seats 字典,解决 'A' 未定义报错 ---
$room['seats'] = [];
if (!empty($room['sections']) && is_array($room['sections'])) {
foreach ($room['sections'] as $sec) {
if (isset($sec['char']) && $sec['char'] !== '') {
$room['seats'][$sec['char']] = $sec;
}
}
}
// 处理 map 数组,过滤空行并修剪
$map = is_array($room['map']) ? $room['map'] : [];
$room['map'] = array_values(array_filter(array_map('trim', $map), function($v) {
return $v !== '';
}));
if (empty($room['map'])) {
return DataReturn("放映室 {$room['name']} 座位排布不能为空", -1);
}
foreach ($room['map'] as $rowStr) {
// 此时 $rowStr 已经 trim 过且非空
foreach (str_split($rowStr) as $char) {
if ($char !== '_' && $char !== '-' && !isset($room['seats'][$char])) {
return DataReturn("放映室 {$room['name']} 中座位字符 '{$char}' 未在分区中定义", -1);
}
}
}
}
// 保存最终 JSON注意避免反斜杠转义
// 注意:此时 $rooms 已被引用并修改了 map 数组(过滤了空行)
$seat_map['rooms'] = $rooms;
$data['seat_map'] = json_encode($seat_map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($id > 0) {
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->update($data);
return DataReturn('更新成功', 0);
} else {
$data['add_time'] = time();
$data['upd_time'] = time();
$data['spec_base_id_map'] = '';
\think\facade\Db::name('vr_seat_templates')->insert($data);
return DataReturn('添加成功', 0);
}
}
$info = [];
if ($id > 0) {
$row = \think\facade\Db::name('vr_seat_templates')->find($id);
if (!empty($row)) {
$seatMap = json_decode($row['seat_map'] ?? '{}', true);
$row['venue_name'] = $seatMap['venue']['name'] ?? '';
$row['venue_address'] = $seatMap['venue']['address'] ?? '';
$row['venue_image'] = $seatMap['venue']['image'] ?? '';
$row['zones_json'] = json_encode($seatMap['sections'] ?? [], JSON_UNESCAPED_UNICODE);
$row['venue_json'] = json_encode([
'name' => $seatMap['venue']['name'] ?? '',
'address' => $seatMap['venue']['address'] ?? '',
'image' => $seatMap['venue']['image'] ?? '',
], JSON_UNESCAPED_UNICODE);
$row['map_json'] = json_encode($seatMap['map'] ?? [], JSON_UNESCAPED_UNICODE);
$info = $row;
}
}
$categories = \think\facade\Db::name('GoodsCategory')
->where('is_enable', 1)
->order('id', 'asc')
->select();
// 加载插件配置,用于获取高德 API Key 等
$config_ret = \app\service\PluginsService::PluginsData('vr_ticket');
$config = $config_ret['data'] ?? [];
return MyView('../../../plugins/vr_ticket/view/venue/save', [
'info' => $info,
'categories' => $categories,
'vr_config' => $config,
]);
}
/**
* 插件设置视图
*/
public function Setup()
{
$data_ret = \app\service\PluginsService::PluginsData('vr_ticket');
$data = $data_ret['data'] ?? [];
return MyView('../../../plugins/vr_ticket/view/admin/setup', [
'data' => $data,
]);
}
/**
* 插件设置保存
*/
public function SetupSave()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$data = [
'amap_api_key' => input('amap_api_key', '', null, 'trim'),
'other_config' => input('other_config', '', null, 'trim'),
];
$params = [
'plugins' => 'vr_ticket',
'data' => $data,
];
$ret = \app\service\PluginsService::PluginsDataSave($params);
if ($ret['code'] == 0) {
return DataReturn('保存成功', 0);
} else {
return DataReturn($ret['msg'], -1);
}
}
/**
* 删除场馆(软删除)
*/
public function VenueDelete()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
$template = \think\facade\Db::name('vr_seat_templates')->where('id', $id)->find();
\think\facade\Db::name('vr_seat_templates')
->where('id', $id)
->update(['status' => 0, 'upd_time' => time()]);
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_DISABLE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['before_status' => $template['status'] ?? 1],
$template ? "场馆: {$template['name']}" : "ID:{$id}"
);
return DataReturn('删除成功', 0);
}
// ============================================================
// 核销记录Verification
// 视图: admin/view/verification/{action}.html
// ============================================================
/**
* 核销记录列表
*/
public function VerificationList()
{
$where = [];
$keywords = input('keywords', '', null, 'trim');
if (!empty($keywords)) {
$where[] = ['ticket_code|verifier_name', 'like', "%{$keywords}%"];
}
$verifier_id = input('verifier_id', 0, 'intval');
if ($verifier_id > 0) {
$where[] = ['verifier_id', '=', $verifier_id];
}
$start_date = input('start_date', '', null, 'trim');
$end_date = input('end_date', '', null, 'trim');
if (!empty($start_date)) {
$where[] = ['created_at', '>=', strtotime($start_date)];
}
if (!empty($end_date)) {
$where[] = ['created_at', '<=', strtotime($end_date . ' 23:59:59')];
}
$list = \think\facade\Db::name('vr_verifications')
->where($where)
->order('id', 'desc')
->paginate(20);
$list_data = $list->toArray();
// 补充票信息
$ticket_ids = array_filter(array_column($list_data['data'], 'ticket_id'));
if (!empty($ticket_ids)) {
$tickets_raw = \think\facade\Db::name('vr_tickets')
->where('id', 'in', $ticket_ids)
->select();
$tickets = [];
foreach ($tickets_raw as $t) {
$tickets[$t['id']] = $t;
}
foreach ($list_data['data'] as &$item) {
$ticket = $tickets[$item['ticket_id']] ?? [];
$item['seat_info'] = $ticket['seat_info'] ?? '';
$item['real_name'] = $ticket['real_name'] ?? '';
$item['goods_id'] = $ticket['goods_id'] ?? 0;
}
unset($item);
}
// 商品名
$goods_ids = array_filter(array_unique(array_column($list_data['data'], 'goods_id')));
if (!empty($goods_ids)) {
$goods_map = \think\facade\Db::name('Goods')
->where('id', 'in', $goods_ids)
->column('title', 'id');
foreach ($list_data['data'] as &$item) {
$item['goods_title'] = $goods_map[$item['goods_id']] ?? '已删除';
}
unset($item);
}
// 核销员列表(用于筛选)
$verifiers = \think\facade\Db::name('vr_verifiers')
->where('status', 1)
->column('name', 'id');
return MyView('../../../plugins/vr_ticket/admin/view/verification/list', [
'list' => $list_data['data'],
'page' => $list->render() ?: '',
'count' => $list_data['total'],
'verifiers' => $verifiers,
]);
}
// ============================================================
// 辅助方法
// ============================================================
/**
* 统计座位数v1 格式:直接传入 seat_map JSON 字符串)
*/
private function countSeats($seat_map_json)
{
if (empty($seat_map_json)) {
return 0;
}
$map = json_decode($seat_map_json, true);
if (empty($map['seats']) || empty($map['map'])) {
return 0;
}
$count = 0;
foreach ($map['map'] as $row) {
foreach (str_split($row) as $char) {
if ($char !== '_' && isset($map['seats'][$char])) {
$count++;
}
}
}
return $count;
}
/**
* 统计座位数(支持 v3 多房间格式 或 v2 格式)
*/
private function countSeatsV2($seatMap)
{
if (empty($seatMap['rooms']) || !is_array($seatMap['rooms'])) {
return 0;
}
$total = 0;
foreach ($seatMap['rooms'] as $rm) {
if (!empty($rm['map']) && is_array($rm['map'])) {
foreach ($rm['map'] as $row) {
if (is_string($row)) {
$total += strlen(str_replace(['_', '-'], '', $row));
}
}
}
}
return $total;
}
}