821 lines
27 KiB
PHP
821 lines
27 KiB
PHP
<?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;
|
||
|
||
use app\admin\controller\Common;
|
||
|
||
/**
|
||
* 所有后台控制器方法都在此类中实现
|
||
* 直接继承 ShopXO Common 控制器以获得 IsLogin + IsPower + ViewInit
|
||
*/
|
||
class Admin extends Common
|
||
{
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
}
|
||
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 = \Db::name('vr_seat_templates')
|
||
->where($where)
|
||
->order('id', 'desc')
|
||
->paginate(20)
|
||
->toArray();
|
||
|
||
// 关联分类名
|
||
$category_ids = array_filter(array_column($list['data'], 'category_id'));
|
||
if (!empty($category_ids)) {
|
||
$categories = \Db::name('GoodsCategory')
|
||
->where('id', 'in', $category_ids)
|
||
->column('name', 'id');
|
||
foreach ($list['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'],
|
||
'page' => $list['page'],
|
||
'count' => $list['total'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 添加/编辑座位模板
|
||
*/
|
||
public function SeatTemplateSave()
|
||
{
|
||
$id = input('id', 0, 'intval');
|
||
|
||
if (IS_AJAX_POST) {
|
||
$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) {
|
||
\Db::name('vr_seat_templates')->where('id', $id)->update($data);
|
||
return DataReturn('更新成功', 0);
|
||
} else {
|
||
$data['add_time'] = time();
|
||
$data['upd_time'] = time();
|
||
\Db::name('vr_seat_templates')->insert($data);
|
||
return DataReturn('添加成功', 0);
|
||
}
|
||
}
|
||
|
||
// 编辑时加载数据
|
||
$info = [];
|
||
if ($id > 0) {
|
||
$info = \Db::name('vr_seat_templates')->find($id);
|
||
}
|
||
|
||
// 加载分类列表(用于下拉选择)
|
||
$categories = \Db::name('GoodsCategory')
|
||
->where('is_delete', 0)
|
||
->order('id', 'asc')
|
||
->select();
|
||
|
||
return MyView('../../../plugins/vr_ticket/admin/view/seat_template/save', [
|
||
'info' => $info,
|
||
'categories' => $categories,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 删除座位模板(软删除)
|
||
*/
|
||
public function SeatTemplateDelete()
|
||
{
|
||
if (!IS_AJAX_POST) {
|
||
return DataReturn('非法请求', -1);
|
||
}
|
||
|
||
$id = input('id', 0, 'intval');
|
||
if ($id <= 0) {
|
||
return DataReturn('参数错误', -1);
|
||
}
|
||
|
||
$template = \Db::name('vr_seat_templates')->where('id', $id)->find();
|
||
\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 = \Db::name('vr_tickets')
|
||
->where($where)
|
||
->order('id', 'desc')
|
||
->paginate(20)
|
||
->toArray();
|
||
|
||
// 补充商品名称
|
||
$goods_ids = array_filter(array_column($list['data'], 'goods_id'));
|
||
if (!empty($goods_ids)) {
|
||
$goods_map = \Db::name('Goods')
|
||
->where('id', 'in', $goods_ids)
|
||
->column('title', 'id');
|
||
foreach ($list['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'],
|
||
'page' => $list['page'],
|
||
'count' => $list['total'],
|
||
'status_map' => $status_map,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 票详情
|
||
*/
|
||
public function TicketDetail()
|
||
{
|
||
$id = input('id', 0, 'intval');
|
||
if ($id <= 0) {
|
||
return DataReturn('参数错误', -1);
|
||
}
|
||
|
||
$ticket = \Db::name('vr_tickets')->find($id);
|
||
if (empty($ticket)) {
|
||
return DataReturn('票不存在', -1);
|
||
}
|
||
|
||
$goods = \Db::name('Goods')->find($ticket['goods_id']);
|
||
|
||
$verifier = [];
|
||
if ($ticket['verifier_id'] > 0) {
|
||
$verifier = \Db::name('vr_verifiers')->find($ticket['verifier_id']);
|
||
}
|
||
|
||
$ticket['qr_code_url'] = \app\plugins\vr_ticket\service\TicketService::getQrCodeUrl($ticket['ticket_code']);
|
||
|
||
$verifiers = \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 (!IS_AJAX_POST) {
|
||
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 (!IS_AJAX_POST) {
|
||
return DataReturn('非法请求', -1);
|
||
}
|
||
|
||
$where = [];
|
||
$goods_id = input('goods_id', 0, 'intval');
|
||
if ($goods_id > 0) {
|
||
$where[] = ['goods_id', '=', $goods_id];
|
||
}
|
||
|
||
$header = ['ID', '订单号', '票码', '观演人', '手机', '座位', '核销状态', '发放时间'];
|
||
$rows = \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 = \Db::name('vr_verifiers')
|
||
->where($where)
|
||
->order('id', 'desc')
|
||
->paginate(20)
|
||
->toArray();
|
||
|
||
// 关联 ShopXO 用户信息
|
||
$user_ids = array_filter(array_column($list['data'], 'user_id'));
|
||
if (!empty($user_ids)) {
|
||
$users_raw = \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'] as &$item) {
|
||
$item['user_name'] = $users[$item['user_id']] ?? '已删除用户';
|
||
}
|
||
unset($item);
|
||
}
|
||
|
||
return MyView('../../../plugins/vr_ticket/admin/view/verifier/list', [
|
||
'list' => $list['data'],
|
||
'page' => $list['page'],
|
||
'count' => $list['total'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 添加/编辑核销员
|
||
*/
|
||
public function VerifierSave()
|
||
{
|
||
$id = input('id', 0, 'intval');
|
||
|
||
if (IS_AJAX_POST) {
|
||
$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 = \Db::name('vr_verifiers')
|
||
->where('user_id', $user_id)
|
||
->where('id', '<>', $id)
|
||
->find();
|
||
if ($exist) {
|
||
return DataReturn('该用户已是核销员', -1);
|
||
}
|
||
|
||
if ($id > 0) {
|
||
\Db::name('vr_verifiers')
|
||
->where('id', $id)
|
||
->update(['name' => $name, 'status' => $status]);
|
||
return DataReturn('更新成功', 0);
|
||
} else {
|
||
\Db::name('vr_verifiers')->insert([
|
||
'user_id' => $user_id,
|
||
'name' => $name,
|
||
'status' => $status,
|
||
'created_at' => time(),
|
||
]);
|
||
return DataReturn('添加成功', 0);
|
||
}
|
||
}
|
||
|
||
$info = [];
|
||
if ($id > 0) {
|
||
$info = \Db::name('vr_verifiers')->find($id);
|
||
}
|
||
|
||
$users = \Db::name('User')
|
||
->where('is_delete', 0)
|
||
->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 (!IS_AJAX_POST) {
|
||
return DataReturn('非法请求', -1);
|
||
}
|
||
|
||
$id = input('id', 0, 'intval');
|
||
if ($id <= 0) {
|
||
return DataReturn('参数错误', -1);
|
||
}
|
||
|
||
$verifier = \Db::name('vr_verifiers')->where('id', $id)->find();
|
||
\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 = \Db::name('vr_seat_templates')
|
||
->where($where)
|
||
->order('id', 'desc')
|
||
->paginate(20)
|
||
->toArray();
|
||
|
||
// 解析 venue.name 和座位数(v3.0 格式:seat_map.venue.name)
|
||
foreach ($list['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['zone_count'] = !empty($seatMap['sections']) ? count($seatMap['sections']) : 0;
|
||
$item['seat_count'] = $this->countSeatsV2($seatMap);
|
||
}
|
||
unset($item);
|
||
|
||
return MyView('../../../plugins/vr_ticket/admin/view/venue/list', [
|
||
'list' => $list['data'],
|
||
'page' => $list['page'],
|
||
'count' => $list['total'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 添加/编辑场馆
|
||
* URL: /plugins/vr_ticket/admin/venueSave
|
||
*/
|
||
public function VenueSave()
|
||
{
|
||
$id = input('id', 0, 'intval');
|
||
|
||
if (IS_AJAX_POST) {
|
||
$data = [
|
||
'name' => input('name', '', null, 'trim'),
|
||
'category_id' => input('category_id', 0, 'intval'),
|
||
'status' => input('status', 1, 'intval'),
|
||
'upd_time' => time(),
|
||
];
|
||
|
||
if (empty($data['name'])) {
|
||
return DataReturn('场馆名称不能为空', -1);
|
||
}
|
||
|
||
// v3.0 seat_map JSON 构建
|
||
$venue = [
|
||
'name' => input('venue_name', '', null, 'trim'),
|
||
'address' => input('venue_address', '', null, 'trim'),
|
||
'image' => input('venue_image', '', null, 'trim'),
|
||
];
|
||
if (empty($venue['name'])) {
|
||
return DataReturn('场馆名称不能为空', -1);
|
||
}
|
||
|
||
$zones_raw = input('zones', '[]', null, 'trim');
|
||
$zones = json_decode($zones_raw, true);
|
||
if (!is_array($zones) || empty($zones)) {
|
||
return DataReturn('请至少添加一个分区', -1);
|
||
}
|
||
|
||
$map_raw = input('seat_map_rows', '[]', null, 'trim');
|
||
$map = json_decode($map_raw, true);
|
||
if (!is_array($map) || empty($map)) {
|
||
return DataReturn('座位排布不能为空', -1);
|
||
}
|
||
|
||
$sections = [];
|
||
$seats = [];
|
||
$row_labels = [];
|
||
foreach ($zones as $zone) {
|
||
$char = strtoupper($zone['char'] ?? '');
|
||
if (empty($char)) {
|
||
return DataReturn('分区字符不能为空', -1);
|
||
}
|
||
$sections[] = [
|
||
'char' => $char,
|
||
'name' => $zone['name'] ?? '',
|
||
'color' => $zone['color'] ?? '#cccccc',
|
||
];
|
||
$seats[$char] = [
|
||
'price' => intval($zone['price'] ?? 0),
|
||
'color' => $zone['color'] ?? '#cccccc',
|
||
'label' => $zone['name'] ?? '',
|
||
];
|
||
}
|
||
|
||
foreach ($map as $rowStr) {
|
||
$rowStr = trim($rowStr);
|
||
if (empty($rowStr)) {
|
||
return DataReturn('座位排布每行不能为空', -1);
|
||
}
|
||
foreach (str_split($rowStr) as $char) {
|
||
if ($char !== '_' && !isset($seats[$char])) {
|
||
return DataReturn("座位排布中字符 '{$char}' 未在分区中定义", -1);
|
||
}
|
||
}
|
||
$row_labels[] = $rowStr[0];
|
||
}
|
||
|
||
$data['seat_map'] = json_encode([
|
||
'venue' => $venue,
|
||
'map' => $map,
|
||
'seats' => $seats,
|
||
'sections' => $sections,
|
||
'row_labels' => array_values(array_unique(array_merge(
|
||
array_column($sections, 'char'),
|
||
$row_labels
|
||
))),
|
||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
|
||
if ($id > 0) {
|
||
\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'] = '';
|
||
\Db::name('vr_seat_templates')->insert($data);
|
||
return DataReturn('添加成功', 0);
|
||
}
|
||
}
|
||
|
||
$info = [];
|
||
if ($id > 0) {
|
||
$row = \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 = \Db::name('GoodsCategory')
|
||
->where('is_delete', 0)
|
||
->order('id', 'asc')
|
||
->select();
|
||
|
||
return MyView('../../../plugins/vr_ticket/admin/view/venue/save', [
|
||
'info' => $info,
|
||
'categories' => $categories,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 删除场馆(软删除)
|
||
*/
|
||
public function VenueDelete()
|
||
{
|
||
if (!IS_AJAX_POST) {
|
||
return DataReturn('非法请求', -1);
|
||
}
|
||
|
||
$id = input('id', 0, 'intval');
|
||
if ($id <= 0) {
|
||
return DataReturn('参数错误', -1);
|
||
}
|
||
|
||
$template = \Db::name('vr_seat_templates')->where('id', $id)->find();
|
||
\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 = \Db::name('vr_verifications')
|
||
->where($where)
|
||
->order('id', 'desc')
|
||
->paginate(20)
|
||
->toArray();
|
||
|
||
// 补充票信息
|
||
$ticket_ids = array_filter(array_column($list['data'], 'ticket_id'));
|
||
if (!empty($ticket_ids)) {
|
||
$tickets_raw = \Db::name('vr_tickets')
|
||
->where('id', 'in', $ticket_ids)
|
||
->select();
|
||
$tickets = [];
|
||
foreach ($tickets_raw as $t) {
|
||
$tickets[$t['id']] = $t;
|
||
}
|
||
foreach ($list['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'], 'goods_id')));
|
||
if (!empty($goods_ids)) {
|
||
$goods_map = \Db::name('Goods')
|
||
->where('id', 'in', $goods_ids)
|
||
->column('title', 'id');
|
||
foreach ($list['data'] as &$item) {
|
||
$item['goods_title'] = $goods_map[$item['goods_id']] ?? '已删除';
|
||
}
|
||
unset($item);
|
||
}
|
||
|
||
// 核销员列表(用于筛选)
|
||
$verifiers = \Db::name('vr_verifiers')
|
||
->where('status', 1)
|
||
->column('name', 'id');
|
||
|
||
return MyView('../../../plugins/vr_ticket/admin/view/verification/list', [
|
||
'list' => $list['data'],
|
||
'page' => $list['page'],
|
||
'count' => $list['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;
|
||
}
|
||
|
||
/**
|
||
* 统计座位数(v2 格式:直接传入已解码的 seat_map 数组)
|
||
*/
|
||
private function countSeatsV2(array $seat_map)
|
||
{
|
||
if (empty($seat_map['seats']) || empty($seat_map['map'])) {
|
||
return 0;
|
||
}
|
||
$count = 0;
|
||
foreach ($seat_map['map'] as $row) {
|
||
foreach (str_split($row) as $char) {
|
||
if ($char !== '_' && isset($seat_map['seats'][$char])) {
|
||
$count++;
|
||
}
|
||
}
|
||
}
|
||
return $count;
|
||
}
|
||
}
|