修复bug

This commit is contained in:
马梓彭 2026-04-05 21:01:02 +08:00
parent 570f28a7e1
commit c4d302f0ca
6 changed files with 211 additions and 40 deletions

View File

@ -283,4 +283,13 @@ export const usereventregistrationGroupTeamLimitGet = (eventId) => {
*/
export const usereventregistrationProjectCompletionCheckPost = (data) => {
return http.post('/usereventregistration/project-completion/check', data);
};
/**
* 查询订单支付状态
* @param {string} orderNo - 订单号
* @returns {Promise<number>} - 0:未支付, 1:已支付
*/
export const usereventregistrationOrderStatusGet = (orderNo) => {
return http.get(`/usereventregistration/order/status/${orderNo}`);
};

View File

@ -104,14 +104,14 @@
</el-form-item>
<span v-else>{{ localFormData.registrationLimit.individualProjectLimit }}</span>
</div>
<div class="limit-summary__card">
<div class="limit-summary__card" v-if="!isOnlyIndividual">
<div class="limit-summary__label">
<span class="required-star" v-if="!readonly">*</span>
团队项目限报数
</div>
<el-form-item v-if="!readonly"
prop="registrationLimit.teamProjectLimit"
:rules="[{ required: true, message: '请填写团队项目限报数', trigger: 'change' }]"
:rules="isOnlyIndividual ? [] : [{ required: true, message: '请填写团队项目限报数', trigger: 'change' }]"
style="margin: 0"
>
<el-input-number v-model="localFormData.registrationLimit.teamProjectLimit" :min="0" :step="1" :disabled="readonly" />
@ -147,7 +147,7 @@
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { ref, watch, nextTick, computed } from 'vue';
import ProTable from '/@/components/ProTable/index.vue';
import { projectPageGet, projectDetailGet } from '/@/api/match/project';
import { ElMessage } from 'element-plus';
@ -158,6 +158,11 @@ const props = defineProps({
});
const emit = defineEmits(['update:modelValue']);
// 判断是否只允许个人报名
const isOnlyIndividual = computed(() => {
return props.modelValue?.registrationType === 0;
});
const formRef = ref(null);
const localFormData = ref({
projects: [],
@ -297,10 +302,16 @@ watch(
/* addProject and removeProject are declared above to avoid initialization order issues */
const rules = {
'registrationLimit.individualProjectLimit': [{ required: true, message: '请填写个人项目限报数', trigger: 'change' }],
'registrationLimit.teamProjectLimit': [{ required: true, message: '请填写团队项目限报数', trigger: 'change' }],
};
const rules = computed(() => {
const baseRules = {
'registrationLimit.individualProjectLimit': [{ required: true, message: '请填写个人项目限报数', trigger: 'change' }],
};
// 只有在不是仅允许个人报名时,才添加团队项目限报数的验证
if (!isOnlyIndividual.value) {
baseRules['registrationLimit.teamProjectLimit'] = [{ required: true, message: '请填写团队项目限报数', trigger: 'change' }];
}
return baseRules;
});
const validate = async () => {
if (!formRef.value) return false;

View File

@ -96,7 +96,10 @@ const currentStepData = computed(() => {
case 1:
return formData.value.units;
case 2:
return formData.value.projectConfig;
return {
...formData.value.projectConfig,
registrationType: formData.value.basicInfo.registrationType
};
case 3:
return formData.value.feeConfig;
}
@ -113,7 +116,8 @@ const handleStepDataUpdate = (newData) => {
break;
case 2:
console.log('Received data from step', activeStep.value, newData);
formData.value.projectConfig = newData;
const { registrationType, ...projectConfig } = newData;
formData.value.projectConfig = projectConfig;
break;
case 3:
formData.value.feeConfig = newData;

View File

@ -100,9 +100,9 @@ const columns = reactive([
prop: 'registrationType',
label: '报名方式',
enum: [
{ label: '两者都有', value: 0 },
{ label: '只允许个人报名', value: 1 },
{ label: '只允许团队报名', value: 2 },
{ label: '只允许个人报名', value: 0 },
{ label: '只允许团队报名', value: 1 },
{ label: '两者都有', value: 2 },
],
},
{

View File

@ -2,23 +2,28 @@
<div class="system-menu-container layout-padding">
<ProTable ref="tableRef" :request-api="orderPageGet" :columns="columns" :pagination="true"
:tool-button="true">
<!-- <template #tools>
<el-button v-auth="'order:export'" type="primary" icon="ele-Plus" @click="onOpenMenu"> 导出 </el-button>
</template> -->
<template #tools>
<el-button v-auth="'order:export'" type="primary" :loading="exportLoading" @click="handleExport">
<template #icon>
<el-icon><Download /></el-icon>
</template>
导出
</el-button>
</template>
<template #index="scope">
{{ scope.$index + 1 }}
</template>
<template #paymentMethod="scope">
{{ scope.row.paymentMethod }}
{{ getPaymentMethodText(scope.row.paymentMethod) }}
</template>
<template #orderType="scope">
{{ scope.row.orderType }}
{{ getOrderTypeText(scope.row.orderType) }}
</template>
<template #registrationMode="scope">
{{ scope.row.registrationMode }}
{{ getRegistrationModeText(scope.row.registrationMode) }}
</template>
<template #orderStatus="scope">
{{ scope.row.orderStatusText || scope.row.orderStatus }}
{{ scope.row.orderStatusText || getOrderStatusText(scope.row.orderStatus) }}
</template>
<template #action="scope">
<el-button size="small" text type="primary" @click="onOpenMenu(scope.row.id)">
@ -49,11 +54,14 @@
<script setup >
import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
import { Download } from '@element-plus/icons-vue';
import ProTable from '/@/components/ProTable/index.vue';
import { orderPageGet , orderDetailGet} from '/@/api/match/order';
import { orderPageGet , orderDetailGet, orderExportGet } from '/@/api/match/order';
// 定义表单引用类型
const formRef = ref();
const tableRef = ref();
const exportLoading = ref(false);
const formData = ref({
orderNo: '',
@ -104,6 +112,38 @@ const normalizeNames = (value) => {
return '';
};
const getRegistrationModeText = (value) => {
const v = Number(value);
if (v === 1) return '个人';
if (v === 2) return '团队';
return '-';
};
const getOrderTypeText = (value) => {
const v = Number(value);
if (v === 1) return '收款';
if (v === 2) return '退款';
return '-';
};
const getPaymentMethodText = (value) => {
const v = Number(value);
if (v === 1) return '线上';
if (v === 2) return '线下';
return '-';
};
const getOrderStatusText = (value) => {
const v = Number(value);
if (v === 0) return '待支付';
if (v === 1) return '已支付';
if (v === 2) return '已取消';
if (v === 3) return '已退款';
if (v === 4) return '已发货';
if (v === 5) return '已完成';
return '-';
};
const detailTableData = computed(() => {
const items = formData.value?.items ?? [];
const persons = formData.value?.persons ?? [];
@ -147,6 +187,35 @@ const onOpenMenu = async(id) => {
isShowDialog.value = true;
};
const handleExport = async () => {
try {
exportLoading.value = true;
const searchParams = tableRef.value?.searchParam || {};
const response = await orderExportGet(searchParams);
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `订单列表_${new Date().getTime()}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
ElMessage.success('导出成功');
} catch (error) {
console.error('导出失败:', error);
ElMessage.error('导出失败,请稍后重试');
} finally {
exportLoading.value = false;
}
};

View File

@ -25,11 +25,35 @@
</template>
<template #registrationStartTime="scope">
{{ formatRange(scope.row.registrationStartTime, scope.row.registrationEndTime) }}
<div class="time-range-display">
<div class="time-item">
<span class="time-label">开始</span>
<span class="time-value">{{ formatDateTime(scope.row.registrationStartTime) }}</span>
</div>
<div class="time-separator">
<el-icon><ArrowRight /></el-icon>
</div>
<div class="time-item">
<span class="time-label">结束</span>
<span class="time-value">{{ formatDateTime(scope.row.registrationEndTime) }}</span>
</div>
</div>
</template>
<template #matchStartTime="scope">
{{ formatRange(scope.row.matchStartTime, scope.row.matchEndTime) }}
<div class="time-range-display">
<div class="time-item">
<span class="time-label">开始</span>
<span class="time-value">{{ formatDateTime(scope.row.matchStartTime) }}</span>
</div>
<div class="time-separator">
<el-icon><ArrowRight /></el-icon>
</div>
<div class="time-item">
<span class="time-label">结束</span>
<span class="time-value">{{ formatDateTime(scope.row.matchEndTime) }}</span>
</div>
</div>
</template>
<template #action="scope">
@ -70,6 +94,21 @@
<!-- 赛事报名三步弹窗 -->
<el-dialog title="赛事报名" v-model="isShowRegisterDialog" width="900px" :close-on-click-modal="false"
@closed="clearRegisterState">
<!-- 规则提示 -->
<el-alert
title="报名规则"
type="info"
:closable="false"
style="margin-bottom: 20px;"
show-icon
>
<template #default>
<ul style="margin: 0; padding-left: 20px;">
<li>一次报名操作仅允许选择并提交<strong>单个团队</strong>内的运动员</li>
<li>该团队只能报名参与<strong>一个项目</strong></li>
</ul>
</template>
</el-alert>
<el-steps :active="activeStep" finish-status="success" align-center>
<el-step title="选择报名项目" />
<el-step title="分配队员" />
@ -244,7 +283,7 @@
<!-- 队伍数量限制提示 -->
<el-alert
v-if="showTeamLimitHint"
title="若管理后台配置一个组别只能限报2队,添加2队后,“添加”按钮隐藏"
title="若管理后台配置一个组别只能限报2队,添加2队后,"添加"按钮隐藏"
type="info"
:closable="false"
style="margin-bottom: 12px;"
@ -402,7 +441,7 @@
<script setup>
import { ref, computed, nextTick, onUnmounted } from 'vue';
import { ElMessage } from 'element-plus';
import { Close, Picture, Loading, CircleCheck } from '@element-plus/icons-vue';
import { Close, Picture, Loading, CircleCheck, ArrowRight } from '@element-plus/icons-vue';
import ProTable from '/@/components/ProTable/index.vue';
import { formatDate } from '/@/utils/formatTime';
import {
@ -417,6 +456,7 @@ import {
usereventregistrationRegistrationLimitCheckPost,
usereventregistrationGroupTeamLimitGet,
usereventregistrationProjectCompletionCheckPost,
usereventregistrationOrderStatusGet,
} from '/@/api/match/usereventregistration';
import { teamPageGet, teamDetailGet } from '/@/api/match/unitUser';
import QRCode from 'qrcodejs2-fixes';
@ -442,8 +482,8 @@ const columns = ref([
{ prop: 'registrationType', label: '报名方式', align: 'center', width: 120 },
{ prop: 'chargeMode', label: '收费模式', align: 'center', width: 120 },
{ prop: 'status', label: '赛事状态', align: 'center', width: 120 },
{ prop: 'registrationStartTime', label: '报名起止时间', align: 'center', minWidth: 220 },
{ prop: 'matchStartTime', label: '比赛起止时间', align: 'center', minWidth: 220 },
{ prop: 'registrationStartTime', label: '报名起止时间', align: 'center', minWidth: 320 },
{ prop: 'matchStartTime', label: '比赛起止时间', align: 'center', minWidth: 320 },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 210 },
]);
@ -766,6 +806,15 @@ const toggleGroupSelection = (id) => {
if (idx >= 0) {
selectedGroupIds.value.splice(idx, 1);
} else {
// 检查是否已经选择了其他项目的组别
const groupToAdd = allGroups.value.find(g => g.id === id);
if (groupToAdd && selectedGroupIds.value.length > 0) {
const existingProjectId = allGroups.value.find(g => g.id === selectedGroupIds.value[0])?.projectId;
if (groupToAdd.projectId !== existingProjectId) {
ElMessage.warning('一次报名只能选择一个项目,请先取消已选择的项目');
return;
}
}
selectedGroupIds.value.push(id);
}
};
@ -811,6 +860,7 @@ const teamMembers = ref([]);
const teamMemberLoading = ref(false);
const activeAssignGroupId = ref(null); // 当前正在分配队员的组别ID
const groupMemberMap = ref({}); // { groupId: [member, ...] }
const usedTeamId = ref(null); // 本次报名已使用的团队ID
const previewLoading = ref(false);
const previewData = ref(null);
@ -856,6 +906,7 @@ const clearRegisterState = () => {
teamMemberLoading.value = false;
activeAssignGroupId.value = null;
groupMemberMap.value = {};
usedTeamId.value = null;
memberValidationMap.value = {};
groupTeamLimitMap.value = {};
memberRegistrationLimitMap.value = {};
@ -889,6 +940,7 @@ const resetStep2AndBeyond = () => {
teamMemberLoading.value = false;
activeAssignGroupId.value = null;
groupMemberMap.value = {};
usedTeamId.value = null;
previewData.value = null;
previewLoading.value = false;
draftRegistrationId.value = null;
@ -947,6 +999,15 @@ const fetchTeamList = async () => {
const onTeamChange = async (teamId) => {
teamMembers.value = [];
if (!teamId) return;
// 检查是否已经分配了其他团队的队员
if (usedTeamId.value && usedTeamId.value !== teamId) {
ElMessage.warning('本次报名已选择了其他团队的队员,请先移除已分配的队员');
// 恢复到之前选择的团队
selectedTeamId.value = usedTeamId.value;
return;
}
teamMemberLoading.value = true;
try {
const { succeeded, data } = await teamDetailGet(teamId);
@ -960,6 +1021,7 @@ const onTeamChange = async (teamId) => {
mobile: m.mobile ?? '',
gender: m.gender ?? '',
role: m.role ?? '',
teamId: teamId, // 添加团队ID
}));
}
} catch {
@ -1065,6 +1127,12 @@ const assignMemberToActiveGroup = async (member) => {
return;
}
// 检查队员是否来自已选择的团队
if (usedTeamId.value && member.teamId !== usedTeamId.value) {
ElMessage.warning('本次报名只能选择同一个团队的队员');
return;
}
// 获取当前组别信息进行验证
const group = selectedGroupItems.value.find(g => g.id === gId);
if (group) {
@ -1073,10 +1141,14 @@ const assignMemberToActiveGroup = async (member) => {
ElMessage.warning(validation.message);
return;
}
}
groupMemberMap.value[gId].push({ ...member });
// 设置已使用的团队
if (!usedTeamId.value) {
usedTeamId.value = member.teamId;
}
groupMemberMap.value[gId].push({ ...member, teamId: member.teamId });
// 实时检查项目完成人数和报名限制
await checkProjectCompletion();
await checkRegistrationLimit();
@ -1086,6 +1158,17 @@ const assignMemberToActiveGroup = async (member) => {
const removeMemberFromGroup = (groupId, memberId) => {
if (!groupMemberMap.value[groupId]) return;
groupMemberMap.value[groupId] = groupMemberMap.value[groupId].filter((m) => (m.userId ?? m.id) !== memberId);
// 检查是否还有队员,如果没有则重置已使用的团队
let hasMembers = false;
Object.values(groupMemberMap.value).forEach(members => {
if (members.length > 0) {
hasMembers = true;
}
});
if (!hasMembers) {
usedTeamId.value = null;
}
};
// 验证优惠券
@ -1368,17 +1451,11 @@ const startPolling = (orderNo) => {
pollingTimer = setInterval(async () => {
try {
// TODO: 调用后端接口查询支付状态
// const { succeeded, data } = await usereventregistrationQueryPayStatusGet(orderNo);
// 暂时模拟支付成功(实际应该调用后端接口)
// 这里应该根据实际返回的状态进行处理
// 模拟5秒后支付成功
// if (data?.status === 'SUCCESS') {
// stopPolling();
// onPaymentSuccess();
// }
const { succeeded, data } = await usereventregistrationOrderStatusGet(orderNo);
if (succeeded && data === 1) {
stopPolling();
onPaymentSuccess();
}
} catch (error) {
console.error('查询支付状态失败:', error);
}
@ -1397,11 +1474,12 @@ const stopPolling = () => {
// 支付成功处理
const onPaymentSuccess = () => {
pollingStatus.value = '支付成功!';
ElMessage.success('支付成功!报名已完成');
initParam.value.refresh = !initParam.value.refresh;
setTimeout(() => {
isShowRegisterDialog.value = false;
}, 1500);
}, 2000);
};
// 取消支付