feat: C端活动详情返回公告与附件,子女账号简化报名弹窗
Made-with: Cursor
This commit is contained in:
parent
3fa1ef95ac
commit
328533e805
@ -8,9 +8,13 @@ import com.competition.common.enums.*;
|
|||||||
import com.competition.common.exception.BusinessException;
|
import com.competition.common.exception.BusinessException;
|
||||||
import com.competition.common.result.PageResult;
|
import com.competition.common.result.PageResult;
|
||||||
import com.competition.modules.biz.contest.entity.BizContest;
|
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.BizContestRegistration;
|
||||||
import com.competition.modules.biz.contest.entity.BizContestWork;
|
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.ContestMapper;
|
||||||
|
import com.competition.modules.biz.contest.mapper.ContestNoticeMapper;
|
||||||
import com.competition.modules.biz.contest.mapper.ContestRegistrationMapper;
|
import com.competition.modules.biz.contest.mapper.ContestRegistrationMapper;
|
||||||
import com.competition.modules.biz.contest.mapper.ContestWorkMapper;
|
import com.competition.modules.biz.contest.mapper.ContestWorkMapper;
|
||||||
import com.competition.modules.biz.contest.service.IContestWorkService;
|
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.entity.UgcWorkPage;
|
||||||
import com.competition.modules.ugc.mapper.UgcWorkMapper;
|
import com.competition.modules.ugc.mapper.UgcWorkMapper;
|
||||||
import com.competition.modules.ugc.mapper.UgcWorkPageMapper;
|
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.entity.UserChild;
|
||||||
import com.competition.modules.user.mapper.UserChildMapper;
|
import com.competition.modules.user.mapper.UserChildMapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -42,6 +48,9 @@ public class PublicActivityService {
|
|||||||
private final UserChildMapper userChildMapper;
|
private final UserChildMapper userChildMapper;
|
||||||
private final UgcWorkMapper ugcWorkMapper;
|
private final UgcWorkMapper ugcWorkMapper;
|
||||||
private final UgcWorkPageMapper ugcWorkPageMapper;
|
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("workRequirement", contest.getWorkRequirement());
|
||||||
result.put("resultState", contest.getResultState());
|
result.put("resultState", contest.getResultState());
|
||||||
result.put("resultPublishTime", contest.getResultPublishTime());
|
result.put("resultPublishTime", contest.getResultPublishTime());
|
||||||
|
|
||||||
|
// 活动公告(仅已发布:publish_time 非空),租户范围与活动授权租户一致
|
||||||
|
result.put("notices", listPublishedNoticesForPublic(contest));
|
||||||
|
|
||||||
|
// 附件(与租户端赛事详情一致,便于 C 端下载)
|
||||||
|
LambdaQueryWrapper<BizContestAttachment> attWrapper = new LambdaQueryWrapper<>();
|
||||||
|
attWrapper.eq(BizContestAttachment::getContestId, id);
|
||||||
|
attWrapper.orderByAsc(BizContestAttachment::getCreateTime);
|
||||||
|
List<BizContestAttachment> attachmentEntities = contestAttachmentMapper.selectList(attWrapper);
|
||||||
|
List<Map<String, Object>> attachments = new ArrayList<>();
|
||||||
|
for (BizContestAttachment a : attachmentEntities) {
|
||||||
|
Map<String, Object> 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公众端可见公告:已发布且属于该活动授权租户(若有);无授权租户列表时仅按 contest_id 过滤。
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> listPublishedNoticesForPublic(BizContest contest) {
|
||||||
|
Long contestId = contest.getId();
|
||||||
|
LambdaQueryWrapper<BizContestNotice> nw = new LambdaQueryWrapper<>();
|
||||||
|
nw.eq(BizContestNotice::getContestId, contestId);
|
||||||
|
nw.isNotNull(BizContestNotice::getPublishTime);
|
||||||
|
List<Integer> tenantInts = contest.getContestTenants();
|
||||||
|
if (tenantInts != null && !tenantInts.isEmpty()) {
|
||||||
|
List<Long> tenantIds = tenantInts.stream().map(Integer::longValue).toList();
|
||||||
|
nw.in(BizContestNotice::getTenantId, tenantIds);
|
||||||
|
}
|
||||||
|
nw.orderByDesc(BizContestNotice::getPublishTime);
|
||||||
|
List<BizContestNotice> rows = contestNoticeMapper.selectList(nw);
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
for (BizContestNotice n : rows) {
|
||||||
|
Map<String, Object> 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, "活动未发布");
|
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();
|
BizContestRegistration reg = new BizContestRegistration();
|
||||||
reg.setContestId(contestId);
|
reg.setContestId(contestId);
|
||||||
reg.setUserId(userId);
|
reg.setUserId(userId);
|
||||||
|
|||||||
@ -233,6 +233,35 @@ export interface PublicActivity {
|
|||||||
workRequirement: string
|
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 = {
|
export const publicActivitiesApi = {
|
||||||
list: (params?: {
|
list: (params?: {
|
||||||
page?: number
|
page?: number
|
||||||
@ -242,7 +271,8 @@ export const publicActivitiesApi = {
|
|||||||
}): Promise<{ list: PublicActivity[]; total: number }> =>
|
}): Promise<{ list: PublicActivity[]; total: number }> =>
|
||||||
publicApi.get("/public/activities", { params }),
|
publicApi.get("/public/activities", { params }),
|
||||||
|
|
||||||
detail: (id: number) => publicApi.get(`/public/activities/${id}`),
|
detail: (id: number): Promise<PublicActivityDetail> =>
|
||||||
|
publicApi.get(`/public/activities/${id}`),
|
||||||
|
|
||||||
register: (
|
register: (
|
||||||
id: number,
|
id: number,
|
||||||
|
|||||||
@ -119,7 +119,7 @@
|
|||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<a-tabs v-model:activeKey="activeTab">
|
<a-tabs v-model:activeKey="activeTab">
|
||||||
<a-tab-pane key="info" tab="活动详情">
|
<a-tab-pane key="info" tab="活动详情">
|
||||||
<div class="rich-content" v-html="activity.description"></div>
|
<div class="rich-content" v-html="activityContentHtml"></div>
|
||||||
<!-- 附件 -->
|
<!-- 附件 -->
|
||||||
<div v-if="activity.attachments?.length" class="attachments">
|
<div v-if="activity.attachments?.length" class="attachments">
|
||||||
<h4>相关附件</h4>
|
<h4>相关附件</h4>
|
||||||
@ -144,7 +144,7 @@
|
|||||||
<div v-for="notice in activity.notices" :key="notice.id" class="notice-item">
|
<div v-for="notice in activity.notices" :key="notice.id" class="notice-item">
|
||||||
<h4>{{ notice.title }}</h4>
|
<h4>{{ notice.title }}</h4>
|
||||||
<div class="notice-content" v-html="notice.content"></div>
|
<div class="notice-content" v-html="notice.content"></div>
|
||||||
<span class="notice-time">{{ formatDate(notice.createTime) }}</span>
|
<span class="notice-time">{{ formatNoticeTime(notice) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
@ -159,23 +159,28 @@
|
|||||||
:width="420"
|
:width="420"
|
||||||
>
|
>
|
||||||
<div class="register-modal">
|
<div class="register-modal">
|
||||||
<p class="modal-desc">请选择参与者:</p>
|
<template v-if="isChildUser">
|
||||||
<a-radio-group v-model:value="participantForm.participantType" class="participant-options">
|
<p class="modal-desc">将使用当前账号报名,确认参加本活动吗?</p>
|
||||||
<div class="participant-option">
|
</template>
|
||||||
<a-radio value="self">我自己</a-radio>
|
<template v-else>
|
||||||
</div>
|
<p class="modal-desc">请选择参与者:</p>
|
||||||
<template v-if="children.length">
|
<a-radio-group v-model:value="participantForm.participantType" class="participant-options">
|
||||||
<div v-for="child in children" :key="child.id" class="participant-option">
|
<div class="participant-option">
|
||||||
<a-radio :value="'child_' + child.id">
|
<a-radio value="self">我自己</a-radio>
|
||||||
子女:{{ child.name }}
|
|
||||||
<span class="child-detail" v-if="child.grade">({{ child.grade }})</span>
|
|
||||||
</a-radio>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<template v-if="children.length">
|
||||||
</a-radio-group>
|
<div v-for="child in children" :key="child.id" class="participant-option">
|
||||||
<a-button type="link" @click="$router.push('/p/mine/children')" class="add-child-link">
|
<a-radio :value="'child_' + child.id">
|
||||||
+ 添加新的子女
|
子女:{{ child.name }}
|
||||||
</a-button>
|
<span class="child-detail" v-if="child.grade">({{ child.grade }})</span>
|
||||||
|
</a-radio>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-button type="link" @click="$router.push('/p/mine/children')" class="add-child-link">
|
||||||
|
+ 添加新的子女
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
block
|
block
|
||||||
@ -212,13 +217,19 @@ import {
|
|||||||
PictureOutlined, HourglassOutlined, TrophyOutlined,
|
PictureOutlined, HourglassOutlined, TrophyOutlined,
|
||||||
TeamOutlined, EnvironmentOutlined, CloseCircleOutlined,
|
TeamOutlined, EnvironmentOutlined, CloseCircleOutlined,
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
import { publicActivitiesApi, publicChildrenApi, type UserWork } from '@/api/public'
|
import {
|
||||||
|
publicActivitiesApi,
|
||||||
|
publicChildrenApi,
|
||||||
|
type PublicActivityDetail,
|
||||||
|
type PublicActivityNotice,
|
||||||
|
type UserWork,
|
||||||
|
} from '@/api/public'
|
||||||
import WorkSelector from './components/WorkSelector.vue'
|
import WorkSelector from './components/WorkSelector.vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const activity = ref<any>(null)
|
const activity = ref<PublicActivityDetail | null>(null)
|
||||||
const activeTab = ref('info')
|
const activeTab = ref('info')
|
||||||
const children = ref<any[]>([])
|
const children = ref<any[]>([])
|
||||||
const showRegisterModal = ref(false)
|
const showRegisterModal = ref(false)
|
||||||
@ -238,6 +249,27 @@ const participantForm = ref({
|
|||||||
|
|
||||||
const formatDate = (d: string) => dayjs(d).format('YYYY-MM-DD')
|
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'))
|
const isLoggedIn = computed(() => !!localStorage.getItem('public_token'))
|
||||||
|
|
||||||
// 报名是否仍在开放中
|
// 报名是否仍在开放中
|
||||||
@ -345,15 +377,20 @@ const handleRegister = async () => {
|
|||||||
router.push({ path: '/p/login', query: { redirect: route.fullPath } })
|
router.push({ path: '/p/login', query: { redirect: route.fullPath } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!activity.value) return
|
||||||
|
|
||||||
registering.value = true
|
registering.value = true
|
||||||
try {
|
try {
|
||||||
const val = participantForm.value.participantType
|
if (isChildUser.value) {
|
||||||
const isChild = val.startsWith('child_')
|
await publicActivitiesApi.register(activity.value.id, { participantType: 'self' })
|
||||||
await publicActivitiesApi.register(activity.value.id, {
|
} else {
|
||||||
participantType: isChild ? 'child' : 'self',
|
const val = participantForm.value.participantType
|
||||||
childId: isChild ? parseInt(val.replace('child_', '')) : undefined,
|
const isChild = val.startsWith('child_')
|
||||||
})
|
await publicActivitiesApi.register(activity.value.id, {
|
||||||
|
participantType: isChild ? 'child' : 'self',
|
||||||
|
childId: isChild ? parseInt(val.replace('child_', '')) : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
message.success('报名成功!')
|
message.success('报名成功!')
|
||||||
showRegisterModal.value = false
|
showRegisterModal.value = false
|
||||||
hasRegistered.value = true
|
hasRegistered.value = true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user