This commit is contained in:
zpc 2025-12-07 18:22:50 +08:00
parent 131768c78b
commit c058f45f55
5 changed files with 315 additions and 83 deletions

View File

@ -1,4 +1,4 @@
/***********************************************************************
/***********************************************************************
* Project: CoreCms
* ProjectName:
* Web: https://www.corecms.net
@ -1195,6 +1195,169 @@ ICoreCmsUserServices userServices
#endregion
#region ======================================================
// POST: Api/SQReservations/DissolveReservation
/// <summary>
/// 后台解散预约(强制取消整个组局)
/// </summary>
/// <param name="entity">预约ID</param>
/// <returns></returns>
[HttpPost]
[Description("解散预约")]
public async Task<AdminUiCallBack> DissolveReservation([FromBody] FMIntId entity)
{
var jm = new AdminUiCallBack();
if (entity == null || entity.id <= 0)
{
jm.msg = "参数错误";
return jm;
}
// 查询预约信息
var reservation = await _SQReservationsServices.QueryByClauseAsync(r => r.id == entity.id);
if (reservation == null)
{
jm.msg = "预约不存在";
return jm;
}
// 检查预约状态
if (reservation.status >= 3) // 3=已结束4=已取消
{
jm.msg = "该预约已结束或已取消,无法再次解散";
return jm;
}
// 1. 更新预约状态为已取消
var updateResult = await _SQReservationsServices.UpdateAsync(
it => new SQReservations
{
status = 4, // 已取消
updated_at = DateTime.Now,
remarks = $"后台于{DateTime.Now:yyyy-MM-dd HH:mm:ss}强制解散"
},
it => it.id == entity.id);
if (!updateResult)
{
jm.msg = "解散预约失败";
return jm;
}
// 2. 有押金时,已支付押金(is_refund=2)的参与者标记为发起退款(is_refund=3)
if (reservation.deposit_fee.HasValue && reservation.deposit_fee.Value > 0)
{
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
is_refund = 3 // 发起退款
},
it => it.reservation_id == entity.id && it.status == 0 && it.is_refund == 2 && it.paymentId != null && it.paymentId != "");
}
// 3. 所有参与者标记为已退出
await _SQReservationParticipantsServices.UpdateAsync(
it => new SQReservationParticipants
{
status = 1, // 已退出
quit_time = DateTime.Now,
remarks = $"后台于{DateTime.Now:yyyy-MM-dd HH:mm:ss}强制解散组局"
},
it => it.reservation_id == entity.id && it.status == 0);
jm.code = 0;
jm.msg = "解散预约成功";
return jm;
}
#endregion
#region ======================================================
// POST: Api/SQReservations/KickParticipant
/// <summary>
/// 后台请出参与者(如果是发起者则解散整个组局)
/// </summary>
/// <param name="entity">参与者ID</param>
/// <returns></returns>
[HttpPost]
[Description("请出参与者")]
public async Task<AdminUiCallBack> KickParticipant([FromBody] FMIntId entity)
{
var jm = new AdminUiCallBack();
if (entity == null || entity.id <= 0)
{
jm.msg = "参数错误";
return jm;
}
// 查询参与者信息
var participant = await _SQReservationParticipantsServices.QueryByClauseAsync(p => p.id == entity.id);
if (participant == null)
{
jm.msg = "参与者不存在";
return jm;
}
// 检查参与者状态
if (participant.status == 1)
{
jm.msg = "该参与者已退出";
return jm;
}
// 查询预约信息
var reservation = await _SQReservationsServices.QueryByClauseAsync(r => r.id == participant.reservation_id);
if (reservation == null)
{
jm.msg = "预约不存在";
return jm;
}
// 检查预约状态
if (reservation.status >= 3)
{
jm.msg = "该预约已结束或已取消";
return jm;
}
// 如果是发起者,解散整个组局
if (participant.role == 1)
{
// 调用解散预约逻辑
return await DissolveReservation(new FMIntId { id = reservation.id });
}
// 普通参与者:标记为已退出
participant.status = 1;
participant.quit_time = DateTime.Now;
participant.remarks = $"后台于{DateTime.Now:yyyy-MM-dd HH:mm:ss}请出组局";
// 有押金且已支付时,标记为发起退款
if (reservation.deposit_fee.HasValue && reservation.deposit_fee.Value > 0 && participant.is_refund == 2 && !string.IsNullOrEmpty(participant.paymentId))
{
participant.is_refund = 3; // 发起退款
}
var updateResult = await _SQReservationParticipantsServices.UpdateAsync(participant);
if (updateResult.code != 0)
{
jm.msg = "请出参与者失败";
return jm;
}
jm.code = 0;
jm.msg = "请出参与者成功";
return jm;
}
#endregion
#region SearchUsers
// POST: Api/SQReservations/SearchUsers
/// <summary>
@ -1216,8 +1379,8 @@ ICoreCmsUserServices userServices
if (!string.IsNullOrEmpty(searchText))
{
// 支持按用户名、昵称、手机号搜索
where = where.And(p => p.userName.Contains(searchText) ||
p.nickName.Contains(searchText) ||
where = where.And(p => p.userName.Contains(searchText) ||
p.nickName.Contains(searchText) ||
p.mobile.Contains(searchText) ||
p.id.ToString().Contains(searchText));
}
@ -1278,8 +1441,8 @@ ICoreCmsUserServices userServices
}
// 检查用户是否已经是参与者
var existingParticipant = await _SQReservationParticipantsServices.QueryListByClauseAsync(p =>
p.reservation_id == entity.reservationId &&
var existingParticipant = await _SQReservationParticipantsServices.QueryListByClauseAsync(p =>
p.reservation_id == entity.reservationId &&
p.user_id == entity.userId);
if (existingParticipant.Any())
@ -1365,8 +1528,8 @@ ICoreCmsUserServices userServices
}
// 检查该预约是否已有发起者
var existingInitiator = await _SQReservationParticipantsServices.QueryListByClauseAsync(p =>
p.reservation_id == entity.reservationId &&
var existingInitiator = await _SQReservationParticipantsServices.QueryListByClauseAsync(p =>
p.reservation_id == entity.reservationId &&
p.role == 1); // 1=发起者
if (existingInitiator.Any())
@ -1401,7 +1564,7 @@ ICoreCmsUserServices userServices
return jm;
}
#endregion
}
}

View File

@ -3990,6 +3990,20 @@
</summary>
<returns></returns>
</member>
<member name="M:CoreCms.Net.Web.Admin.Controllers.SQReservationsController.DissolveReservation(CoreCms.Net.Model.FromBody.FMIntId)">
<summary>
后台解散预约(强制取消整个组局)
</summary>
<param name="entity">预约ID</param>
<returns></returns>
</member>
<member name="M:CoreCms.Net.Web.Admin.Controllers.SQReservationsController.KickParticipant(CoreCms.Net.Model.FromBody.FMIntId)">
<summary>
后台请出参与者(如果是发起者则解散整个组局)
</summary>
<param name="entity">参与者ID</param>
<returns></returns>
</member>
<member name="M:CoreCms.Net.Web.Admin.Controllers.SQReservationsController.SearchUsers">
<summary>
搜索用户

View File

@ -164,7 +164,8 @@
slotHtml += ' <div class="layui-col-md3"><strong>组局名称:</strong>' + (reservation.title || '无') + '</div>';
slotHtml += ' <div class="layui-col-md2"><strong>参与人数:</strong>' + (reservation.player_count || 0) + '人</div>';
slotHtml += ' <div class="layui-col-md2"><strong>鸽子费:</strong>' + (reservation.deposit_fee || 0) + '元</div>';
slotHtml += ' <div class="layui-col-md3"><strong>最晚到店:</strong>' + (reservation.latest_arrival_time || '无') + '</div>';
slotHtml += ' <div class="layui-col-md2"><strong>最晚到店:</strong>' + (reservation.latest_arrival_time || '无') + '</div>';
slotHtml += ' <div class="layui-col-md2" style="text-align: right;"><button class="layui-btn layui-btn-danger layui-btn-sm btn-dissolve-reservation" data-id="' + reservation.id + '" data-title="' + (reservation.title || '无') + '">解散预约</button></div>';
slotHtml += ' </div>';
slotHtml += ' <div class="layui-row" style="margin-top: 5px;">';
slotHtml += ' <div class="layui-col-md3"><strong>玩法类型:</strong>' + (reservation.game_type || '无') + '</div>';
@ -255,13 +256,90 @@
return '<span style="background: ' + refundColor + '; color: white; padding: 2px 6px; border-radius: 3px; font-size: 12px;">' + refundText + '</span>';
}
},
{ field: 'remarks', title: '备注', minWidth: 150 }
{ field: 'remarks', title: '备注', minWidth: 120 },
{
field: 'operation',
title: '操作',
width: 140,
fixed: 'right',
templet: function (d) {
if (d.status === 1) {
return '<span style="color: #999;">已退出</span>';
}
var btnText = d.role === 1 ? '请出(解散组局)' : '请出组局';
var btnClass = d.role === 1 ? 'layui-btn-danger' : 'layui-btn-warm';
return '<button class="layui-btn ' + btnClass + ' layui-btn-xs btn-kick-participant" data-id="' + d.id + '" data-role="' + d.role + '" data-name="' + (d.user_name || '') + '">'+btnText+'</button>';
}
}
]]
});
});
}
}
}
// 绑定解散预约按钮事件
$(document).off('click', '.btn-dissolve-reservation').on('click', '.btn-dissolve-reservation', function () {
var reservationId = $(this).data('id');
var reservationTitle = $(this).data('title');
layer.confirm('确定要解散组局【' + reservationTitle + '】吗?<br><br><span style="color: #FF5722;">所有已支付押金的参与者将发起退款。</span>', {
title: '解散预约确认',
btn: ['确定解散', '取消'],
icon: 3
}, function (index) {
layer.close(index);
var loadIndex = layer.load(2);
coreHelper.Post("Api/SQReservations/DissolveReservation", { id: reservationId }, function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg('解散成功', { icon: 1 });
// 关闭弹窗并刷新对应日期
layer.closeAll('page');
if (typeof window._refreshSingleDay === 'function') {
window._refreshSingleDay(dateStr);
}
} else {
layer.msg(res.msg || '解散失败', { icon: 2 });
}
});
});
});
// 绑定请出参与者按钮事件
$(document).off('click', '.btn-kick-participant').on('click', '.btn-kick-participant', function () {
var participantId = $(this).data('id');
var role = $(this).data('role');
var userName = $(this).data('name');
var confirmMsg = '';
if (role === 1) {
confirmMsg = '该用户【' + userName + '】是<span style="color: #FF5722;">发起者</span>,请出将<span style="color: #FF5722;">解散整个组局</span>,确定继续吗?';
} else {
confirmMsg = '确定要将用户【' + userName + '】请出组局吗?<br><br><span style="color: #999;">如已支付押金将发起退款。</span>';
}
layer.confirm(confirmMsg, {
title: '请出组局确认',
btn: ['确定请出', '取消'],
icon: 3
}, function (index) {
layer.close(index);
var loadIndex = layer.load(2);
coreHelper.Post("Api/SQReservations/KickParticipant", { id: participantId }, function (res) {
layer.close(loadIndex);
if (res.code === 0) {
layer.msg(res.msg || '操作成功', { icon: 1 });
// 关闭弹窗并刷新对应日期
layer.closeAll('page');
if (typeof window._refreshSingleDay === 'function') {
window._refreshSingleDay(dateStr);
}
} else {
layer.msg(res.msg || '操作失败', { icon: 2 });
}
});
});
});
} else {
layer.msg(e.msg || '获取数据失败');
}

View File

@ -21,106 +21,81 @@
</div>
</div>
<!-- 提示信息 -->
<div class="layui-form-item">
<div class="layui-input-block">
<blockquote class="layui-elem-quote layui-quote-nm" style="border-left-color: #ff9800;">
<i class="layui-icon layui-icon-tips" style="color: #ff9800;"></i>
未启用的时段将使用普通价格,已启用的时段将覆盖当天的普通价格
</blockquote>
</div>
</div>
<!-- 时段价格列表 -->
<div class="time-slot-list" style="margin-top: 20px;">
<!-- 凌晨时段 -->
<div class="time-slot-item" data-slot="0" style="border: 1px solid #e6e6e6; border-radius: 4px; padding: 15px; margin-bottom: 15px; background: #fff;">
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<label class="layui-form-label" style="width: 80px;">凌晨:</label>
<div class="layui-input-block" style="margin-left: 90px; display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_0" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; flex: 1; min-width: 500px;">
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">普通价格描述:</span>
<input type="text" name="price_desc_standard_0" placeholder="例如150元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">会员价格描述:</span>
<input type="text" name="price_desc_member_0" placeholder="例如120元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="color: #999; font-size: 12px;" id="default_price_0">
(默认: 普通 <span class="default-standard">-</span> / 会员 <span class="default-member">-</span>)
<div class="layui-input-block" style="margin-left: 90px;">
<div style="display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_0" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; display: flex; align-items: center;">
<span style="margin-right: 5px;">普通价格:</span>
<input type="text" name="price_desc_standard_0" placeholder="请输入价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
<span style="margin: 0 15px 0 20px;">会员价格:</span>
<input type="text" name="price_desc_member_0" placeholder="请输入会员价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
</div>
</div>
<div class="default-price-tip" id="default_price_tip_0" style="color: #999; font-size: 12px; margin-top: 8px; margin-left: 60px; display: none;"></div>
</div>
</div>
</div>
<!-- 上午时段 -->
<div class="time-slot-item" data-slot="1" style="border: 1px solid #e6e6e6; border-radius: 4px; padding: 15px; margin-bottom: 15px; background: #fff;">
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<label class="layui-form-label" style="width: 80px;">上午:</label>
<div class="layui-input-block" style="margin-left: 90px; display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_1" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; flex: 1; min-width: 500px;">
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">普通价格描述:</span>
<input type="text" name="price_desc_standard_1" placeholder="例如200元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">会员价格描述:</span>
<input type="text" name="price_desc_member_1" placeholder="例如180元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="color: #999; font-size: 12px;" id="default_price_1">
(默认: 普通 <span class="default-standard">-</span> / 会员 <span class="default-member">-</span>)
<div class="layui-input-block" style="margin-left: 90px;">
<div style="display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_1" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; display: flex; align-items: center;">
<span style="margin-right: 5px;">普通价格:</span>
<input type="text" name="price_desc_standard_1" placeholder="请输入价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
<span style="margin: 0 15px 0 20px;">会员价格:</span>
<input type="text" name="price_desc_member_1" placeholder="请输入会员价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
</div>
</div>
<div class="default-price-tip" id="default_price_tip_1" style="color: #999; font-size: 12px; margin-top: 8px; margin-left: 60px; display: none;"></div>
</div>
</div>
</div>
<!-- 下午时段 -->
<div class="time-slot-item" data-slot="2" style="border: 1px solid #e6e6e6; border-radius: 4px; padding: 15px; margin-bottom: 15px; background: #fff;">
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<label class="layui-form-label" style="width: 80px;">下午:</label>
<div class="layui-input-block" style="margin-left: 90px; display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_2" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; flex: 1; min-width: 500px;">
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">普通价格描述:</span>
<input type="text" name="price_desc_standard_2" placeholder="例如200元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">会员价格描述:</span>
<input type="text" name="price_desc_member_2" placeholder="例如180元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="color: #999; font-size: 12px;" id="default_price_2">
(默认: 普通 <span class="default-standard">-</span> / 会员 <span class="default-member">-</span>)
<div class="layui-input-block" style="margin-left: 90px;">
<div style="display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_2" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; display: flex; align-items: center;">
<span style="margin-right: 5px;">普通价格:</span>
<input type="text" name="price_desc_standard_2" placeholder="请输入价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
<span style="margin: 0 15px 0 20px;">会员价格:</span>
<input type="text" name="price_desc_member_2" placeholder="请输入会员价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
</div>
</div>
<div class="default-price-tip" id="default_price_tip_2" style="color: #999; font-size: 12px; margin-top: 8px; margin-left: 60px; display: none;"></div>
</div>
</div>
</div>
<!-- 晚上时段 -->
<div class="time-slot-item" data-slot="3" style="border: 1px solid #e6e6e6; border-radius: 4px; padding: 15px; margin-bottom: 15px; background: #fff;">
<div class="layui-form-item" style="margin-bottom: 10px;">
<div class="layui-form-item" style="margin-bottom: 0;">
<label class="layui-form-label" style="width: 80px;">晚上:</label>
<div class="layui-input-block" style="margin-left: 90px; display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_3" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; flex: 1; min-width: 500px;">
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">普通价格描述:</span>
<input type="text" name="price_desc_standard_3" placeholder="例如250元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="margin-bottom: 10px;">
<span style="margin-right: 5px;">会员价格描述:</span>
<input type="text" name="price_desc_member_3" placeholder="例如220元/时段" class="layui-input" style="width: 200px; display: inline-block;" disabled>
</div>
<div style="color: #999; font-size: 12px;" id="default_price_3">
(默认: 普通 <span class="default-standard">-</span> / 会员 <span class="default-member">-</span>)
<div class="layui-input-block" style="margin-left: 90px;">
<div style="display: flex; align-items: center; flex-wrap: wrap;">
<input type="checkbox" name="switch_3" lay-skin="switch" lay-text="启用|关闭" lay-filter="slot-switch">
<div style="margin-left: 20px; display: flex; align-items: center;">
<span style="margin-right: 5px;">普通价格:</span>
<input type="text" name="price_desc_standard_3" placeholder="请输入价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
<span style="margin: 0 15px 0 20px;">会员价格:</span>
<input type="text" name="price_desc_member_3" placeholder="请输入会员价格描述" class="layui-input" style="width: 150px; display: inline-block;" disabled>
</div>
</div>
<div class="default-price-tip" id="default_price_tip_3" style="color: #999; font-size: 12px; margin-top: 8px; margin-left: 60px; display: none;"></div>
</div>
</div>
</div>
@ -179,9 +154,10 @@
var slotType = slot.time_slot_type;
var slotItem = $('.time-slot-item[data-slot="' + slotType + '"]');
// 显示默认价格描述
slotItem.find('#default_price_' + slotType + ' .default-standard').text(slot.default_price_desc_standard || '-');
slotItem.find('#default_price_' + slotType + ' .default-member').text(slot.default_price_desc_member || '-');
// 显示默认价格提示
var defaultStandard = slot.default_price_desc_standard || '-';
var defaultMember = slot.default_price_desc_member || '-';
$('#default_price_tip_' + slotType).html('当前默认价格:普通 ' + defaultStandard + ' / 会员 ' + defaultMember).show();
if (slot.has_holiday_price) {
// 已有节假日价格,回显
@ -190,10 +166,10 @@
slotItem.find('input[name="price_desc_member_' + slotType + '"]').val(slot.holiday_price_desc_member || '');
toggleSlotForm(slotType, true);
} else {
// 没有节假日价格,使用默认价格描述
// 没有节假日价格,不填充默认值,保持输入框为空
slotItem.find('input[name="switch_' + slotType + '"]').prop('checked', false);
slotItem.find('input[name="price_desc_standard_' + slotType + '"]').val(slot.default_price_desc_standard || '');
slotItem.find('input[name="price_desc_member_' + slotType + '"]').val(slot.default_price_desc_member || '');
slotItem.find('input[name="price_desc_standard_' + slotType + '"]').val('');
slotItem.find('input[name="price_desc_member_' + slotType + '"]').val('');
toggleSlotForm(slotType, false);
}
});

View File

@ -1161,7 +1161,8 @@
});
}
// 刷新单个日期
// 刷新单个日期同时挂载到window供弹窗调用
window._refreshSingleDay = refreshSingleDay;
function refreshSingleDay(dateStr) {
if (!selectedRoomId || !dateStr) {
return;