feat: 真删除功能 + 三按钮布局 + seat_template 视图补全

后端(Admin.php):
- SeatTemplateDelete/VenueDelete:新增 hard_delete 参数
  - hard_delete=0(默认):软删除(status→0),返回'已禁用'
  - hard_delete=1:真删除,先检查商品关联再 DELETE
- SeatTemplateEnable/VenueEnable:新增启用 API,含审计日志

前端(view/venue/list.html):
- 按钮改为三按钮布局:编辑 / 禁用/启用 / 删除
- 删除按钮点击后弹出警告弹窗
  - 警告:删除记录不会导致已上架商品内容变动
  - 若需同步,请编辑对应商品并保存
- 禁用/启用按钮使用 submit-ajax,data-view=reload 自动刷新

新增(admin/view/seat_template/):
- list.html:座位模板列表(三按钮布局,与 venue/list.html 相同)
- save.html:座位模板编辑页(基础版,seat_map 由 venue 编辑器管理)
council/ProductManager
Council 2026-04-20 15:08:27 +08:00
parent 168d85e61d
commit df8353a697
4 changed files with 441 additions and 6 deletions

View File

@ -235,11 +235,39 @@ class Admin extends Common
return DataReturn('参数错误', -1);
}
$hardDelete = input('hard_delete', 0, 'intval');
$template = \think\facade\Db::name('vr_seat_templates')->where('id', $id)->find();
if (empty($template)) {
return DataReturn('记录不存在', -1);
}
if ($hardDelete) {
// 真删除:先检查是否有商品关联
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete', 0)
->find();
if (!empty($goods)) {
return DataReturn('该模板有关联商品,请先解除商品绑定后再删除', -402);
}
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->delete();
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_DELETE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['name' => $template['name']],
"模板: {$template['name']}"
);
return DataReturn('删除成功', 0);
}
// 软删除(禁用)
\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,
@ -248,7 +276,35 @@ class Admin extends Common
$template ? "模板: {$template['name']}" : "ID:{$id}"
);
return DataReturn('删除成功', 0);
return DataReturn('已禁用', 0);
}
public function SeatTemplateEnable()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
\think\facade\Db::name('vr_seat_templates')
->where('id', $id)
->update(['status' => 1, 'upd_time' => time()]);
\think\facade\Db::name('vr_seat_templates')
->where('id', $id)
->update(['status' => 1, 'upd_time' => time()]);
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_ENABLE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['after_status' => 1],
"模板ID:{$id}"
);
return DataReturn('已启用', 0);
}
// ============================================================
@ -811,11 +867,37 @@ class Admin extends Common
return DataReturn('参数错误', -1);
}
$hardDelete = input('hard_delete', 0, 'intval');
$template = \think\facade\Db::name('vr_seat_templates')->where('id', $id)->find();
if (empty($template)) {
return DataReturn('记录不存在', -1);
}
if ($hardDelete) {
// 真删除:先检查是否有商品关联
$goods = \think\facade\Db::name('Goods')
->where('vr_goods_config', 'like', '%"template_id":' . $id . '%')
->where('is_delete', 0)
->find();
if (!empty($goods)) {
return DataReturn('该模板有关联商品,请先解除商品绑定后再删除', -402);
}
\think\facade\Db::name('vr_seat_templates')->where('id', $id)->delete();
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_DELETE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['name' => $template['name']],
"场馆: {$template['name']}"
);
return DataReturn('删除成功', 0);
}
// 软删除(禁用)
\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,
@ -823,8 +905,31 @@ class Admin extends Common
['before_status' => $template['status'] ?? 1],
$template ? "场馆: {$template['name']}" : "ID:{$id}"
);
return DataReturn('已禁用', 0);
}
return DataReturn('删除成功', 0);
public function VenueEnable()
{
if (!(request()->isAjax() && request()->isPost())) {
return DataReturn('非法请求', -1);
}
$id = input('id', 0, 'intval');
if ($id <= 0) {
return DataReturn('参数错误', -1);
}
\think\facade\Db::name('vr_seat_templates')
->where('id', $id)
->update(['status' => 1, 'upd_time' => time()]);
\app\plugins\vr_ticket\service\AuditService::log(
\app\plugins\vr_ticket\service\AuditService::ACTION_ENABLE_TEMPLATE,
\app\plugins\vr_ticket\service\AuditService::TARGET_TEMPLATE,
$id,
['after_status' => 1],
"场馆ID:{$id}"
);
return DataReturn('已启用', 0);
}
// ============================================================

View File

@ -0,0 +1,193 @@
{{:ModuleInclude('public/header')}}
<!-- right content start -->
<div class="content-right" style="padding: 0 40px;">
<div class="content">
<!-- 页面顶部导航栏/工具栏 -->
<div class="am-cf am-padding-bottom-sm">
<div class="am-fr">
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'Setup')}}" class="am-btn am-btn-warning am-btn-sm am-radius" title="全局设置高德 API 等参数">
<i class="am-icon-cog"></i> 插件全局设置
</a>
</div>
</div>
<!-- 搜索与筛选区域 -->
<div class="am-panel am-panel-default am-radius">
<div class="am-panel-bd">
<form class="am-form form-validation form-search" method="post" action="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateList')}}" request-type="form">
<div class="am-g am-g-fixed">
<!-- 基本搜索: 模板名称 -->
<div class="am-u-sm-12 am-u-md-4">
<div class="am-form-group">
<label class="am-text-sm">模板名称</label>
<input type="text" autocomplete="off" name="name" class="am-radius am-form-field" placeholder="搜索模板名称..." value="{{if !empty($data_req['name'])}}{{$data_req['name']}}{{/if}}" />
</div>
</div>
<!-- 状态筛选 -->
<div class="am-u-sm-12 am-u-md-3">
<div class="am-form-group">
<label class="am-text-sm">状态</label>
<select name="status" class="chosen-select am-radius" data-placeholder="全部状态">
<option value="">全部状态</option>
<option value="1" {{if isset($data_req['status']) and $data_req['status'] eq '1'}}selected{{/if}}>启用</option>
<option value="0" {{if isset($data_req['status']) and $data_req['status'] eq '0'}}selected{{/if}}>禁用</option>
</select>
</div>
</div>
<!-- 操作按钮组 -->
<div class="am-u-sm-12 am-u-md-5 am-text-right" style="padding-top: 24px;">
<button class="am-btn am-btn-primary am-radius am-btn-sm" type="submit" data-am-loading="{spinner:'circle-o-notch', loadingText:'搜索中...'}">
<i class="am-icon-search"></i> 查询数据
</button>
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateList')}}" class="am-btn am-btn-default am-radius am-btn-sm am-margin-left-xs">
<i class="am-icon-refresh"></i> 重置
</a>
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateSave')}}" class="am-btn am-btn-success am-radius am-btn-sm am-margin-left-sm">
<i class="am-icon-plus"></i> 添加模板
</a>
</div>
</div>
</form>
</div>
</div>
<!-- 数据列表区域 -->
<div class="am-panel am-panel-default am-radius am-margin-top-lg">
<div class="am-panel-hd">座位模板列表</div>
<div class="am-scrollable-horizontal">
<table class="am-table am-table-striped am-table-hover am-text-middle am-margin-bottom-0">
<thead>
<tr>
<th class="am-text-center" width="60">ID</th>
<th class="am-text-left">模板信息</th>
<th class="am-text-left">绑定的分类</th>
<th class="am-text-center" width="90">座位数</th>
<th class="am-text-center" width="90">状态</th>
<th class="am-text-right" width="160">操作</th>
</tr>
</thead>
<tbody>
{{if !empty($list)}}
{{foreach $list as $v}}
<tr id="data-list-{{$v.id}}">
<td class="am-text-center">
<span class="am-text-secondary">{{$v.id}}</span>
</td>
<td class="am-text-left">
<div class="am-text-md"><strong>{{$v.name}}</strong></div>
<div class="am-text-xs am-text-grey" style="margin: 2px 0;">
座位数:{{$v.seat_count}}
</div>
</td>
<td class="am-text-left">
{{if !empty($v.category_name)}}
<span class="am-badge am-badge-secondary am-radius am-text-xs">{{$v.category_name}}</span>
{{else /}}
<span class="am-text-grey">-</span>
{{/if}}
</td>
<td class="am-text-center">
<span class="am-badge am-badge-success am-radius">{{$v.seat_count}}</span>
</td>
<td class="am-text-center">
{{if $v.status eq 1}}
<span class="am-text-success"><i class="am-icon-check-circle"></i> 启用</span>
{{else /}}
<span class="am-text-danger"><i class="am-icon-times-circle"></i> 禁用</span>
{{/if}}
</td>
<td class="am-text-right view-operation">
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateSave', ['id'=>$v['id']])}}" class="am-btn am-btn-secondary am-btn-xs am-radius">
<i class="am-icon-edit"></i> 编辑
</a>
{{if $v.status eq 1}}
<button class="am-btn am-btn-warning am-btn-xs am-radius am-margin-left-xs submit-ajax" data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateDelete')}}" data-id="{{$v.id}}" data-view="reload" data-msg="确定要禁用此模板?">
<i class="am-icon-ban"></i> 禁用
</button>
<button class="am-btn am-btn-danger am-btn-xs am-radius am-margin-left-xs btn-open-delete-confirm" data-id="{{$v.id}}">
<i class="am-icon-trash-o"></i> 删除
</button>
{{else}}
<button class="am-btn am-btn-success am-btn-xs am-radius am-margin-left-xs submit-ajax" data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateEnable')}}" data-id="{{$v.id}}" data-view="reload" data-msg="确定要启用此模板?">
<i class="am-icon-check"></i> 启用
</button>
{{/if}}
</td>
</tr>
{{/foreach}}
{{else /}}
<tr><td colspan="6" class="table-no">暂无模板数据</td></tr>
{{/if}}
</tbody>
</table>
</div>
</div>
<!-- 全局删除确认弹窗(单个,供所有行共用) -->
<div class="am-modal am-modal-confirm" id="stmpl-confirm-delete-modal">
<div class="am-modal-dialog">
<div class="am-modal-hd">
<i class="am-icon-exclamation-triangle am-text-warning" style="font-size:1.3em;vertical-align:middle;"></i>
<span style="margin-left:6px;">确定删除此模板?</span>
<a class="am-modal-close" href="javascript:void(0)">&times;</a>
</div>
<div class="am-modal-bd am-text-left">
<p style="color:#e00;font-weight:bold;margin-bottom:8px;">⚠️ 删除记录不会导致已上架商品内容变动。</p>
<p style="color:#555;">若需要同步场馆信息到已发布商品,请编辑对应商品并保存。</p>
</div>
<div class="am-modal-footer">
<button class="am-btn am-btn-default am-radius" data-am-modal-cancel>取消</button>
<button class="am-btn am-btn-danger am-radius btn-do-real-delete">确认删除</button>
</div>
</div>
</div>
<script>
$(function() {
// 删除按钮:打开弹窗并记录当前行 ID
$(document).on('click', '.btn-open-delete-confirm', function() {
var id = $(this).data('id');
$('#stmpl-confirm-delete-modal').find('.btn-do-real-delete').attr('data-id', id);
$('#stmpl-confirm-delete-modal').modal('open');
});
// 弹窗确认删除:构造 hard_delete=1 参数并提交
$(document).on('click', '.btn-do-real-delete', function() {
var $modal = $('#stmpl-confirm-delete-modal');
var id = $(this).attr('data-id');
var url = '{{:PluginsAdminUrl("vr_ticket", "admin", "SeatTemplateDelete")}}';
$modal.modal('close');
AMUI.dialog.loading({ title: '正在删除...' });
$.ajax({
url: RequestUrlHandle(url),
type: 'POST',
dataType: 'json',
data: { id: id, hard_delete: 1 },
success: function(result) {
$.AMUI.progress.done();
Prompt(result.msg, result.code == 0 ? 'success' : 'warning');
if (result.code == 0) {
setTimeout(function() { location.reload(); }, 1200);
}
},
error: function() {
$.AMUI.progress.done();
Prompt('删除失败');
}
});
});
});
</script>
<div class="am-margin-top-sm">
{{if !empty($list)}}
{{$page|raw}}
{{/if}}
</div>
</div>
</div>
<!-- right content end -->
{{:ModuleInclude('public/footer')}}

View File

@ -0,0 +1,73 @@
{{:ModuleInclude('public/header')}}
<!-- right content start -->
<div class="content-right" style="padding: 0 40px;">
<div class="content">
<div class="am-panel am-panel-default am-radius">
<div class="am-panel-hd">
{{if !empty($info)}}
编辑座位模板
{{else}}
添加座位模板
{{/if}}
</div>
<div class="am-panel-bd">
<form class="am-form am-form-horizontal" method="POST" action="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateSave')}}" request-type="ajax-form">
{{if !empty($info)}}
<input type="hidden" name="id" value="{{$info.id}}" />
{{/if}}
<div class="am-form-group">
<label class="am-u-sm-2 am-control-label"><em>*</em> 模板名称</label>
<div class="am-u-sm-9">
<input type="text" name="name" class="am-radius am-form-field" placeholder="例如演唱会A区" value="{{if !empty($info)}}{{$info.name}}{{/if}}" required />
</div>
</div>
<div class="am-form-group">
<label class="am-u-sm-2 am-control-label"><em>*</em> 绑定分类</label>
<div class="am-u-sm-9">
<select name="category_id" class="am-radius" required>
<option value="">请选择分类</option>
{{if !empty($categories)}}
{{foreach $categories as $c}}
<option value="{{$c.id}}" {{if !empty($info) and $info.category_id eq $c.id}}selected{{/if}}>{{$c.name}}</option>
{{/foreach}}
{{/if}}
</select>
<span class="am-text-xs am-text-grey am-margin-left-xs">绑定分类后,该分类下的商品可使用此模板</span>
</div>
</div>
<div class="am-form-group">
<label class="am-u-sm-2 am-control-label">状态</label>
<div class="am-u-sm-9 am-form-select">
<select name="status" class="am-radius">
<option value="1" {{if empty($info) or $info.status eq 1}}selected{{/if}}>启用</option>
<option value="0" {{if !empty($info) and $info.status eq 0}}selected{{/if}}>禁用</option>
</select>
</div>
</div>
<!-- seat_map 由 v3 场馆编辑器管理,此处不提供编辑 -->
<input type="hidden" name="seat_map" value="{{if !empty($info)}}{{$info.seat_map}}{{else}}{}{{/if}}" />
<input type="hidden" name="spec_base_id_map" value="{{if !empty($info)}}{{$info.spec_base_id_map}}{{/if}}" />
<div class="am-form-group">
<div class="am-u-sm-offset-2 am-u-sm-9">
<button type="submit" class="am-btn am-btn-primary am-btn-sm am-radius" data-am-loading="{spinner:'circle-o-notch', loadingText:'保存中...'}">
<i class="am-icon-save"></i> 保存
</button>
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'SeatTemplateList')}}" class="am-btn am-btn-default am-btn-sm am-radius am-margin-left-sm">
<i class="am-icon-reply"></i> 返回
</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- right content end -->
{{:ModuleInclude('public/footer')}}

View File

@ -114,8 +114,18 @@
<a href="{{:PluginsAdminUrl('vr_ticket', 'admin', 'VenueSave', ['id'=>$v['id']])}}" class="am-btn am-btn-secondary am-btn-xs am-radius">
<i class="am-icon-edit"></i> 编辑
</a>
<!-- 座位模板逻辑暂保留旧入口或按需隐藏 -->
<button class="am-btn am-btn-danger am-btn-xs am-radius am-icon-trash-o submit-delete am-margin-left-xs" data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'VenueDelete')}}" data-id="{{$v.id}}"> 删除</button>
{{if $v.status eq 1}}
<button class="am-btn am-btn-warning am-btn-xs am-radius am-margin-left-xs submit-ajax" data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'VenueDelete')}}" data-id="{{$v.id}}" data-view="reload" data-msg="确定要禁用此场馆?">
<i class="am-icon-ban"></i> 禁用
</button>
<button class="am-btn am-btn-danger am-btn-xs am-radius am-margin-left-xs btn-open-delete-confirm" data-id="{{$v.id}}">
<i class="am-icon-trash-o"></i> 删除
</button>
{{else}}
<button class="am-btn am-btn-success am-btn-xs am-radius am-margin-left-xs submit-ajax" data-url="{{:PluginsAdminUrl('vr_ticket', 'admin', 'VenueEnable')}}" data-id="{{$v.id}}" data-view="reload" data-msg="确定要启用此场馆?">
<i class="am-icon-check"></i> 启用
</button>
{{/if}}
</td>
</tr>
{{/foreach}}
@ -127,7 +137,61 @@
</div>
</div>
<!-- 分页 -->
<!-- 全局删除确认弹窗(单个,供所有行共用) -->
<div class="am-modal am-modal-confirm" id="venue-confirm-delete-modal">
<div class="am-modal-dialog">
<div class="am-modal-hd">
<i class="am-icon-exclamation-triangle am-text-warning" style="font-size:1.3em;vertical-align:middle;"></i>
<span style="margin-left:6px;">确定删除此场馆?</span>
<a class="am-modal-close" href="javascript:void(0)">&times;</a>
</div>
<div class="am-modal-bd am-text-left">
<p style="color:#e00;font-weight:bold;margin-bottom:8px;">⚠️ 删除记录不会导致已上架商品内容变动。</p>
<p style="color:#555;">若需要同步场馆信息到已发布商品,请编辑对应商品并保存。</p>
</div>
<div class="am-modal-footer">
<button class="am-btn am-btn-default am-radius" data-am-modal-cancel>取消</button>
<button class="am-btn am-btn-danger am-radius btn-do-real-delete">确认删除</button>
</div>
</div>
</div>
<script>
$(function() {
// 删除按钮:打开弹窗并记录当前行 ID
$(document).on('click', '.btn-open-delete-confirm', function() {
var id = $(this).data('id');
$('#venue-confirm-delete-modal').find('.btn-do-real-delete').attr('data-id', id);
$('#venue-confirm-delete-modal').modal('open');
});
// 弹窗确认删除:构造 hard_delete=1 参数并提交
$(document).on('click', '.btn-do-real-delete', function() {
var $modal = $('#venue-confirm-delete-modal');
var id = $(this).attr('data-id');
var url = '{{:PluginsAdminUrl("vr_ticket", "admin", "VenueDelete")}}';
$modal.modal('close');
AMUI.dialog.loading({ title: '正在删除...' });
$.ajax({
url: RequestUrlHandle(url),
type: 'POST',
dataType: 'json',
data: { id: id, hard_delete: 1 },
success: function(result) {
$.AMUI.progress.done();
Prompt(result.msg, result.code == 0 ? 'success' : 'warning');
if (result.code == 0) {
setTimeout(function() { location.reload(); }, 1200);
}
},
error: function() {
$.AMUI.progress.done();
Prompt('删除失败');
}
});
});
});
</script>
<div class="am-margin-top-sm">
{{if !empty($list)}}
{{$page|raw}}