退款页面优化
This commit is contained in:
parent
696746c4da
commit
95146c207b
@ -307,5 +307,23 @@ export const usereventrecordMemberValidatePost = (data) => {
|
||||
* }>}
|
||||
*/
|
||||
export const usereventrecordRegistrationLimitCheckPost = (data) => {
|
||||
return http.post('/usereventrecord/registration-limit/check', data);
|
||||
return http.post("usereventrecord/registration-limit/check", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算退款金额
|
||||
* @param {Object} data - 计算退款金额请求
|
||||
* @param {number} data.RegistrationId - 报名ID
|
||||
* @param {number} data.CancelType - 取消类型(0-全部取消,1-部分取消)
|
||||
* @param {Array<number>} [data.PersonIds] - 要取消的队员ID列表(部分取消时使用)
|
||||
* @param {Array<string>} [data.CancelTeams] - 要取消的团队列表(团队项目部分取消时使用)
|
||||
* @returns {Promise<{
|
||||
* RefundAmount: number,
|
||||
* RemainingRefundableAmount: number,
|
||||
* RefundedAmount: number,
|
||||
* ActualPaidAmount: number
|
||||
* }>}
|
||||
*/
|
||||
export const usereventrecordCalculateRefund = (data) => {
|
||||
return http.post("usereventrecord/cancel/calculate-refund", data);
|
||||
};
|
||||
@ -468,10 +468,14 @@
|
||||
{
|
||||
'member-selected': selectedMembers.includes(member.memberId),
|
||||
'team-removed': isTeamRemoved(team.registrationItemId, team.teamIndex),
|
||||
'disabled': project.projectType === '团队'
|
||||
'disabled': project.projectType === '团队',
|
||||
'personal-selected': project.projectType === '个人' && selectedMembers.includes(member.memberId)
|
||||
}
|
||||
]"
|
||||
@click="toggleMember(member.memberId)">
|
||||
<!-- 个人项目取消遮罩 -->
|
||||
<div v-if="project.projectType === '个人' && selectedMembers.includes(member.memberId)" class="member-overlay"></div>
|
||||
|
||||
{{ member.name }}
|
||||
<el-tag
|
||||
v-if="member.isLeader"
|
||||
@ -480,12 +484,22 @@
|
||||
style="margin-left: 4px; font-size: 10px;">
|
||||
队长
|
||||
</el-tag>
|
||||
<el-icon
|
||||
v-if="project.projectType === '个人' && selectedMembers.includes(member.memberId)"
|
||||
class="remove-icon"
|
||||
@click.stop="removeMember(member.memberId)">
|
||||
<Close />
|
||||
</el-icon>
|
||||
|
||||
<!-- 个人项目取消按钮 -->
|
||||
<div v-if="project.projectType === '个人'" class="personal-remove-container">
|
||||
<el-icon
|
||||
v-if="selectedMembers.includes(member.memberId)"
|
||||
class="personal-remove-icon"
|
||||
@click.stop="toggleMember(member.memberId)">
|
||||
<CircleCheck />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-else
|
||||
class="personal-remove-icon"
|
||||
@click.stop="toggleMember(member.memberId)">
|
||||
<Close />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -518,10 +532,14 @@
|
||||
{
|
||||
'member-selected': selectedMembers.includes(member.memberId),
|
||||
'team-removed': project.projectType === '团队' && teamRemoved,
|
||||
'disabled': project.projectType === '团队'
|
||||
'disabled': project.projectType === '团队',
|
||||
'personal-selected': project.projectType === '个人' && selectedMembers.includes(member.memberId)
|
||||
}
|
||||
]"
|
||||
@click="toggleMember(member.memberId)">
|
||||
<!-- 个人项目取消遮罩 -->
|
||||
<div v-if="project.projectType === '个人' && selectedMembers.includes(member.memberId)" class="member-overlay"></div>
|
||||
|
||||
{{ member.name }}
|
||||
<el-tag
|
||||
v-if="member.isLeader"
|
||||
@ -530,12 +548,22 @@
|
||||
style="margin-left: 4px; font-size: 10px;">
|
||||
队长
|
||||
</el-tag>
|
||||
<el-icon
|
||||
v-if="project.projectType === '个人' && selectedMembers.includes(member.memberId)"
|
||||
class="remove-icon"
|
||||
@click.stop="removeMember(member.memberId)">
|
||||
<Close />
|
||||
</el-icon>
|
||||
|
||||
<!-- 个人项目取消按钮 -->
|
||||
<div v-if="project.projectType === '个人'" class="personal-remove-container">
|
||||
<el-icon
|
||||
v-if="selectedMembers.includes(member.memberId)"
|
||||
class="personal-remove-icon"
|
||||
@click.stop="toggleMember(member.memberId)">
|
||||
<CircleCheck />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-else
|
||||
class="personal-remove-icon"
|
||||
@click.stop="toggleMember(member.memberId)">
|
||||
<Close />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -555,13 +583,31 @@
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 退款信息显示(通用) -->
|
||||
<div class="refund-info-display" style="background: #f5f7fa; padding: 15px; border-radius: 6px; margin-bottom: 16px;">
|
||||
<div class="refund-info-item" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span>实际支付:</span>
|
||||
<span>¥{{ formatAmount(refundCalculationResult?.actualPaidAmount) }}</span>
|
||||
</div>
|
||||
<div v-if="refundCalculationResult?.refundedAmount > 0" class="refund-info-item" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span>已退款:</span>
|
||||
<span style="color: #f56c6c;">¥{{ formatAmount(refundCalculationResult?.refundedAmount) }}</span>
|
||||
</div>
|
||||
<div class="refund-info-item" style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
||||
<span>剩余可退款:</span>
|
||||
<span style="color: #e6a23c; font-weight: bold;">¥{{ formatAmount(refundCalculationResult?.refundableAmount) }}</span>
|
||||
</div>
|
||||
<div class="refund-info-item" style="display: flex; justify-content: space-between; padding-top: 8px; border-top: 1px solid #e4e7ed;">
|
||||
<span style="font-weight: bold;">本次退款:</span>
|
||||
<span v-if="calculatingRefund" style="font-weight: bold; color: #909399;">计算中...</span>
|
||||
<span v-else style="font-weight: bold; color: #1fae66;">¥{{ formatAmount(totalRefundAmount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 线下支付退款表单(仅在需要时显示) -->
|
||||
<template v-if="cancelPreviewData?.needPayeeInfo">
|
||||
<el-divider content-position="left">退款信息</el-divider>
|
||||
<el-form :model="refundForm" label-width="120px" style="max-width: 500px;">
|
||||
<el-form-item label="退款金额">
|
||||
<span style="font-size: 18px; font-weight: bold; color: #1fae66;">¥{{ totalRefundAmount.toFixed(2) }}</span>
|
||||
</el-form-item>
|
||||
<el-divider content-position="left">退款账户信息</el-divider>
|
||||
<el-form :model="refundForm" label-width="140px" style="max-width: 550px;">
|
||||
<el-form-item label="收款人姓名" required>
|
||||
<el-input v-model="refundForm.payeeName" placeholder="请输入收款人姓名" />
|
||||
</el-form-item>
|
||||
@ -584,7 +630,7 @@
|
||||
<template #extra>
|
||||
<div v-if="cancelResult?.success">
|
||||
<p v-if="cancelResult?.refundAmount > 0">
|
||||
退款金额:<span style="font-weight: bold; color: #1fae66;">¥{{ cancelResult?.refundAmount?.toFixed(2) }}</span>
|
||||
退款金额:<span style="font-weight: bold; color: #1fae66;">¥{{ formatAmount(cancelResult?.refundAmount) }}</span>
|
||||
</p>
|
||||
<p v-if="cancelResult?.refundNo">
|
||||
退款单号:{{ cancelResult?.refundNo }}
|
||||
@ -603,7 +649,7 @@
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span class="refund-label">退款金额</span>
|
||||
<span class="refund-amount">¥{{ totalRefundAmount.toFixed(2) }}</span>
|
||||
<span class="refund-amount">¥{{ formatAmount(totalRefundAmount) }}</span>
|
||||
<el-button @click="isShowCancelDialog = false" class="footer-btn">上一步</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@ -648,6 +694,7 @@ import {
|
||||
usereventrecordUpdateMembersPost,
|
||||
usereventrecordMemberValidatePost,
|
||||
usereventrecordRegistrationLimitCheckPost,
|
||||
usereventrecordCalculateRefund,
|
||||
} from '/@/api/match/usereventrecord';
|
||||
import { teamPageGet, teamDetailGet } from '/@/api/match/unitUser';
|
||||
|
||||
@ -1232,126 +1279,88 @@ const mockMembers3 = ref([
|
||||
]);
|
||||
|
||||
// 新的响应式变量
|
||||
const selectedMembers = ref([]); // 选择的队员ID
|
||||
const removedTeams = ref(new Set()); // 存储被移除的团队(格式:registrationItemId_teamIndex
|
||||
const cancelAll = ref(false);
|
||||
const hasValidationError = ref(false);
|
||||
const hasLimitError = ref(false);
|
||||
const hasCountError = ref(false);
|
||||
const isFreeProject = ref(false); // 控制是否为免费项目
|
||||
// 线下支付收款人信息
|
||||
const refundForm = reactive({
|
||||
payeeName: '',
|
||||
payeeBankName: '',
|
||||
payeeBankAccount: ''
|
||||
});
|
||||
|
||||
const totalRefundAmount = computed(() => {
|
||||
// 免费项目退款金额为0
|
||||
if (isFreeProject.value || cancelPreviewData.value?.chargeMode === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果取消全部,直接返回可退款金额
|
||||
if (cancelAll.value) {
|
||||
return cancelPreviewData.value?.refundableAmount ?? 0;
|
||||
}
|
||||
|
||||
// 根据收费模式动态计算退款金额
|
||||
const chargeMode = cancelPreviewData.value?.chargeMode;
|
||||
const refundableAmount = cancelPreviewData.value?.refundableAmount ?? 0;
|
||||
const actualPaidAmount = cancelPreviewData.value?.actualPaidAmount ?? 0;
|
||||
|
||||
// 计算退款比例
|
||||
let refundRatio = 1;
|
||||
if (actualPaidAmount > 0 && refundableAmount >= 0) {
|
||||
refundRatio = refundableAmount / actualPaidAmount;
|
||||
}
|
||||
|
||||
let calculatedAmount = 0;
|
||||
|
||||
switch (chargeMode) {
|
||||
case 1: // 定价(赛事)
|
||||
// 定价赛事:个人取消不退款,团队取消按比例退款
|
||||
// 检查是否有团队被取消
|
||||
const hasRemovedTeam = removedTeams.value.size > 0;
|
||||
if (hasRemovedTeam) {
|
||||
calculatedAmount = refundableAmount;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // 单价(赛事)
|
||||
// 单价赛事:个人取消按比例退款,团队取消按比例退款
|
||||
// 统计取消的人数
|
||||
let cancelCount = 0;
|
||||
|
||||
// 统计团队被取消的团队中的队员数量
|
||||
for (const project of cancelPreviewData.value?.projectDetails || []) {
|
||||
for (const group of project.groups || []) {
|
||||
for (const team of group.teams || []) {
|
||||
const teamKey = `${team.registrationItemId}_${team.teamIndex}`;
|
||||
if (removedTeams.value.has(teamKey)) {
|
||||
cancelCount += (team.members?.length || 0);
|
||||
} else {
|
||||
// 统计该团队中被单独取消的队员
|
||||
for (const member of team.members || []) {
|
||||
if (selectedMembers.value.includes(member.memberId)) {
|
||||
cancelCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 统计总人数
|
||||
let totalCount = 0;
|
||||
for (const member of cancelPreviewData.value?.members || []) {
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
// 计算单价
|
||||
const unitPriceEvent = totalCount > 0 ? actualPaidAmount / totalCount : 0;
|
||||
calculatedAmount = unitPriceEvent * cancelCount * refundRatio;
|
||||
break;
|
||||
|
||||
case 3: // 单价(项目)
|
||||
// 单价项目:个人取消按比例退款,团队取消按比例退款
|
||||
let projectCancelAmount = 0;
|
||||
|
||||
for (const project of cancelPreviewData.value?.projectDetails || []) {
|
||||
for (const group of project.groups || []) {
|
||||
const unitPrice = group.unitPrice || 0;
|
||||
|
||||
for (const team of group.teams || []) {
|
||||
const teamKey = `${team.registrationItemId}_${team.teamIndex}`;
|
||||
|
||||
if (removedTeams.value.has(teamKey)) {
|
||||
// 团队取消:整个团队都取消
|
||||
projectCancelAmount += unitPrice * (team.members?.length || 0);
|
||||
} else {
|
||||
// 统计该团队中被单独取消的队员
|
||||
for (const member of team.members || []) {
|
||||
if (selectedMembers.value.includes(member.memberId)) {
|
||||
projectCancelAmount += unitPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
calculatedAmount = projectCancelAmount * refundRatio;
|
||||
break;
|
||||
|
||||
default:
|
||||
calculatedAmount = 0;
|
||||
}
|
||||
|
||||
return Math.max(0, calculatedAmount);
|
||||
const selectedMembers = ref([]); // 选择的队员ID
|
||||
const removedTeams = ref(new Set()); // 存储被移除的团队(格式:registrationItemId_teamIndex
|
||||
const cancelAll = ref(false);
|
||||
const hasValidationError = ref(false);
|
||||
const hasLimitError = ref(false);
|
||||
const hasCountError = ref(false);
|
||||
const isFreeProject = ref(false); // 控制是否为免费项目
|
||||
// 线下支付收款人信息
|
||||
const refundForm = reactive({
|
||||
payeeName: '',
|
||||
payeeBankName: '',
|
||||
payeeBankAccount: ''
|
||||
});
|
||||
|
||||
const toggleMember = (memberId) => {
|
||||
// 安全的金额格式化函数
|
||||
const formatAmount = (amount) => {
|
||||
if (amount === null || amount === undefined || isNaN(amount)) {
|
||||
return '0.00';
|
||||
}
|
||||
return Number(amount).toFixed(2);
|
||||
};
|
||||
|
||||
// 后端返回的退款金额数据
|
||||
const refundCalculationResult = ref({
|
||||
refundAmount: 0,
|
||||
refundableAmount: 0,
|
||||
refundedAmount: 0,
|
||||
actualPaidAmount: 0
|
||||
});
|
||||
|
||||
const calculatingRefund = ref(false);
|
||||
|
||||
// 调用后端计算退款金额的函数
|
||||
const calculateRefundAmount = async () => {
|
||||
if (!cancelPreviewData.value?.registrationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 免费项目不计算
|
||||
if (isFreeProject.value || cancelPreviewData.value?.chargeMode === 0) {
|
||||
refundCalculationResult.value = {
|
||||
refundAmount: 0,
|
||||
refundableAmount: cancelPreviewData.value?.refundableAmount ?? 0,
|
||||
refundedAmount: cancelPreviewData.value?.refundedAmount ?? 0,
|
||||
actualPaidAmount: cancelPreviewData.value?.actualPaidAmount ?? 0
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
calculatingRefund.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
RegistrationId: cancelPreviewData.value.registrationId,
|
||||
CancelType: cancelAll.value ? 0 : 1,
|
||||
PersonIds: cancelAll.value ? undefined : [...selectedMembers.value],
|
||||
CancelTeams: cancelAll.value ? undefined : [...removedTeams.value]
|
||||
};
|
||||
|
||||
const { succeeded, data } = await usereventrecordCalculateRefund(payload);
|
||||
if (succeeded && data) {
|
||||
refundCalculationResult.value = {
|
||||
// 这里只更新本次退款金额
|
||||
refundAmount: data.refundAmount ?? 0,
|
||||
// 剩余可退款、已退款、实际支付都来自预览数据,保持一致
|
||||
refundableAmount: cancelPreviewData.value?.refundableAmount ?? 0,
|
||||
refundedAmount: cancelPreviewData.value?.refundedAmount ?? 0,
|
||||
actualPaidAmount: cancelPreviewData.value?.actualPaidAmount ?? 0
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('计算退款金额失败:', e);
|
||||
} finally {
|
||||
calculatingRefund.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const totalRefundAmount = computed(() => {
|
||||
return refundCalculationResult.value?.refundAmount ?? 0;
|
||||
});
|
||||
|
||||
|
||||
const toggleMember = async (memberId) => {
|
||||
// 如果取消全部被选中,则不允许手动修改
|
||||
if (cancelAll.value) {
|
||||
return;
|
||||
@ -1365,8 +1374,7 @@ const toggleMember = (memberId) => {
|
||||
for (const project of cancelPreviewData.value.projectDetails) {
|
||||
if (project.projectType === '团队') {
|
||||
for (const group of project.groups || []) {
|
||||
for (const team of group.teams || []) {
|
||||
for (const member of team.members || []) {
|
||||
for (const team of group.teams || []) { for (const member of team.members || []) {
|
||||
if (member.memberId === memberId) {
|
||||
isTeamMember = true;
|
||||
break;
|
||||
@ -1400,6 +1408,9 @@ const toggleMember = (memberId) => {
|
||||
} else {
|
||||
selectedMembers.value.push(memberId);
|
||||
}
|
||||
|
||||
// 重新计算退款金额
|
||||
await calculateRefundAmount();
|
||||
};
|
||||
|
||||
const removeMember = (memberId) => {
|
||||
@ -1420,7 +1431,7 @@ const isTeamRemoved = (registrationItemId, teamIndex) => {
|
||||
return removedTeams.value.has(teamKey);
|
||||
};
|
||||
|
||||
const toggleTeam = (registrationItemId, teamIndex) => {
|
||||
const toggleTeam = async (registrationItemId, teamIndex) => {
|
||||
const teamKey = `${registrationItemId}_${teamIndex}`;
|
||||
|
||||
if (removedTeams.value.has(teamKey)) {
|
||||
@ -1442,6 +1453,9 @@ const toggleTeam = (registrationItemId, teamIndex) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算退款金额
|
||||
await calculateRefundAmount();
|
||||
};
|
||||
|
||||
// 项目级别的团队移除功能(兼容旧逻辑)
|
||||
@ -1524,7 +1538,7 @@ const toggleTeamForProject = (projectId) => {
|
||||
};
|
||||
|
||||
// 监听取消全部的变化
|
||||
watch(cancelAll, (newVal) => {
|
||||
watch(cancelAll, async (newVal) => {
|
||||
if (newVal) {
|
||||
// 取消全部:选中所有队员,并取消所有团队
|
||||
selectedMembers.value = (cancelPreviewData.value?.members || []).map(m => m.memberId);
|
||||
@ -1544,6 +1558,9 @@ watch(cancelAll, (newVal) => {
|
||||
selectedMembers.value = [];
|
||||
removedTeams.value.clear();
|
||||
}
|
||||
|
||||
// 重新计算退款金额
|
||||
await calculateRefundAmount();
|
||||
});
|
||||
|
||||
const openCancelDialog = async (row) => {
|
||||
@ -1572,6 +1589,16 @@ const openCancelDialog = async (row) => {
|
||||
cancelPreviewData.value = data;
|
||||
// 根据后端返回的数据设置是否为免费项目
|
||||
isFreeProject.value = data?.chargeMode === 0 || data?.actualPaidAmount <= 0;
|
||||
|
||||
// 初始化退款计算结果 - 先使用preview数据
|
||||
refundCalculationResult.value = {
|
||||
// 初始状态下没有选择取消项,所以本次退款金额为0
|
||||
refundAmount: 0,
|
||||
// 剩余可退款金额直接使用preview返回的数据
|
||||
refundableAmount: data?.refundableAmount ?? 0,
|
||||
refundedAmount: data?.refundedAmount ?? 0,
|
||||
actualPaidAmount: data?.actualPaidAmount ?? 0
|
||||
};
|
||||
} else {
|
||||
ElMessage.error('获取取消报名预览失败');
|
||||
isShowCancelDialog.value = false;
|
||||
@ -1632,7 +1659,7 @@ const submitCancel = async () => {
|
||||
// 添加取消的团队
|
||||
cancelTeams: Array.from(removedTeams.value),
|
||||
// 添加计算后的退款金额
|
||||
calculatedRefundAmount: totalRefundAmount.value
|
||||
calculatedRefundAmount: totalRefundAmount
|
||||
};
|
||||
|
||||
if (!cancelAll.value) {
|
||||
@ -2389,6 +2416,68 @@ const submitChangeMembers = async () => {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
// 个人项目取消相关样式
|
||||
.member-tag {
|
||||
&.personal-selected {
|
||||
background: white;
|
||||
position: relative;
|
||||
overflow: visible; // 保持可见,让图标在容器外显示
|
||||
}
|
||||
|
||||
// 选中状态下的图标样式
|
||||
&.personal-selected .personal-remove-icon {
|
||||
color: #1fae66;
|
||||
border-color: #1fae66;
|
||||
background: white;
|
||||
|
||||
&:hover {
|
||||
color: #1fae66;
|
||||
background: rgba(31, 174, 102, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(144, 147, 153, 0.6);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.personal-remove-container {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.personal-remove-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: white;
|
||||
color: var(--el-text-color-secondary);
|
||||
border: 2px solid var(--el-border-color-lighter);
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
border-color: #f56c6c;
|
||||
background: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证提示
|
||||
.validation-hints {
|
||||
margin-bottom: 16px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user