fix:活动详情优化

This commit is contained in:
zhonghua 2026-04-03 15:28:15 +08:00
parent 4a70bc7d43
commit b3954ffcf3
4 changed files with 164 additions and 31 deletions

View File

@ -17,6 +17,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
@Tag(name = "赛事公告") @Tag(name = "赛事公告")
@ -27,6 +28,34 @@ public class ContestNoticeController {
private final IContestNoticeService noticeService; private final IContestNoticeService noticeService;
/**
* 解析日期时间字符串兼容 ISO 格式带毫秒和 Z 时区标记
*/
private LocalDateTime parseDateTime(String dateTime) {
if (!StringUtils.hasText(dateTime)) {
return null;
}
// 尝试 ISO 格式yyyy-MM-dd'T'HH:mm:ss.SSSZ yyyy-MM-dd'T'HH:mm:ss'Z'
try {
// 处理带 Z ISO 格式
if (dateTime.endsWith("Z")) {
return LocalDateTime.parse(dateTime.substring(0, dateTime.length() - 1));
}
// 处理带毫秒的格式
if (dateTime.contains(".") && dateTime.indexOf(".") + 4 == dateTime.length()) {
return LocalDateTime.parse(dateTime.substring(0, dateTime.indexOf(".")));
}
return LocalDateTime.parse(dateTime);
} catch (Exception e) {
// 尝试空格分隔格式
try {
return LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception ex) {
return null;
}
}
}
@PostMapping @PostMapping
@RequirePermission("notice:create") @RequirePermission("notice:create")
@Operation(summary = "创建公告") @Operation(summary = "创建公告")
@ -38,7 +67,7 @@ public class ContestNoticeController {
notice.setNoticeType(dto.getNoticeType()); notice.setNoticeType(dto.getNoticeType());
notice.setPriority(dto.getPriority()); notice.setPriority(dto.getPriority());
if (StringUtils.hasText(dto.getPublishTime())) { if (StringUtils.hasText(dto.getPublishTime())) {
notice.setPublishTime(LocalDateTime.parse(dto.getPublishTime())); notice.setPublishTime(parseDateTime(dto.getPublishTime()));
} }
// 设置当前租户 ID租户隔离 // 设置当前租户 ID租户隔离
notice.setTenantId(SecurityUtil.getCurrentTenantId()); notice.setTenantId(SecurityUtil.getCurrentTenantId());
@ -102,7 +131,7 @@ public class ContestNoticeController {
notice.setNoticeType(dto.getNoticeType()); notice.setNoticeType(dto.getNoticeType());
notice.setPriority(dto.getPriority()); notice.setPriority(dto.getPriority());
if (StringUtils.hasText(dto.getPublishTime())) { if (StringUtils.hasText(dto.getPublishTime())) {
notice.setPublishTime(LocalDateTime.parse(dto.getPublishTime())); notice.setPublishTime(parseDateTime(dto.getPublishTime()));
} }
noticeService.updateById(notice); noticeService.updateById(notice);
return Result.success(); return Result.success();

View File

@ -18,6 +18,8 @@ import com.competition.modules.biz.contest.mapper.ContestMapper;
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.IContestService; import com.competition.modules.biz.contest.service.IContestService;
import com.competition.modules.sys.entity.SysTenant;
import com.competition.modules.sys.mapper.SysTenantMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -37,6 +39,7 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
private final ContestAttachmentMapper contestAttachmentMapper; private final ContestAttachmentMapper contestAttachmentMapper;
private final ContestRegistrationMapper contestRegistrationMapper; private final ContestRegistrationMapper contestRegistrationMapper;
private final ContestWorkMapper contestWorkMapper; private final ContestWorkMapper contestWorkMapper;
private final SysTenantMapper sysTenantMapper;
// 支持两种日期格式ISO 格式 (T 分隔) 和空格分隔格式 // 支持两种日期格式ISO 格式 (T 分隔) 和空格分隔格式
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
@ -204,6 +207,24 @@ public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> i
List<BizContestAttachment> attachments = contestAttachmentMapper.selectList(attWrapper); List<BizContestAttachment> attachments = contestAttachmentMapper.selectList(attWrapper);
result.put("attachments", attachments); result.put("attachments", attachments);
// 查询授权租户的详细信息
List<Integer> tenantIds = contest.getContestTenants();
if (tenantIds != null && !tenantIds.isEmpty()) {
List<Map<String, Object>> tenantInfoList = new ArrayList<>();
for (Integer tenantId : tenantIds) {
SysTenant tenant = sysTenantMapper.selectById(tenantId.longValue());
if (tenant != null) {
Map<String, Object> tenantInfo = new LinkedHashMap<>();
tenantInfo.put("id", tenant.getId());
tenantInfo.put("name", tenant.getName());
tenantInfo.put("code", tenant.getCode());
tenantInfo.put("tenantType", tenant.getTenantType());
tenantInfoList.add(tenantInfo);
}
}
result.put("contestTenantInfos", tenantInfoList);
}
return result; return result;
} }

View File

@ -12,7 +12,14 @@ export interface Contest {
endTime: string; endTime: string;
address?: string; address?: string;
content?: string; content?: string;
visibility?: "public" | "designated" | "internal"; // 可见范围
contestTenants?: number[]; contestTenants?: number[];
contestTenantInfos?: Array<{
id: number;
name: string;
code: string;
tenantType?: string;
}>;
coverUrl?: string; coverUrl?: string;
posterUrl?: string; posterUrl?: string;
contactName?: string; contactName?: string;

View File

@ -250,6 +250,60 @@
<!-- 右侧组织信息 --> <!-- 右侧组织信息 -->
<div class="content-right"> <div class="content-right">
<div class="sidebar-card">
<div class="sidebar-header">
<EyeOutlined />
<span>可见范围</span>
</div>
<div class="sidebar-body">
<div class="sidebar-item">
<div class="item-label">
<GlobalOutlined />
可见范围
</div>
<div class="item-value">
{{ getVisibilityText(contest?.visibility) }}
</div>
</div>
<div
v-if="contest?.visibility === 'designated' && contest?.contestTenantInfos?.length"
class="sidebar-item"
>
<div class="item-label">
<TeamOutlined />
开放机构
</div>
<div class="item-value">
<div
v-for="tenant in contest.contestTenantInfos"
:key="tenant.id"
class="org-name"
>
{{ tenant.name }}{{ tenant.code }}
</div>
</div>
</div>
</div>
</div>
<div class="sidebar-card">
<div class="sidebar-header">
<InfoCircleOutlined />
<span>基本信息</span>
</div>
<div class="sidebar-body">
<div class="sidebar-item">
<div class="item-label">
<ClockCircleOutlined />
创建时间
</div>
<div class="item-value">
{{ formatDateTime(contest?.createTime) }}
</div>
</div>
</div>
</div>
<div class="sidebar-card"> <div class="sidebar-card">
<div class="sidebar-header"> <div class="sidebar-header">
<TeamOutlined /> <TeamOutlined />
@ -262,9 +316,8 @@
主办单位 主办单位
</div> </div>
<div class="item-value"> <div class="item-value">
<template <template v-if="contest.organizers">
v-if="contest.organizers && contest.organizers.length" <template v-if="Array.isArray(contest.organizers)">
>
<div <div
v-for="org in contest.organizers" v-for="org in contest.organizers"
:key="org" :key="org"
@ -273,6 +326,10 @@
{{ org }} {{ org }}
</div> </div>
</template> </template>
<div v-else-if="contest.organizers" class="org-name">
{{ contest.organizers }}
</div>
</template>
<span v-else class="empty-text">暂无</span> <span v-else class="empty-text">暂无</span>
</div> </div>
</div> </div>
@ -283,11 +340,8 @@
协办单位 协办单位
</div> </div>
<div class="item-value"> <div class="item-value">
<template <template v-if="contest.coOrganizers">
v-if=" <template v-if="Array.isArray(contest.coOrganizers)">
contest.coOrganizers && contest.coOrganizers.length
"
>
<div <div
v-for="org in contest.coOrganizers" v-for="org in contest.coOrganizers"
:key="org" :key="org"
@ -296,6 +350,10 @@
{{ org }} {{ org }}
</div> </div>
</template> </template>
<div v-else-if="contest.coOrganizers" class="org-name">
{{ contest.coOrganizers }}
</div>
</template>
<span v-else class="empty-text">暂无</span> <span v-else class="empty-text">暂无</span>
</div> </div>
</div> </div>
@ -306,9 +364,8 @@
赞助单位 赞助单位
</div> </div>
<div class="item-value"> <div class="item-value">
<template <template v-if="contest.sponsors">
v-if="contest.sponsors && contest.sponsors.length" <template v-if="Array.isArray(contest.sponsors)">
>
<div <div
v-for="sp in contest.sponsors" v-for="sp in contest.sponsors"
:key="sp" :key="sp"
@ -317,6 +374,10 @@
{{ sp }} {{ sp }}
</div> </div>
</template> </template>
<div v-else-if="contest.sponsors" class="org-name">
{{ contest.sponsors }}
</div>
</template>
<span v-else class="empty-text">暂无</span> <span v-else class="empty-text">暂无</span>
</div> </div>
</div> </div>
@ -382,6 +443,8 @@ import {
GiftOutlined, GiftOutlined,
PhoneOutlined, PhoneOutlined,
UserOutlined, UserOutlined,
GlobalOutlined,
InfoCircleOutlined,
} from "@ant-design/icons-vue" } from "@ant-design/icons-vue"
import dayjs from "dayjs" import dayjs from "dayjs"
import { useAuthStore } from "@/stores/auth" import { useAuthStore } from "@/stores/auth"
@ -513,6 +576,19 @@ const getNoticeTypeText = (type?: string) => {
} }
} }
const getVisibilityText = (visibility?: string) => {
switch (visibility) {
case "public":
return "公开(所有公众用户可见)"
case "designated":
return "指定机构"
case "internal":
return "仅内部"
default:
return "未知"
}
}
const getRankClass = (rank?: number) => { const getRankClass = (rank?: number) => {
if (rank === 1) return "rank-1" if (rank === 1) return "rank-1"
if (rank === 2) return "rank-2" if (rank === 2) return "rank-2"