diff --git a/backend-java/src/main/java/com/competition/modules/pub/service/PublicActivityService.java b/backend-java/src/main/java/com/competition/modules/pub/service/PublicActivityService.java index 95daa4e..5d97384 100644 --- a/backend-java/src/main/java/com/competition/modules/pub/service/PublicActivityService.java +++ b/backend-java/src/main/java/com/competition/modules/pub/service/PublicActivityService.java @@ -8,9 +8,13 @@ import com.competition.common.enums.*; import com.competition.common.exception.BusinessException; import com.competition.common.result.PageResult; import com.competition.modules.biz.contest.entity.BizContest; +import com.competition.modules.biz.contest.entity.BizContestAttachment; +import com.competition.modules.biz.contest.entity.BizContestNotice; import com.competition.modules.biz.contest.entity.BizContestRegistration; import com.competition.modules.biz.contest.entity.BizContestWork; +import com.competition.modules.biz.contest.mapper.ContestAttachmentMapper; import com.competition.modules.biz.contest.mapper.ContestMapper; +import com.competition.modules.biz.contest.mapper.ContestNoticeMapper; import com.competition.modules.biz.contest.mapper.ContestRegistrationMapper; import com.competition.modules.biz.contest.mapper.ContestWorkMapper; import com.competition.modules.biz.contest.service.IContestWorkService; @@ -19,6 +23,8 @@ import com.competition.modules.ugc.entity.UgcWork; import com.competition.modules.ugc.entity.UgcWorkPage; import com.competition.modules.ugc.mapper.UgcWorkMapper; import com.competition.modules.ugc.mapper.UgcWorkPageMapper; +import com.competition.modules.sys.entity.SysUser; +import com.competition.modules.sys.mapper.SysUserMapper; import com.competition.modules.user.entity.UserChild; import com.competition.modules.user.mapper.UserChildMapper; import lombok.RequiredArgsConstructor; @@ -42,6 +48,9 @@ public class PublicActivityService { private final UserChildMapper userChildMapper; private final UgcWorkMapper ugcWorkMapper; private final UgcWorkPageMapper ugcWorkPageMapper; + private final ContestNoticeMapper contestNoticeMapper; + private final ContestAttachmentMapper contestAttachmentMapper; + private final SysUserMapper sysUserMapper; /** * 活动列表(公开) @@ -102,9 +111,60 @@ public class PublicActivityService { result.put("workRequirement", contest.getWorkRequirement()); result.put("resultState", contest.getResultState()); result.put("resultPublishTime", contest.getResultPublishTime()); + + // 活动公告(仅已发布:publish_time 非空),租户范围与活动授权租户一致 + result.put("notices", listPublishedNoticesForPublic(contest)); + + // 附件(与租户端赛事详情一致,便于 C 端下载) + LambdaQueryWrapper attWrapper = new LambdaQueryWrapper<>(); + attWrapper.eq(BizContestAttachment::getContestId, id); + attWrapper.orderByAsc(BizContestAttachment::getCreateTime); + List attachmentEntities = contestAttachmentMapper.selectList(attWrapper); + List> attachments = new ArrayList<>(); + for (BizContestAttachment a : attachmentEntities) { + Map am = new LinkedHashMap<>(); + am.put("id", a.getId()); + am.put("fileName", a.getFileName()); + am.put("fileUrl", a.getFileUrl()); + am.put("fileType", a.getFileType()); + am.put("format", a.getFormat()); + am.put("size", a.getSize()); + attachments.add(am); + } + result.put("attachments", attachments); + return result; } + /** + * 公众端可见公告:已发布且属于该活动授权租户(若有);无授权租户列表时仅按 contest_id 过滤。 + */ + private List> listPublishedNoticesForPublic(BizContest contest) { + Long contestId = contest.getId(); + LambdaQueryWrapper nw = new LambdaQueryWrapper<>(); + nw.eq(BizContestNotice::getContestId, contestId); + nw.isNotNull(BizContestNotice::getPublishTime); + List tenantInts = contest.getContestTenants(); + if (tenantInts != null && !tenantInts.isEmpty()) { + List tenantIds = tenantInts.stream().map(Integer::longValue).toList(); + nw.in(BizContestNotice::getTenantId, tenantIds); + } + nw.orderByDesc(BizContestNotice::getPublishTime); + List rows = contestNoticeMapper.selectList(nw); + List> list = new ArrayList<>(); + for (BizContestNotice n : rows) { + Map m = new LinkedHashMap<>(); + m.put("id", n.getId()); + m.put("title", n.getTitle()); + m.put("content", n.getContent()); + m.put("noticeType", n.getNoticeType()); + m.put("publishTime", n.getPublishTime()); + m.put("createTime", n.getCreateTime()); + list.add(m); + } + return list; + } + /** * 查询当前用户的报名信息(包含作品提交状态) */ @@ -294,6 +354,15 @@ public class PublicActivityService { throw new BusinessException(400, "活动未发布"); } + // 子女账号仅允许以本人报名,不可代报其他子女 + SysUser currentUser = sysUserMapper.selectById(userId); + if (currentUser != null && UserType.CHILD.getValue().equals(currentUser.getUserType())) { + String pt = dto.getParticipantType() != null ? dto.getParticipantType() : ParticipantType.SELF.getValue(); + if (ParticipantType.CHILD.getValue().equals(pt) || dto.getChildId() != null) { + throw new BusinessException(400, "子女账号只能以本人身份报名"); + } + } + BizContestRegistration reg = new BizContestRegistration(); reg.setContestId(contestId); reg.setUserId(userId); diff --git a/frontend/src/api/public.ts b/frontend/src/api/public.ts index 2326212..3ba0436 100644 --- a/frontend/src/api/public.ts +++ b/frontend/src/api/public.ts @@ -233,6 +233,35 @@ export interface PublicActivity { workRequirement: string } +/** 公众端活动详情(含公告、附件等扩展字段) */ +export interface PublicActivityNotice { + id: number + title: string + content: string + noticeType?: string + publishTime?: string + createTime?: string +} + +export interface PublicActivityAttachment { + id: number + fileName: string + fileUrl: string + fileType?: string + format?: string + size?: string +} + +export interface PublicActivityDetail extends PublicActivity { + /** 兼容旧字段;详情正文以后端 content 为准 */ + description?: string + notices?: PublicActivityNotice[] + attachments?: PublicActivityAttachment[] + ageMin?: number + ageMax?: number + targetCities?: string[] +} + export const publicActivitiesApi = { list: (params?: { page?: number @@ -242,7 +271,8 @@ export const publicActivitiesApi = { }): Promise<{ list: PublicActivity[]; total: number }> => publicApi.get("/public/activities", { params }), - detail: (id: number) => publicApi.get(`/public/activities/${id}`), + detail: (id: number): Promise => + publicApi.get(`/public/activities/${id}`), register: ( id: number, diff --git a/frontend/src/views/public/ActivityDetail.vue b/frontend/src/views/public/ActivityDetail.vue index f1c0905..62481b3 100644 --- a/frontend/src/views/public/ActivityDetail.vue +++ b/frontend/src/views/public/ActivityDetail.vue @@ -119,7 +119,7 @@
-
+

相关附件

@@ -144,7 +144,7 @@

{{ notice.title }}

- {{ formatDate(notice.createTime) }} + {{ formatNoticeTime(notice) }}
@@ -159,23 +159,28 @@ :width="420" >
- - -
- 我自己 -
- (null) +const activity = ref(null) const activeTab = ref('info') const children = ref([]) const showRegisterModal = ref(false) @@ -238,6 +249,27 @@ const participantForm = ref({ const formatDate = (d: string) => dayjs(d).format('YYYY-MM-DD') +const formatNoticeTime = (n: PublicActivityNotice) => + formatDate(n.publishTime || n.createTime || '') + +/** 活动详情富文本:后端字段为 content */ +const activityContentHtml = computed(() => { + const a = activity.value + if (!a) return '' + return a.content || a.description || '' +}) + +/** 子女账号:直接报名,不选参与者、不添加子女 */ +const isChildUser = computed(() => { + const raw = localStorage.getItem('public_user') + if (!raw) return false + try { + return JSON.parse(raw).userType === 'child' + } catch { + return false + } +}) + const isLoggedIn = computed(() => !!localStorage.getItem('public_token')) // 报名是否仍在开放中 @@ -345,15 +377,20 @@ const handleRegister = async () => { router.push({ path: '/p/login', query: { redirect: route.fullPath } }) return } + if (!activity.value) return registering.value = true try { - const val = participantForm.value.participantType - const isChild = val.startsWith('child_') - await publicActivitiesApi.register(activity.value.id, { - participantType: isChild ? 'child' : 'self', - childId: isChild ? parseInt(val.replace('child_', '')) : undefined, - }) + if (isChildUser.value) { + await publicActivitiesApi.register(activity.value.id, { participantType: 'self' }) + } else { + const val = participantForm.value.participantType + const isChild = val.startsWith('child_') + await publicActivitiesApi.register(activity.value.id, { + participantType: isChild ? 'child' : 'self', + childId: isChild ? parseInt(val.replace('child_', '')) : undefined, + }) + } message.success('报名成功!') showRegisterModal.value = false hasRegistered.value = true