fix:多项前端修复与功能对齐

- 修复评委端进入评审contestId为NaN(record.id→record.contestId)
- 修复评委评审详情403(活动名称改为路由传参,跳过需要contest:read权限的接口)
- 已发布活动隐藏编辑按钮
- 添加评委成功提示去重(移除子组件重复message)
- 用户端活动阶段判断修复(报名与提交重叠时优先显示提交阶段)
- 用户端作品提交支持submitRule(once/resubmit)重新提交
- 后端公共API补充submitRule字段返回
- 报名统计接口增加租户隔离,修复统计与列表数据不一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhonghua 2026-04-03 20:29:28 +08:00
parent 3ef05de193
commit 1003776dd3
8 changed files with 70 additions and 12 deletions

View File

@ -37,7 +37,9 @@ public class ContestRegistrationController {
@RequirePermission("contest:read")
@Operation(summary = "获取报名统计")
public Result<Map<String, Object>> getStats(@RequestParam(required = false) Long contestId) {
return Result.success(registrationService.getStats(contestId, SecurityUtil.getCurrentTenantId()));
Long tenantId = SecurityUtil.getCurrentTenantId();
boolean isSuperAdmin = SecurityUtil.isSuperAdmin();
return Result.success(registrationService.getStats(contestId, tenantId, isSuperAdmin));
}
@GetMapping

View File

@ -15,7 +15,7 @@ public interface IContestRegistrationService extends IService<BizContestRegistra
PageResult<Map<String, Object>> findAll(QueryRegistrationDto dto, Long tenantId, boolean isSuperTenant);
Map<String, Object> getStats(Long contestId, Long tenantId);
Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperAdmin);
Map<String, Object> findDetail(Long id, Long tenantId);

View File

@ -123,20 +123,26 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
}
@Override
public Map<String, Object> getStats(Long contestId, Long tenantId) {
log.info("获取报名统计赛事ID{}", contestId);
public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperAdmin) {
log.info("获取报名统计赛事ID{}租户ID{},超管:{}", contestId, tenantId, isSuperAdmin);
// 非超管需要按租户过滤与列表查询保持一致
LambdaQueryWrapper<BizContestRegistration> baseWrapper = new LambdaQueryWrapper<>();
if (contestId != null) {
baseWrapper.eq(BizContestRegistration::getContestId, contestId);
}
if (!isSuperAdmin && tenantId != null) {
baseWrapper.eq(BizContestRegistration::getTenantId, tenantId);
}
long total = count(baseWrapper);
LambdaQueryWrapper<BizContestRegistration> pendingWrapper = new LambdaQueryWrapper<>();
if (contestId != null) {
pendingWrapper.eq(BizContestRegistration::getContestId, contestId);
}
if (!isSuperAdmin && tenantId != null) {
pendingWrapper.eq(BizContestRegistration::getTenantId, tenantId);
}
pendingWrapper.eq(BizContestRegistration::getRegistrationState, "pending");
long pending = count(pendingWrapper);
@ -144,6 +150,9 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
if (contestId != null) {
passedWrapper.eq(BizContestRegistration::getContestId, contestId);
}
if (!isSuperAdmin && tenantId != null) {
passedWrapper.eq(BizContestRegistration::getTenantId, tenantId);
}
passedWrapper.eq(BizContestRegistration::getRegistrationState, "passed");
long passed = count(passedWrapper);
@ -151,6 +160,9 @@ public class ContestRegistrationServiceImpl extends ServiceImpl<ContestRegistrat
if (contestId != null) {
rejectedWrapper.eq(BizContestRegistration::getContestId, contestId);
}
if (!isSuperAdmin && tenantId != null) {
rejectedWrapper.eq(BizContestRegistration::getTenantId, tenantId);
}
rejectedWrapper.eq(BizContestRegistration::getRegistrationState, "rejected");
long rejected = count(rejectedWrapper);

View File

@ -85,6 +85,7 @@ public class PublicActivityService {
result.put("registerState", contest.getRegisterState());
result.put("submitStartTime", contest.getSubmitStartTime());
result.put("submitEndTime", contest.getSubmitEndTime());
result.put("submitRule", contest.getSubmitRule());
result.put("reviewStartTime", contest.getReviewStartTime());
result.put("reviewEndTime", contest.getReviewEndTime());
result.put("workType", contest.getWorkType());
@ -116,10 +117,11 @@ public class PublicActivityService {
result.put("registrationState", reg.getRegistrationState());
result.put("registrationTime", reg.getRegistrationTime());
// 查询是否已提交作品
// 查询是否已提交作品只统计最新版本
Long workCount = contestWorkMapper.selectCount(
new LambdaQueryWrapper<BizContestWork>()
.eq(BizContestWork::getRegistrationId, reg.getId()));
.eq(BizContestWork::getRegistrationId, reg.getId())
.eq(BizContestWork::getIsLatest, true));
result.put("hasSubmittedWork", workCount > 0);
result.put("workCount", workCount);
@ -318,6 +320,30 @@ public class PublicActivityService {
throw new BusinessException(400, "未报名或报名未通过");
}
// 查询活动提交规则
BizContest contest = contestMapper.selectById(contestId);
String submitRule = contest != null ? contest.getSubmitRule() : "once";
// 查询已有作品
BizContestWork existingWork = contestWorkMapper.selectOne(
new LambdaQueryWrapper<BizContestWork>()
.eq(BizContestWork::getContestId, contestId)
.eq(BizContestWork::getRegistrationId, reg.getId())
.eq(BizContestWork::getIsLatest, true)
.orderByDesc(BizContestWork::getVersion)
.last("LIMIT 1"));
if (existingWork != null) {
if ("once".equals(submitRule)) {
throw new BusinessException(400, "该活动仅允许提交一次作品");
}
// resubmit 模式将旧作品标记为非最新
existingWork.setIsLatest(false);
contestWorkMapper.updateById(existingWork);
}
int nextVersion = (existingWork != null) ? existingWork.getVersion() + 1 : 1;
BizContestWork work = new BizContestWork();
work.setContestId(contestId);
work.setRegistrationId(reg.getId());
@ -328,7 +354,7 @@ public class PublicActivityService {
work.setSubmitterUserId(userId);
work.setStatus("submitted");
work.setSubmitTime(LocalDateTime.now());
work.setVersion(1);
work.setVersion(nextVersion);
work.setIsLatest(true);
if (dto.get("userWorkId") != null) {
work.setUserWorkId(Long.valueOf(dto.get("userWorkId").toString()));

View File

@ -214,6 +214,7 @@ export interface PublicActivity {
registerEndTime: string
submitStartTime: string
submitEndTime: string
submitRule: string
reviewStartTime: string
reviewEndTime: string
organizers: any

View File

@ -155,7 +155,6 @@
<template v-else>
<a-button type="link" size="small" @click="router.push(`/${tenantCode}/contests/${record.id}`)">查看</a-button>
<a-button v-permission="'contest:update'" type="link" size="small" @click="handleAddJudge(record.id)">评委</a-button>
<a-button v-permission="'contest:update'" type="link" size="small" @click="handleEdit(record.id)">编辑</a-button>
<a-button v-permission="'contest:publish'" type="link" size="small" style="color: #f59e0b" @click="handlePublishClick(record)">取消发布</a-button>
</template>
</template>

View File

@ -348,7 +348,6 @@ const handleSubmit = async () => {
)
}
message.success("添加评委成功")
emit("success")
} catch (error: any) {
message.error(error?.response?.data?.message || "添加评委失败")

View File

@ -60,12 +60,18 @@
<a-button v-if="!isLoggedIn" type="primary" size="large" block class="action-btn" @click="goLogin">
登录后查看作品
</a-button>
<a-button v-else-if="!hasRegistered && isRegisterOpen" type="primary" size="large" block class="action-btn" @click="showRegisterModal = true">
立即报名
</a-button>
<a-button v-else-if="!hasRegistered" size="large" block disabled class="action-btn-disabled">
需先报名才能提交作品
报名已截止
</a-button>
<a-button v-else-if="!hasSubmittedWork" type="primary" size="large" block class="action-btn" @click="openSubmitWork">
<upload-outlined /> 提交作品
</a-button>
<a-button v-else-if="canResubmit" type="primary" size="large" block class="action-btn" @click="openSubmitWork">
<upload-outlined /> 重新提交
</a-button>
<a-button v-else size="large" block class="action-btn-done">
<check-circle-outlined /> 作品已提交
</a-button>
@ -246,14 +252,27 @@ const formatDate = (d: string) => dayjs(d).format('YYYY-MM-DD')
const isLoggedIn = computed(() => !!localStorage.getItem('public_token'))
//
const isRegisterOpen = computed(() => {
if (!activity.value) return false
const now = dayjs()
return !now.isBefore(activity.value.registerStartTime) && now.isBefore(activity.value.registerEndTime)
})
// submitRule === 'resubmit'
const canResubmit = computed(() => {
return hasSubmittedWork.value && activity.value?.submitRule === 'resubmit'
})
//
const currentStage = computed(() => {
if (!activity.value) return 'pending'
const now = dayjs()
const a = activity.value
if (now.isBefore(a.registerStartTime)) return 'pending'
//
if (a.submitStartTime && !now.isBefore(a.submitStartTime) && a.submitEndTime && now.isBefore(a.submitEndTime)) return 'submit'
if (now.isBefore(a.registerEndTime)) return 'register'
if (a.submitStartTime && now.isBefore(a.submitEndTime)) return 'submit'
if (a.reviewStartTime && now.isBefore(a.reviewEndTime)) return 'review'
if (a.status === 'finished' || a.resultState === 'published') return 'finished'
return 'review'