6 Commits bf3b1d8375 ... 72ec77637a

Author SHA1 Message Date
  wenkai 72ec77637a Merge branch 'dev' into wk-dev-8-13 6 days ago
  wenkai 46fa28eac2 feat:增加字体 6 days ago
  wenkai 7f7a8a6cab feat:导出号码布zip、生成二维码、增加权限校验 6 days ago
  wenkai 9dcca47435 feat:导出号码对照表 1 week ago
  wenkai 2add5a29ce Merge branch 'refs/heads/dev' into wk-dev-8-13 1 week ago
  wenkai 9ff7312bcd feat:添加文章类型 1 week ago
34 changed files with 993 additions and 670 deletions
  1. 1 0
      .gitignore
  2. 26 4
      ruoyi-modules/ruoyi-game-event/pom.xml
  3. 0 117
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/AppGlobalTextController.java
  4. 20 18
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameEventController.java
  5. 1 4
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/MarkdownController.java
  6. 66 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/NumberController.java
  7. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/AppEventMd.java
  8. 0 57
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/AppGlobalText.java
  9. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/AppEventMdBo.java
  10. 0 52
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/AppGlobalTextBo.java
  11. 33 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GenerateBibBo.java
  12. 2 2
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AppEventMdVo.java
  13. 0 67
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AppGlobalTextVo.java
  14. 30 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AthleteCodeVo.java
  15. 44 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AthleteNumberTableVO.java
  16. 0 21
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/AppGlobalTextMapper.java
  17. 0 77
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IAppGlobalTextService.java
  18. 2 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameAthleteService.java
  19. 7 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameEventProjectService.java
  20. 24 4
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameEventService.java
  21. 9 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameTeamService.java
  22. 0 147
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/AppGlobalTextServiceImpl.java
  23. 14 6
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameAthleteServiceImpl.java
  24. 36 18
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventProjectServiceImpl.java
  25. 570 31
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java
  26. 35 13
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java
  27. 46 18
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/IEnrollServiceImpl.java
  28. BIN
      ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyh.ttc
  29. BIN
      ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyhbd.ttc
  30. BIN
      ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyhl.ttc
  31. BIN
      ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/simhei.ttf
  32. BIN
      ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/simsun.ttc
  33. 0 11
      ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/AppGlobalTextMapper.xml
  34. 25 1
      本周改动.md

+ 1 - 0
.gitignore

@@ -46,3 +46,4 @@ nbdist/
 !*/build/*.xml
 
 .flattened-pom.xml
+!*/src/main/resources/*.yml

+ 26 - 4
ruoyi-modules/ruoyi-game-event/pom.xml

@@ -120,11 +120,33 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-system</artifactId>
         </dependency>
+        <!-- iText -->
         <dependency>
-            <groupId>org.javassist</groupId>
-            <artifactId>javassist</artifactId>
-            <version>3.29.2-GA</version>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itextpdf</artifactId>
+            <version>5.5.13.2</version>
+        </dependency>
+        <!-- ZXing -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <!-- ZXing -->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.21</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.11.0</version>
         </dependency>
     </dependencies>
-
 </project>

+ 0 - 117
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/AppGlobalTextController.java

@@ -1,117 +0,0 @@
-package org.dromara.system.controller;
-
-import java.util.List;
-
-import lombok.RequiredArgsConstructor;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.constraints.*;
-import cn.dev33.satoken.annotation.SaCheckPermission;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.validation.annotation.Validated;
-import org.dromara.common.idempotent.annotation.RepeatSubmit;
-import org.dromara.common.log.annotation.Log;
-import org.dromara.common.web.core.BaseController;
-import org.dromara.common.mybatis.core.page.PageQuery;
-import org.dromara.common.core.domain.R;
-import org.dromara.common.core.validate.AddGroup;
-import org.dromara.common.core.validate.EditGroup;
-import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.excel.utils.ExcelUtil;
-import org.dromara.system.domain.vo.AppGlobalTextVo;
-import org.dromara.system.domain.bo.AppGlobalTextBo;
-import org.dromara.system.service.IAppGlobalTextService;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-
-/**
- * 移动端富文本
- *
- * @author zlt
- * @date 2025-08-18
- */
-@Validated
-@RequiredArgsConstructor
-@RestController
-@RequestMapping("/system/globalText")
-public class AppGlobalTextController extends BaseController {
-
-    private final IAppGlobalTextService appGlobalTextService;
-
-    /**
-     * 查询移动端富文本列表
-     */
-    @SaCheckPermission("system:globalText:list")
-    @GetMapping("/list")
-    public TableDataInfo<AppGlobalTextVo> list(AppGlobalTextBo bo, PageQuery pageQuery) {
-        return appGlobalTextService.queryPageList(bo, pageQuery);
-    }
-
-    /**
-     * 导出移动端富文本列表
-     */
-    @SaCheckPermission("system:globalText:export")
-    @Log(title = "移动端富文本", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(AppGlobalTextBo bo, HttpServletResponse response) {
-        List<AppGlobalTextVo> list = appGlobalTextService.queryList(bo);
-        ExcelUtil.exportExcel(list, "移动端富文本", AppGlobalTextVo.class, response);
-    }
-
-    /**
-     * 获取移动端富文本详细信息
-     *
-     * @param id 主键
-     */
-    @SaCheckPermission("system:globalText:query")
-    @GetMapping("/{id}")
-    public R<AppGlobalTextVo> getInfo(@NotNull(message = "主键不能为空")
-                                     @PathVariable Long id) {
-        return R.ok(appGlobalTextService.queryById(id));
-    }
-
-    /**
-     * 新增移动端富文本
-     */
-    @SaCheckPermission("system:globalText:add")
-    @Log(title = "移动端富文本", businessType = BusinessType.INSERT)
-    @RepeatSubmit()
-    @PostMapping()
-    public R<Void> add(@Validated(AddGroup.class) @RequestBody AppGlobalTextBo bo) {
-        return toAjax(appGlobalTextService.insertByBo(bo));
-    }
-
-    /**
-     * 修改移动端富文本
-     */
-    @SaCheckPermission("system:globalText:edit")
-    @Log(title = "移动端富文本", businessType = BusinessType.UPDATE)
-    @RepeatSubmit()
-    @PutMapping()
-    public R<Void> edit(@Validated(EditGroup.class) @RequestBody AppGlobalTextBo bo) {
-        return toAjax(appGlobalTextService.updateByBo(bo));
-    }
-
-    /**
-     * 删除移动端富文本
-     *
-     * @param ids 主键串
-     */
-    @SaCheckPermission("system:globalText:remove")
-    @Log(title = "移动端富文本", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public R<Void> remove(@NotEmpty(message = "主键不能为空")
-                          @PathVariable Long[] ids) {
-        return toAjax(appGlobalTextService.deleteWithValidByIds(List.of(ids), true));
-    }
-
-    /**
-     * 根据事件id和类型获取富文本
-     *
-     * @param eventId 事件id
-     * @param type    类型
-     */
-    @Log(title = "根据事件id和类型获取富文本", businessType = BusinessType.DELETE)
-    @GetMapping("/getTextByEventAndType")
-    public R<List<AppGlobalTextVo>> getTextByEventAndType(@RequestParam Long eventId, @RequestParam Long type) {
-        return R.ok(appGlobalTextService.getTextByEventAndType(eventId, type));
-    }
-}

+ 20 - 18
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameEventController.java

@@ -1,30 +1,31 @@
 package org.dromara.system.controller;
 
-import java.util.List;
-import java.util.Map;
-
-import lombok.RequiredArgsConstructor;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import org.dromara.common.core.exception.ServiceException;
-import org.dromara.common.redis.utils.RedisUtils;
-import org.dromara.system.domain.constant.GameEventConstant;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.validation.annotation.Validated;
-import org.dromara.common.idempotent.annotation.RepeatSubmit;
-import org.dromara.common.log.annotation.Log;
-import org.dromara.common.web.core.BaseController;
-import org.dromara.common.mybatis.core.page.PageQuery;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
-import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
-import org.dromara.system.domain.vo.GameEventVo;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.web.core.BaseController;
 import org.dromara.system.domain.bo.GameEventBo;
+import org.dromara.system.domain.constant.GameEventConstant;
+import org.dromara.system.domain.vo.GameEventVo;
 import org.dromara.system.service.IGameEventService;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
 
 /**
  * 赛事基本信息
@@ -151,4 +152,5 @@ public class GameEventController extends BaseController {
     public R<Long> count(@PathVariable Long type) {
         return R.ok(gameEventService.countGameEvent(type));
     }
+
 }

+ 1 - 4
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/MarkdownController.java

@@ -1,7 +1,6 @@
 package org.dromara.system.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
@@ -17,8 +16,6 @@ import org.dromara.system.service.IAppEventMdService;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.List;
-
 /**
  * 赛事富文本
  *
@@ -62,7 +59,7 @@ public class MarkdownController extends BaseController {
     /**
      * 编辑移动端富文本
      */
-    @SaCheckPermission("system:eventMd:edit")
+    @SaCheckPermission("system:gameEvent:writeArticle")
     @Log(title = "移动端富文本", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()

+ 66 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/NumberController.java

@@ -0,0 +1,66 @@
+package org.dromara.system.controller;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.system.domain.bo.GenerateBibBo;
+import org.dromara.system.domain.constant.GameEventConstant;
+import org.dromara.system.domain.vo.AthleteNumberTableVO;
+import org.dromara.system.service.IGameEventService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/number")
+public class NumberController {
+
+    private final IGameEventService gameEventService;
+
+    /**
+     *
+     * @return key:队伍名称 value:队伍运动员
+     */
+    @GetMapping("/generateTable")
+    public List<AthleteNumberTableVO> generateNumberTable() {
+        Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        Long eventId = Long.valueOf(cacheObject.toString());
+        return gameEventService.getNumberTable(eventId);
+    }
+
+
+    /**
+     * 导出号码对照表
+     */
+    @SaCheckPermission("system:gameEvent:numberExport")
+    @PostMapping("/export")
+    public void exportNumberTable(HttpServletRequest request, HttpServletResponse response) {
+        Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        Long eventId = Long.valueOf(cacheObject.toString());
+        gameEventService.exportNumberTable(request, response, eventId);
+    }
+
+
+    /**
+     * 生成号码布
+     *
+     */
+    @SaCheckPermission("system:gameEvent:numberBib")
+    @PostMapping("/generateBib")
+    public void generateNumberBib(HttpServletResponse response,
+                                  @RequestPart("bgImage") MultipartFile bgImage,
+                                  @RequestPart("logo") MultipartFile logo,
+                                  GenerateBibBo bibParam) {
+        gameEventService.generateNumberBib(response, bgImage, logo, bibParam);
+    }
+
+}

+ 1 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/AppEventMd.java

@@ -45,7 +45,7 @@ public class AppEventMd extends TenantEntity {
 
     /**
      * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-  6:赛事分组 7:运动员号码簿 8:项目场地
+  6:赛事分组 7:运动员号码簿 8:项目场地 9:交通指示
      */
     private Integer type;
 

+ 0 - 57
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/AppGlobalText.java

@@ -1,57 +0,0 @@
-package org.dromara.system.domain;
-
-import org.dromara.common.tenant.core.TenantEntity;
-import com.baomidou.mybatisplus.annotation.*;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-import java.io.Serial;
-
-/**
- * 移动端富文本对象 app_global_text
- *
- * @author zlt
- * @date 2025-08-18
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-@TableName("app_global_text")
-public class AppGlobalText extends TenantEntity {
-
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    /**
-     *
-     */
-    @TableId(value = "id")
-    private Long id;
-
-    /**
-     * 赛事id
-     */
-    private Long eventId;
-
-    /**
-     * 标题
-     */
-    private String title;
-
-    /**
-     * 内容
-     */
-    private String content;
-
-    /**
-     * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-     6:赛事分组 7:运动员号码簿 8:项目场地
-     */
-    private Long type;
-
-    /**
-     * 备注
-     */
-    private String remark;
-
-
-}

+ 1 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/AppEventMdBo.java

@@ -45,7 +45,7 @@ public class AppEventMdBo extends BaseEntity {
 
     /**
      * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-  6:赛事分组 7:运动员号码簿 8:项目场地
+  6:赛事分组 7:运动员号码簿 8:项目场地 9:交通指示
      */
     @NotNull(message = "类型不能为空")
     private Integer type;

+ 0 - 52
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/AppGlobalTextBo.java

@@ -1,52 +0,0 @@
-package org.dromara.system.domain.bo;
-
-import org.dromara.system.domain.AppGlobalText;
-import org.dromara.common.mybatis.core.domain.BaseEntity;
-import io.github.linpeilie.annotations.AutoMapper;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-/**
- * 移动端富文本业务对象 app_global_text
- *
- * @author zlt
- * @date 2025-08-18
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-@AutoMapper(target = AppGlobalText.class, reverseConvertGenerate = false)
-public class AppGlobalTextBo extends BaseEntity {
-
-    /**
-     *
-     */
-    private Long id;
-
-    /**
-     * 赛事id
-     */
-    private Long eventId;
-
-    /**
-     * 标题
-     */
-    private String title;
-
-    /**
-     * 内容
-     */
-    private String content;
-
-    /**
-     * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-  6:赛事分组 7:运动员号码簿 8:项目场地
-     */
-    private Long type;
-
-    /**
-     * 备注
-     */
-    private String remark;
-
-
-}

+ 33 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GenerateBibBo.java

@@ -0,0 +1,33 @@
+package org.dromara.system.domain.bo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+public class GenerateBibBo implements Serializable {
+
+    /**
+     * logo位置
+     */
+    private Double logoX;
+    private Double logoY;
+
+    /**
+     * 二维码位置
+     */
+    private Double qRCodeX;
+    private Double qRCodeY;
+
+    /**
+     * 号码字体样式
+     */
+    private String fontName;
+    private Integer fontSize;
+    private Integer fontColor;
+
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+}

+ 2 - 2
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AppEventMdVo.java

@@ -54,10 +54,10 @@ public class AppEventMdVo implements Serializable {
 
     /**
      * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-  6:赛事分组 7:运动员号码簿 8:项目场地
+  6:赛事分组 7:运动员号码簿 8:项目场地 9:交通指示
      */
     @ExcelProperty(value = "类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程 " +
-        "6:赛事分组 7:运动员号码簿 8:项目场地", converter = ExcelDictConvert.class)
+        "6:赛事分组 7:运动员号码簿 8:项目场地 9:交通指示", converter = ExcelDictConvert.class)
     @ExcelDictFormat(dictType = "markdown_location")
     private Integer type;
 

+ 0 - 67
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AppGlobalTextVo.java

@@ -1,67 +0,0 @@
-package org.dromara.system.domain.vo;
-
-import org.dromara.common.excel.annotation.ExcelDictFormat;
-import org.dromara.system.domain.AppGlobalText;
-import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
-import cn.idev.excel.annotation.ExcelProperty;
-import io.github.linpeilie.annotations.AutoMapper;
-import lombok.Data;
-
-import java.io.Serial;
-import java.io.Serializable;
-
-
-/**
- * 移动端富文本视图对象 app_global_text
- *
- * @author zlt
- * @date 2025-08-18
- */
-@Data
-@ExcelIgnoreUnannotated
-@AutoMapper(target = AppGlobalText.class)
-public class AppGlobalTextVo implements Serializable {
-
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 主键
-     */
-    @ExcelProperty(value = "主键")
-    private Long id;
-
-    /**
-     * 赛事id
-     */
-    @ExcelProperty(value = "赛事id")
-    private Long eventId;
-
-    /**
-     * 标题
-     */
-    @ExcelProperty(value = "标题")
-    private String title;
-
-    /**
-     * 内容
-     */
-    @ExcelProperty(value = "内容")
-    private String content;
-
-    /**
-     * 类型 1:竞赛流程 2:竞赛项目 3:活动议程 4:项目介绍 5:竞赛流程
-  6:赛事分组 7:运动员号码簿 8:项目场地
-     */
-    @ExcelProperty(value = "活动类型")
-    @ExcelDictFormat(dictType = "markdown_location")
-    private Long type;
-
-    /**
-     * 备注
-     */
-    @ExcelProperty(value = "备注")
-    private String remark;
-
-
-}

+ 30 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AthleteCodeVo.java

@@ -0,0 +1,30 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+public class AthleteCodeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    private Long id;
+
+    /**
+     * 运动员编号
+     */
+    private String code;
+    /**
+     * 姓名
+     */
+    @ExcelProperty(value = "姓名")
+    private String name;
+
+}

+ 44 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/AthleteNumberTableVO.java

@@ -0,0 +1,44 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.Advice;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 用户反馈建议视图对象 advice
+ *
+ * @author wenkai
+ * @date 2025-08-18
+ */
+@Data
+public class AthleteNumberTableVO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 队伍名称
+     */
+    private String teamName;
+
+    /**
+     * 人数
+     */
+    private Long memberCount;
+
+    /**
+     * 号码段
+     */
+    private String numberRange;
+
+    List<AthleteCodeVo> athleteCodeVos;
+
+}
+

+ 0 - 21
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/AppGlobalTextMapper.java

@@ -1,21 +0,0 @@
-package org.dromara.system.mapper;
-
-import org.dromara.system.domain.AppGlobalText;
-import org.dromara.system.domain.vo.AppGlobalTextVo;
-import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
-
-import java.util.List;
-
-/**
- * 移动端富文本Mapper接口
- *
- * @author zlt
- * @date 2025-08-18
- */
-public interface AppGlobalTextMapper extends BaseMapperPlus<AppGlobalText, AppGlobalTextVo> {
-
-    /**
-     * 根据赛事id和类型查询富文本数据
-     */
-    List<AppGlobalTextVo> getTextByEventAndType(Long eventId, Long type);
-}

+ 0 - 77
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IAppGlobalTextService.java

@@ -1,77 +0,0 @@
-package org.dromara.system.service;
-
-import org.dromara.system.domain.vo.AppGlobalTextVo;
-import org.dromara.system.domain.bo.AppGlobalTextBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * 移动端富文本Service接口
- *
- * @author zlt
- * @date 2025-08-18
- */
-public interface IAppGlobalTextService {
-
-    /**
-     * 查询移动端富文本
-     *
-     * @param id 主键
-     * @return 移动端富文本
-     */
-    AppGlobalTextVo queryById(Long id);
-
-    /**
-     * 分页查询移动端富文本列表
-     *
-     * @param bo        查询条件
-     * @param pageQuery 分页参数
-     * @return 移动端富文本分页列表
-     */
-    TableDataInfo<AppGlobalTextVo> queryPageList(AppGlobalTextBo bo, PageQuery pageQuery);
-
-    /**
-     * 查询符合条件的移动端富文本列表
-     *
-     * @param bo 查询条件
-     * @return 移动端富文本列表
-     */
-    List<AppGlobalTextVo> queryList(AppGlobalTextBo bo);
-
-    /**
-     * 新增移动端富文本
-     *
-     * @param bo 移动端富文本
-     * @return 是否新增成功
-     */
-    Boolean insertByBo(AppGlobalTextBo bo);
-
-    /**
-     * 修改移动端富文本
-     *
-     * @param bo 移动端富文本
-     * @return 是否修改成功
-     */
-    Boolean updateByBo(AppGlobalTextBo bo);
-
-    /**
-     * 校验并批量删除移动端富文本信息
-     *
-     * @param ids     待删除的主键集合
-     * @param isValid 是否进行有效性校验
-     * @return 是否删除成功
-     */
-    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
-
-    /**
-     * 获取移动端富文本列表
-     *
-     * @param eventId 赛事id
-     * @param type    类型
-     * @return 移动端富文本列表
-     */
-    List<AppGlobalTextVo> getTextByEventAndType(Long eventId, Long type);
-}

+ 2 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameAthleteService.java

@@ -87,6 +87,8 @@ public interface IGameAthleteService {
      */
     List<GameAthleteVo> listByIds(Collection<Long> athleteIds);
 
+    Long countByEventId(Long eventId);
+
     /**
      * 根据赛事ID、项目ID和搜索条件查询运动员列表
      * @param eventId 赛事ID

+ 7 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameEventProjectService.java

@@ -87,4 +87,11 @@ public interface IGameEventProjectService {
      * @return
      */
     List<GameEventProjectVo> queryListByEventId(Long eventId);
+
+    /**
+     * 查询当前赛事下项目名称和id的映射关系
+     * @param eventId
+     * @return
+     */
+    Map<String, Long> mapProjectAndProjectId(Long eventId);
 }

+ 24 - 4
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameEventService.java

@@ -1,10 +1,14 @@
 package org.dromara.system.service;
 
-import org.dromara.common.core.domain.R;
-import org.dromara.system.domain.vo.GameEventVo;
-import org.dromara.system.domain.bo.GameEventBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
 import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.bo.GameEventBo;
+import org.dromara.system.domain.bo.GenerateBibBo;
+import org.dromara.system.domain.vo.AthleteNumberTableVO;
+import org.dromara.system.domain.vo.GameEventVo;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.util.Collection;
 import java.util.List;
@@ -100,4 +104,20 @@ public interface IGameEventService {
      * @return
      */
     Long countGameEvent(Long type);
+
+    /**
+     * 获取默认赛事的号码对照表
+     * @return
+     */
+    List<AthleteNumberTableVO> getNumberTable(Long eventId);
+
+    /**
+     * 导出号码对照表
+     * @param request
+     * @param response
+     * @param eventId
+     */
+    void exportNumberTable(HttpServletRequest request, HttpServletResponse response, Long eventId);
+
+    void generateNumberBib(HttpServletResponse response, MultipartFile bgImage, MultipartFile logo, GenerateBibBo bibParam);
 }

+ 9 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameTeamService.java

@@ -106,4 +106,13 @@ public interface IGameTeamService {
      * @return 是否更新成功
      */
     Boolean updateTeamAthletes(Long teamId, List<Long> athleteIds);
+
+    Long countByEventId(Long eventId);
+
+    /**
+     * 根据队伍id查询队伍名称映射
+     * @param teamIds
+     * @return
+     */
+    Map<Long, String> queryTeamIdAndName(Set<Long> teamIds);
 }

+ 0 - 147
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/AppGlobalTextServiceImpl.java

@@ -1,147 +0,0 @@
-package org.dromara.system.service.impl;
-
-import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.dromara.system.domain.bo.AppGlobalTextBo;
-import org.dromara.system.domain.vo.AppGlobalTextVo;
-import org.dromara.system.domain.AppGlobalText;
-import org.dromara.system.mapper.AppGlobalTextMapper;
-import org.dromara.system.service.IAppGlobalTextService;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
-
-/**
- * 移动端富文本Service业务层处理
- *
- * @author zlt
- * @date 2025-08-18
- */
-@Slf4j
-@RequiredArgsConstructor
-@Service
-public class AppGlobalTextServiceImpl implements IAppGlobalTextService {
-
-    private final AppGlobalTextMapper baseMapper;
-
-    /**
-     * 查询移动端富文本
-     *
-     * @param id 主键
-     * @return 移动端富文本
-     */
-    @Override
-    public AppGlobalTextVo queryById(Long id){
-        return baseMapper.selectVoById(id);
-    }
-
-    /**
-     * 分页查询移动端富文本列表
-     *
-     * @param bo        查询条件
-     * @param pageQuery 分页参数
-     * @return 移动端富文本分页列表
-     */
-    @Override
-    public TableDataInfo<AppGlobalTextVo> queryPageList(AppGlobalTextBo bo, PageQuery pageQuery) {
-        LambdaQueryWrapper<AppGlobalText> lqw = buildQueryWrapper(bo);
-        Page<AppGlobalTextVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
-        return TableDataInfo.build(result);
-    }
-
-    /**
-     * 查询符合条件的移动端富文本列表
-     *
-     * @param bo 查询条件
-     * @return 移动端富文本列表
-     */
-    @Override
-    public List<AppGlobalTextVo> queryList(AppGlobalTextBo bo) {
-        LambdaQueryWrapper<AppGlobalText> lqw = buildQueryWrapper(bo);
-        return baseMapper.selectVoList(lqw);
-    }
-
-    private LambdaQueryWrapper<AppGlobalText> buildQueryWrapper(AppGlobalTextBo bo) {
-        Map<String, Object> params = bo.getParams();
-        LambdaQueryWrapper<AppGlobalText> lqw = Wrappers.lambdaQuery();
-        lqw.orderByAsc(AppGlobalText::getId);
-        lqw.eq(bo.getEventId() != null, AppGlobalText::getEventId, bo.getEventId());
-        lqw.eq(StringUtils.isNotBlank(bo.getTitle()), AppGlobalText::getTitle, bo.getTitle());
-        lqw.eq(StringUtils.isNotBlank(bo.getContent()), AppGlobalText::getContent, bo.getContent());
-        lqw.eq(bo.getType() != null, AppGlobalText::getType, bo.getType());
-        return lqw;
-    }
-
-    /**
-     * 新增移动端富文本
-     *
-     * @param bo 移动端富文本
-     * @return 是否新增成功
-     */
-    @Override
-    public Boolean insertByBo(AppGlobalTextBo bo) {
-        AppGlobalText add = MapstructUtils.convert(bo, AppGlobalText.class);
-        validEntityBeforeSave(add);
-        boolean flag = baseMapper.insert(add) > 0;
-        if (flag) {
-            bo.setId(add.getId());
-        }
-        return flag;
-    }
-
-    /**
-     * 修改移动端富文本
-     *
-     * @param bo 移动端富文本
-     * @return 是否修改成功
-     */
-    @Override
-    public Boolean updateByBo(AppGlobalTextBo bo) {
-        AppGlobalText update = MapstructUtils.convert(bo, AppGlobalText.class);
-        validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 0;
-    }
-
-    /**
-     * 保存前的数据校验
-     */
-    private void validEntityBeforeSave(AppGlobalText entity){
-        //TODO 做一些数据校验,如唯一约束
-    }
-
-    /**
-     * 校验并批量删除移动端富文本信息
-     *
-     * @param ids     待删除的主键集合
-     * @param isValid 是否进行有效性校验
-     * @return 是否删除成功
-     */
-    @Override
-    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if(isValid){
-            //TODO 做一些业务上的校验,判断是否需要校验
-        }
-        return baseMapper.deleteByIds(ids) > 0;
-    }
-
-    /**
-     * 根据赛事ID和类型获取富文本内容
-     *
-     * @param eventId 赛事ID
-     * @param type    类型
-     * @return 富文本内容
-     */
-    @Override
-    public List<AppGlobalTextVo> getTextByEventAndType(Long eventId, Long type) {
-        return baseMapper.getTextByEventAndType(eventId, type);
-    }
-}

+ 14 - 6
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameAthleteServiceImpl.java

@@ -176,7 +176,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
                     .ifPresent(projectValue -> {
 //                        String[] projectIds = projectValue.split(",");
                         List<String> projectIds = JSONUtil.toList(projectValue, String.class);
-                        if(CollUtil.isNotEmpty(projectIds)){
+                        if (CollUtil.isNotEmpty(projectIds)) {
                             List<GameEventProjectVo> projects =
                                 gameEventProjectService.listProjectsByEventIdAndProjectIndex(
                                     vo.getEventId(), projectIds);
@@ -216,7 +216,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
             }
         }
         LambdaQueryWrapper<GameAthlete> lqw = buildQueryWrapper(bo);
-        List<GameAthleteVo> athleteList =baseMapper.selectVoList(lqw);
+        List<GameAthleteVo> athleteList = baseMapper.selectVoList(lqw);
         athleteList.forEach(vo -> {
             Optional.ofNullable(vo.getProjectValue())
                 .filter(StringUtils::isNotBlank)
@@ -293,7 +293,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         }
         GameAthlete add = MapstructUtils.convert(bo, GameAthlete.class);
         validEntityBeforeSave(add);
-        if(CollectionUtils.isNotEmpty(bo.getProjectList())){
+        if (CollectionUtils.isNotEmpty(bo.getProjectList())) {
             add.setProjectValue(JSONUtil.toJsonStr(bo.getProjectList()));
         }
         boolean flag = baseMapper.insert(add) > 0;
@@ -320,7 +320,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
             }
         }
         GameAthlete update = MapstructUtils.convert(bo, GameAthlete.class);
-        if(CollectionUtils.isNotEmpty(bo.getProjectList())){
+        if (CollectionUtils.isNotEmpty(bo.getProjectList())) {
             update.setProjectValue(JSONUtil.toJsonStr(bo.getProjectList()));
         }
         validEntityBeforeSave(update);
@@ -423,7 +423,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
     @Override
     public List<GameAthleteVo> queryListByEventIdAndProjectId(Long eventId, Long projectId, String searchValue) {
         log.info("查询运动员列表: eventId={}, projectId={}, searchValue={}", eventId, projectId, searchValue);
-        
+
         LambdaQueryWrapper<GameAthlete> lqw = Wrappers.lambdaQuery();
         lqw.eq(GameAthlete::getEventId, eventId);
 
@@ -456,7 +456,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
             .filter(athlete -> {
                 if (CollectionUtils.isNotEmpty(athlete.getProjectList())) {
                     boolean containsProject = athlete.getProjectList().contains(projectId.toString());
-                    log.debug("运动员 {} 的项目列表: {}, 包含项目 {}: {}", 
+                    log.debug("运动员 {} 的项目列表: {}, 包含项目 {}: {}",
                             athlete.getName(), athlete.getProjectList(), projectId, containsProject);
                     return containsProject;
                 } else {
@@ -469,4 +469,12 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         log.info("过滤后参与项目 {} 的运动员数量: {}", projectId, filteredAthletes.size());
         return filteredAthletes;
     }
+
+    @Override
+    public Long countByEventId(Long eventId) {
+        return baseMapper.selectCount(
+            Wrappers.lambdaQuery(GameAthlete.class)
+                .eq(GameAthlete::getEventId, eventId)
+        );
+    }
 }

+ 36 - 18
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventProjectServiceImpl.java

@@ -84,22 +84,22 @@ public class GameEventProjectServiceImpl implements IGameEventProjectService {
         // 使用 forEach 直接修改原对象
         result.getRecords()
             .forEach(vo -> {
-            Optional.ofNullable(vo.getEventId())
-                .filter(ObjectUtil::isNotEmpty)
-                .ifPresent(eventId -> {
-                    GameEvent gameEvent = gameEventMapper.selectById(eventId);
-                    if (gameEvent != null) {
-                        vo.setEventName(gameEvent.getEventName());
-                    }
-                });
+                Optional.ofNullable(vo.getEventId())
+                    .filter(ObjectUtil::isNotEmpty)
+                    .ifPresent(eventId -> {
+                        GameEvent gameEvent = gameEventMapper.selectById(eventId);
+                        if (gameEvent != null) {
+                            vo.setEventName(gameEvent.getEventName());
+                        }
+                    });
 
-            Optional.ofNullable(vo.getRefereeGroup())
-                .filter(ObjectUtil::isNotEmpty)
-                .ifPresent(refereeIdStr -> {
-                    List<String> refereeList = JSONUtil.toList(refereeIdStr.toString(), String.class);
-                    vo.setRefereeGroups(refereeList);
-                });
-        });
+                Optional.ofNullable(vo.getRefereeGroup())
+                    .filter(ObjectUtil::isNotEmpty)
+                    .ifPresent(refereeIdStr -> {
+                        List<String> refereeList = JSONUtil.toList(refereeIdStr.toString(), String.class);
+                        vo.setRefereeGroups(refereeList);
+                    });
+            });
 
         return TableDataInfo.build(result);
     }
@@ -149,18 +149,18 @@ public class GameEventProjectServiceImpl implements IGameEventProjectService {
         lqw.eq(bo.getEventId() != null, GameEventProject::getEventId, bo.getEventId());
         // 通过名称模糊查询
         Optional.ofNullable(bo.getEventName())
-            .ifPresent(eventName->{
+            .ifPresent(eventName -> {
                 List<GameEvent> gameEvents = gameEventMapper.selectList(
                     Wrappers.lambdaQuery(GameEvent.class)
                         .like(GameEvent::getEventName, bo.getEventName())
                         .select(GameEvent::getEventId)
                 );
-                if(CollectionUtils.isNotEmpty(gameEvents)){
+                if (CollectionUtils.isNotEmpty(gameEvents)) {
                     List<Long> ids = gameEvents.stream()
                         .map(GameEvent::getEventId)
                         .collect(Collectors.toList());
                     lqw.in(GameEventProject::getEventId, ids);
-                }else{
+                } else {
                     lqw.apply("1=0");
                 }
             });
@@ -316,4 +316,22 @@ public class GameEventProjectServiceImpl implements IGameEventProjectService {
         );
         return projects;
     }
+
+    /**
+     * 查询当前赛事下项目名称和id的映射关系
+     *
+     * @param eventId
+     * @return
+     */
+    @Override
+    public Map<String, Long> mapProjectAndProjectId(Long eventId) {
+        List<GameEventProject> projects = baseMapper.selectList(
+            Wrappers.lambdaQuery(GameEventProject.class)
+                .eq(GameEventProject::getEventId, eventId)
+                .select(GameEventProject::getProjectId, GameEventProject::getProjectName)
+        );
+        return projects.isEmpty() ?
+            Map.of() :
+            projects.stream().collect(Collectors.toMap(GameEventProject::getProjectName, GameEventProject::getProjectId));
+    }
 }

+ 570 - 31
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java

@@ -1,30 +1,58 @@
 package org.dromara.system.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import jakarta.annotation.PostConstruct;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+import com.itextpdf.text.BaseColor;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.BaseFont;
+import com.itextpdf.text.pdf.PdfContentByte;
+import com.itextpdf.text.pdf.PdfWriter;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.json.utils.JsonUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
-import org.dromara.system.domain.constant.GameEventConstant;
-import org.springframework.stereotype.Service;
-import org.dromara.system.domain.bo.GameEventBo;
-import org.dromara.system.domain.vo.GameEventVo;
 import org.dromara.system.domain.GameEvent;
+import org.dromara.system.domain.bo.GameAthleteBo;
+import org.dromara.system.domain.bo.GameEventBo;
+import org.dromara.system.domain.bo.GameTeamBo;
+import org.dromara.system.domain.bo.GenerateBibBo;
+import org.dromara.system.domain.constant.GameEventConstant;
+import org.dromara.system.domain.vo.*;
 import org.dromara.system.mapper.GameEventMapper;
+import org.dromara.system.service.IGameAthleteService;
+import org.dromara.system.service.IGameEventProjectService;
 import org.dromara.system.service.IGameEventService;
+import org.dromara.system.service.IGameTeamService;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.multipart.MultipartFile;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 
 /**
@@ -34,11 +62,18 @@ import java.util.stream.Collectors;
  * @date 2025-07-30
  */
 @Slf4j
-@RequiredArgsConstructor
 @Service
 public class GameEventServiceImpl implements IGameEventService {
 
-    private final GameEventMapper baseMapper;
+    @Resource
+    private GameEventMapper baseMapper;
+    @Resource
+    private IGameTeamService gameTeamService;
+    @Lazy
+    @Resource
+    private IGameAthleteService gameAthleteService;
+    @Resource
+    private IGameEventProjectService gameEventProjectService;
 
     /**
      * 查询赛事基本信息
@@ -191,12 +226,8 @@ public class GameEventServiceImpl implements IGameEventService {
      */
     @Override
     public Map<String, Long> getEventIdNameMap() {
-        List<GameEvent> idNameList = this.baseMapper.selectList(
-            new LambdaQueryWrapper<GameEvent>()
-                .select(GameEvent::getEventId, GameEvent::getEventName)
-        );
-        Map<String, Long> idNameMap = idNameList.stream()
-            .collect(Collectors.toMap(GameEvent::getEventName, GameEvent::getEventId));
+        List<GameEvent> idNameList = this.baseMapper.selectList(new LambdaQueryWrapper<GameEvent>().select(GameEvent::getEventId, GameEvent::getEventName));
+        Map<String, Long> idNameMap = idNameList.stream().collect(Collectors.toMap(GameEvent::getEventName, GameEvent::getEventId));
         return idNameMap;
     }
 
@@ -253,8 +284,7 @@ public class GameEventServiceImpl implements IGameEventService {
     public int updateEventDefault(GameEventBo bo) {
         GameEvent event = MapstructUtils.convert(bo, GameEvent.class);
         // 先将所有赛事设置为非默认
-        int row = baseMapper.update(null, new LambdaUpdateWrapper<GameEvent>()
-            .set(GameEvent::getIsDefault, "1"));
+        int row = baseMapper.update(null, new LambdaUpdateWrapper<GameEvent>().set(GameEvent::getIsDefault, "1"));
         // 再将指定赛事设置为默认
         row += baseMapper.updateById(event);
         if (row > 0) {
@@ -274,12 +304,521 @@ public class GameEventServiceImpl implements IGameEventService {
      */
     @Override
     public Long countGameEvent(Long type) {
-        return this.baseMapper.selectCount(
-            Wrappers.lambdaQuery(GameEvent.class)
-                .apply(type == 0, "1=1")
-                .apply(type == 1, "start_time > now()")
-                .apply(type == 2, "start_time <= now() and end_time >= now()")
-                .apply(type == 3, "end_time < now()")
+        return this.baseMapper.selectCount(Wrappers.lambdaQuery(GameEvent.class).apply(type == 0, "1=1").apply(type == 1, "start_time > now()").apply(type == 2, "start_time <= now() and end_time >= now()").apply(type == 3, "end_time < now()"));
+    }
+
+    /**
+     * 获取默认赛事的号码对照表
+     *
+     * @param eventId
+     * @return
+     */
+    @Override
+    public List<AthleteNumberTableVO> getNumberTable(Long eventId) {
+        //1.查询当前赛事下的所有队伍
+        GameTeamBo bo = new GameTeamBo();
+        bo.setEventId(eventId);
+        List<GameTeamVo> gameTeam = gameTeamService.queryList(bo);
+        //2.查询当前赛事下所有运动员
+        GameAthleteBo gameAthleteBo = new GameAthleteBo();
+        gameAthleteBo.setEventId(eventId);
+        Map<Long, GameAthleteVo> athleteVoMap = gameAthleteService.queryList(gameAthleteBo).stream().collect(Collectors.toMap(GameAthleteVo::getAthleteId, v -> v));
+        //3.组装map
+        List<AthleteNumberTableVO> numberTable = new ArrayList<>();
+        for (GameTeamVo vo : gameTeam) {
+            List<AthleteCodeVo> athleteCodeVos = new ArrayList<>();
+            AtomicLong memberCount = new AtomicLong(0);
+            AthleteNumberTableVO athleteNumberTableVO = new AthleteNumberTableVO();
+            vo.getAthleteList().forEach(athleteId -> {
+                GameAthleteVo athleteVo = athleteVoMap.get(Long.valueOf(athleteId));
+                if (athleteVo != null) {
+                    AthleteCodeVo athleteCodeVo = new AthleteCodeVo();
+                    athleteCodeVo.setId(memberCount.incrementAndGet());
+                    athleteCodeVo.setCode(athleteVo.getAthleteCode());
+                    athleteCodeVo.setName(athleteVo.getName());
+                    athleteCodeVos.add(athleteCodeVo);
+                }
+            });
+            athleteNumberTableVO.setNumberRange(vo.getNumberRange());
+            athleteNumberTableVO.setTeamName(vo.getTeamName());
+            athleteNumberTableVO.setMemberCount(memberCount.get());
+            athleteNumberTableVO.setAthleteCodeVos(athleteCodeVos);
+            numberTable.add(athleteNumberTableVO);
+        }
+        return numberTable;
+    }
+
+
+    /**
+     * 使用poi生成号码对照表
+     *
+     * @param response
+     * @param eventId
+     */
+    /**
+     * 使用poi生成号码对照表
+     *
+     * @param request
+     * @param response
+     * @param eventId
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void exportNumberTable(HttpServletRequest request, HttpServletResponse response, Long eventId) {
+
+        // 获取当前赛事的队伍信息
+        List<AthleteNumberTableVO> numberTable = this.getNumberTable(eventId);
+        if (CollectionUtils.isEmpty(numberTable)) {
+            throw new ServiceException("该赛事无队伍数据");
+        }
+
+        // 1. 创建Excel表格
+        Workbook wb = new XSSFWorkbook();
+
+        // 1.2 创建公共样式:边框 + 居中 + 加粗
+        CellStyle style = wb.createCellStyle();
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setTopBorderColor(IndexedColors.BLACK.getIndex());
+        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+        style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
+        style.setRightBorderColor(IndexedColors.BLACK.getIndex());
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        // 字体加粗
+        org.apache.poi.ss.usermodel.Font font = wb.createFont();
+        font.setBold(true);
+        style.setFont(font);
+
+        // 创建带浅灰色背景的样式(用于序号行)
+        CellStyle backgroundFillStyle = wb.createCellStyle();
+        backgroundFillStyle.cloneStyleFrom(style);
+        backgroundFillStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        backgroundFillStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+        // 定义1-8列的样式(当前统一使用style,未来可差异化)
+        CellStyle[] columnStyles = new CellStyle[8];
+        for (int i = 0; i < 8; i++) {
+            columnStyles[i] = style;
+        }
+
+        try {
+            for (AthleteNumberTableVO teamNumberTable : numberTable) {
+                if (teamNumberTable.getMemberCount() == 0) {
+                    continue;
+                }
+                // 2. 遍历队伍信息,为每个队伍创建一个工作簿
+                Sheet sheet = wb.createSheet(teamNumberTable.getTeamName());
+                // 3. 按照规定的格式渲染
+                // 3.1 创建表头(号码对照表) 第一行 合并8个单元格 即0行7列
+                Row headerTitleRow = sheet.createRow(0);
+                Cell headerTitleCell = headerTitleRow.createCell(0);
+                headerTitleCell.setCellValue("号码对照表");
+                CellRangeAddress headerTitleRegion = new CellRangeAddress(0, 0, 0, 7);
+                sheet.addMergedRegion(headerTitleRegion);
+                // 应用样式到合并区域的所有单元格
+                for (int i = headerTitleRegion.getFirstColumn(); i <= headerTitleRegion.getLastColumn(); i++) {
+                    Cell cell = headerTitleRow.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
+                    cell.setCellStyle(backgroundFillStyle);
+                }
+
+                // 3.2 创建表头(队伍名称、人数、号码段) 第二行,按照指定格式:AB列合并、C列空白、DE列合并、F列空白、GH列合并
+                Row teamInfoRow = sheet.createRow(1);
+
+                // 设置队伍名称,并合并AB列(0-1列)
+                Cell teamNameInfoCell = teamInfoRow.createCell(0);
+                teamNameInfoCell.setCellValue(teamNumberTable.getTeamName());
+                CellRangeAddress teamNameInfoRegion = new CellRangeAddress(1, 1, 0, 1); // 合并AB列
+                sheet.addMergedRegion(teamNameInfoRegion);
+                teamNameInfoCell.setCellStyle(style); // 应用样式
+
+                // C列保持空白,但需创建单元格并应用样式
+                Cell cBlankCell = teamInfoRow.createCell(2);
+                cBlankCell.setCellStyle(style); // 应用样式
+
+                // 设置人数,并合并DE列(3-4列)
+                Cell teamMemberInfoCell = teamInfoRow.createCell(3);
+                teamMemberInfoCell.setCellValue(teamNumberTable.getMemberCount() + "人");
+                CellRangeAddress teamMemberInfoRegion = new CellRangeAddress(1, 1, 3, 4); // 合并DE列
+                sheet.addMergedRegion(teamMemberInfoRegion);
+                teamMemberInfoCell.setCellStyle(style); // 应用样式
+
+                // F列保持空白,但需创建单元格并应用样式
+                Cell fBlankCell = teamInfoRow.createCell(5);
+                fBlankCell.setCellStyle(style); // 应用样式
+
+                // 设置号码段,并合并GH列(6-7列)
+                Cell teamNumberInfoCell = teamInfoRow.createCell(6);
+                teamNumberInfoCell.setCellValue(teamNumberTable.getNumberRange());
+                CellRangeAddress teamNumberInfoRegion = new CellRangeAddress(1, 1, 6, 7); // 合并GH列
+                sheet.addMergedRegion(teamNumberInfoRegion);
+                Cell teamNumberInfoCell1 = teamInfoRow.createCell(7);
+                teamNumberInfoCell.setCellStyle(style); // 应用样式
+                teamNumberInfoCell1.setCellStyle(style); // 应用样式
+
+                // 确保所有列都应用了样式
+                for (int i = 0; i <= 7; i++) {
+                    Cell cell = teamInfoRow.getCell(i, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
+                    if (cell != null && cell.getCellStyle() == null) { // 如果当前单元格没有样式,则应用默认样式
+                        cell.setCellStyle(style);
+                    }
+                }
+
+                // 3.3 渲染运动员数据(序号、号码、姓名),每8人为一组,每组3行
+                AtomicLong cellCount = new AtomicLong(0);
+                List<AthleteCodeVo> athletes = teamNumberTable.getAthleteCodeVos();
+
+                for (AthleteCodeVo athlete : athletes) {
+                    int groupIndex = Math.toIntExact(cellCount.get() / 8); // 第几组
+                    int colIndex = Math.toIntExact(cellCount.get() % 8);   // 第几列 (0-7)
+
+                    int idRowIdx = groupIndex * 3 + 2;     // 序号行索引
+                    int codeRowIdx = idRowIdx + 1;         // 号码行索引
+                    int nameRowIdx = idRowIdx + 2;         // 姓名行索引
+
+                    // 获取或创建行
+                    Row idRow = sheet.getRow(idRowIdx);
+                    if (idRow == null) idRow = sheet.createRow(idRowIdx);
+
+                    Row codeRow = sheet.getRow(codeRowIdx);
+                    if (codeRow == null) codeRow = sheet.createRow(codeRowIdx);
+
+                    Row nameRow = sheet.getRow(nameRowIdx);
+                    if (nameRow == null) nameRow = sheet.createRow(nameRowIdx);
+
+                    // 创建并设置序号单元格(带灰色背景)
+                    Cell idCell = idRow.createCell(colIndex);
+                    idCell.setCellValue(athlete.getId());
+                    idCell.setCellStyle(backgroundFillStyle);
+
+                    // 创建并设置号码单元格
+                    Cell codeCell = codeRow.createCell(colIndex);
+                    codeCell.setCellValue(athlete.getCode());
+                    codeCell.setCellStyle(columnStyles[colIndex]);
+
+                    // 创建并设置姓名单元格
+                    Cell nameCell = nameRow.createCell(colIndex);
+                    nameCell.setCellValue(athlete.getName());
+                    nameCell.setCellStyle(columnStyles[colIndex]);
+
+                    cellCount.incrementAndGet();
+                }
+
+                // 补全最后一组的空列(确保1-8列都有样式)
+                if (!athletes.isEmpty()) {
+                    int lastGroupIndex = Math.toIntExact((cellCount.get() - 1) / 8);
+                    int lastFilledCol = Math.toIntExact((cellCount.get() - 1) % 8);
+
+                    if (lastFilledCol < 7) {
+                        int idRowIdx = lastGroupIndex * 3 + 2;
+                        int codeRowIdx = idRowIdx + 1;
+                        int nameRowIdx = idRowIdx + 2;
+
+                        Row idRow = sheet.getRow(idRowIdx);
+                        Row codeRow = sheet.getRow(codeRowIdx);
+                        Row nameRow = sheet.getRow(nameRowIdx);
+
+                        for (int col = lastFilledCol + 1; col < 8; col++) {
+                            // 补序号(空但有背景)
+                            Cell idCell = idRow.createCell(col);
+                            idCell.setCellStyle(backgroundFillStyle);
+
+                            // 补号码(空)
+                            Cell codeCell = codeRow.createCell(col);
+                            codeCell.setCellStyle(columnStyles[col]);
+
+                            // 补姓名(空)
+                            Cell nameCell = nameRow.createCell(col);
+                            nameCell.setCellStyle(columnStyles[col]);
+                        }
+                    }
+                }
+
+                // 自动调整列宽
+                for (int i = 0; i < 8; i++) {
+                    sheet.setColumnWidth(i, 14 * 256);
+                }
+            }
+
+            // 4. 渲染完所有队伍信息后返回Excel文件
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("UTF-8");
+            String fileName = "号码对照表.xlsx";
+            response.setHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("utf-8"), "ISO8859-1"));
+            String origin = request.getHeader("Origin");
+            if (origin != null) {
+                response.addHeader("Access-Control-Allow-Origin", origin);
+            }
+
+            // 写入输出流
+            wb.write(response.getOutputStream());
+
+        } catch (IOException e) {
+            log.error("导出号码对照表异常:", e);
+            throw new ServiceException("导出失败:" + e.getMessage());
+        } finally {
+            try {
+                wb.close();
+            } catch (IOException e) {
+                log.error("关闭Workbook失败:", e);
+            }
+        }
+    }
+
+    @Override
+    public void generateNumberBib(HttpServletResponse response, MultipartFile bgImage, MultipartFile logo, GenerateBibBo bibParam) {
+        //1.查询当前赛事所有队员数据
+        GameAthleteBo gameAthleteBo = new GameAthleteBo();
+        Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        Long defaultEventId = Long.valueOf(cacheObject.toString());
+        gameAthleteBo.setEventId(defaultEventId);
+        List<GameAthleteVo> athleteVoList = gameAthleteService.queryList(gameAthleteBo);
+        //2.提前查询队员队伍名称缓存
+        Set<Long> teamIds = athleteVoList.stream()
+            .map(GameAthleteVo::getTeamId)
+            .collect(Collectors.toSet());
+        Map<Long, String> teamNameMap = gameTeamService.queryTeamIdAndName(teamIds);
+        //3.查询赛事所有项目缓存
+        Map<Long, String> projectMap = gameEventProjectService.queryListByEventId(defaultEventId)
+            .stream().collect(Collectors.toMap(GameEventProjectVo::getProjectId, GameEventProjectVo::getProjectName));
+        //4.根据参数生成号码布
+        GameEventVo eventVo = baseMapper.selectVoById(defaultEventId);
+        generateBib(response, bgImage, logo, eventVo.getEventName(), athleteVoList, teamNameMap, projectMap, bibParam);
+    }
+
+    /**
+     * 生成号码布并直接通过 HttpServletResponse 返回 ZIP 文件
+     *
+     * @param response        HttpServletResponse 对象(用于输出 ZIP)
+     * @param backgroundImage 背景图 (MultipartFile)
+     * @param logo            Logo 图片(可选)
+     * @param eventName       赛事名称
+     * @param athleteList     运动员列表
+     * @param teamNameMap     队伍id名称映射
+     * @param projectMap      项目id名称映射
+     * @param bibParam        布局参数(位置、字体等)
+     * @throws Exception
+     */
+    public void generateBib(HttpServletResponse response,
+                            MultipartFile backgroundImage,
+                            MultipartFile logo,
+                            String eventName,
+                            List<GameAthleteVo> athleteList,
+                            Map<Long, String> teamNameMap,
+                            Map<Long, String> projectMap,
+                            GenerateBibBo bibParam) {
+        try {
+            // 提取布局参数
+            Double logoX = bibParam.getLogoX();
+            Double logoY = bibParam.getLogoY();
+            Double qRCodeX = bibParam.getQRCodeX();
+            Double qRCodeY = bibParam.getQRCodeY();
+            String fontName = bibParam.getFontName();
+            Integer fontSize = bibParam.getFontSize();
+            Integer fontColor = bibParam.getFontColor();
+
+            // 设置默认值
+            if (fontSize == null) fontSize = 14;
+            BaseColor textColor = parseColor(fontColor);
+            BaseFont baseFont = getChineseFont(fontName);
+
+            // 读取背景图
+            Image bgImage = Image.getInstance(backgroundImage.getBytes());
+            float pageWidth = bgImage.getWidth();
+            float pageHeight = bgImage.getHeight();
+
+            // 提前读取 logo 字节数组
+            byte[] logoBytes = null;
+            if (logo != null && !logo.isEmpty()) {
+                logoBytes = logo.getBytes();
+            }
+
+            // 设置响应头(返回 ZIP 压缩包)
+            response.reset();
+            response.setContentType("application/zip");
+            response.setHeader("Content-Disposition", "attachment; filename=\"athlete_bibs.zip\"");
+
+            try (java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(response.getOutputStream())) {
+
+                for (GameAthleteVo athlete : athleteList) {
+                    // 每个运动员生成一个 PDF
+                    ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
+                    Document document = new Document(new Rectangle(pageWidth, pageHeight));
+                    PdfWriter writer = PdfWriter.getInstance(document, pdfStream);
+                    document.open();
+
+                    PdfContentByte cb = writer.getDirectContent();
+
+                    // 添加背景图
+                    bgImage.setAbsolutePosition(0, 0);
+                    document.add(bgImage);
+
+                    // 添加Logo - 更精确的位置调整
+                    if (logoBytes != null) {
+                        try {
+                            Image img = Image.getInstance(logoBytes);
+                            img.scaleToFit(80, 80);
+
+                            // 直接使用前端传递的坐标值,更精确的位置调整
+                            float logoPositionX = logoX != null ? logoX.floatValue() : 50f;
+                            float logoPositionY = logoY != null ? logoY.floatValue() : pageHeight - 130f;
+
+                            // 更精确的位置调整
+                            logoPositionX += 10; // 向右偏移10pt
+                            logoPositionY -= 50; // 向下偏移50pt(与编号重合)
+
+                            img.setAbsolutePosition(logoPositionX, logoPositionY);
+                            cb.addImage(img);
+                        } catch (Exception e) {
+                            // 忽略或记录日志
+                        }
+                    }
+
+                    // 计算文本宽度和高度
+                    float textWidth = baseFont.getWidthPoint(athlete.getAthleteCode(), fontSize);
+                    float textHeight = fontSize;
+
+                    // 确保文本垂直和水平都居中
+                    float textPositionX = (pageWidth - textWidth) / 2; // 水平居中
+                    float textPositionY = (pageHeight / 2) + (textHeight / 2); // 垂直居中
+
+                    addText(cb, baseFont, fontSize, textColor, textPositionX, textPositionY, athlete.getAthleteCode());
+
+                    // 添加赛事名称
+                    if (eventName != null && !eventName.trim().isEmpty()) {
+
+                        // 设置字体大小和颜色
+                        int eventNameFontSize = 64; // 确保与绘制文字时的字体大小一致
+                        cb.beginText();
+                        cb.setFontAndSize(baseFont, eventNameFontSize);
+                        cb.setColorFill(BaseColor.BLACK);
+
+                        // 计算文本宽度和X坐标以实现水平居中
+                        textWidth = baseFont.getWidthPoint(eventName, eventNameFontSize);
+                        float textX = (pageWidth - textWidth) / 2;
+
+                        // 设置文本垂直位置使其位于页面顶部,留出一定间距
+                        float textY = pageHeight - eventNameFontSize - 10; // 调整这个值以改变顶部间距
+
+                        cb.setTextMatrix(textX, textY);
+                        cb.showText(eventName);
+                        cb.endText();
+                    }
+                    StringBuilder joinProject = new StringBuilder();
+                    athlete.getProjectList().forEach(
+                        projectId -> joinProject.append(projectMap.get(Long.valueOf(projectId))).append(" ")
+                    );
+
+                    // 生成二维码 - 更精确的位置调整
+                    String qrData = String.format(
+                        """
+                            赛事名称:%s,
+                            运动员序号:%d,
+                            运动员编号:%s,
+                            参与项目:%s,
+                            队伍id:%d,
+                            队伍名称:%s
+                            运动员姓名:%s
+                            性别:%s,
+                            年龄:%d
+                            """,
+                        eventName, athlete.getAthleteId(), athlete.getAthleteCode(),
+                        joinProject.toString(),
+                        athlete.getTeamId(), teamNameMap.get(athlete.getTeamId()), athlete.getName(), athlete.getGender(), athlete.getAge()
+                    );
+
+                    byte[] qrBytes = generateQRCode(qrData, 150, 150);
+                    Image qrImage = Image.getInstance(qrBytes);
+
+                    // 直接使用前端传递的坐标值,更精确的位置调整
+                    float qrX = qRCodeX != null ? qRCodeX.floatValue() : pageWidth - 170f;
+                    float qrY = qRCodeY != null ? qRCodeY.floatValue() : 50f;
+
+                    // 更精确的位置调整
+                    qrX += 15; // 向右偏移15pt
+                    qrY -= 55; // 向下偏移55pt(与编号重合)
+
+                    qrImage.setAbsolutePosition(qrX, qrY);
+                    cb.addImage(qrImage);
+
+                    document.close();
+
+                    // 写入 ZIP:文件名为 bib_号码_姓名.pdf
+                    String fileName = String.format("bib_%s_%s.pdf", athlete.getAthleteCode(), athlete.getName());
+                    zos.putNextEntry(new java.util.zip.ZipEntry(fileName));
+                    zos.write(pdfStream.toByteArray());
+                    zos.closeEntry();
+                }
+
+                // 强制刷新输出流
+                zos.flush();
+                response.flushBuffer();
+
+            } catch (Exception e) {
+                throw new RuntimeException("生成号码布 ZIP 失败", e);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+        }
+    }
+
+    // 工具方法:添加文本
+    private static void addText(PdfContentByte cb, BaseFont font, int size, BaseColor color, float x, float y, String text) {
+        cb.beginText();
+        cb.setFontAndSize(font, size);
+        cb.setColorFill(color);
+        cb.setTextMatrix(x, y);
+        cb.showText(text);
+        cb.endText();
+    }
+
+    // 工具方法:生成二维码
+    private static byte[] generateQRCode(String data, int width, int height) throws Exception {
+        Map<EncodeHintType, Object> hints = new HashMap<>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        BitMatrix matrix = new MultiFormatWriter().encode(data, BarcodeFormat.QR_CODE, width, height, hints);
+
+        // 创建BufferedImage并设置透明度
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                // 如果该位置是背景,则设置为完全透明;如果是数据点,则保持不透明
+                int color = matrix.get(x, y) ? 0xFF000000 : 0x00000000;
+                image.setRGB(x, y, color);
+            }
+        }
+
+        // 将BufferedImage写入ByteArrayOutputStream
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ImageIO.write(image, "png", out);
+        return out.toByteArray();
+    }
+
+    // 工具方法:解析颜色 (0xRRGGBB)
+    private static BaseColor parseColor(Integer colorInt) {
+        if (colorInt == null) return BaseColor.BLACK;
+        return new BaseColor(
+            (colorInt >> 16) & 0xFF,
+            (colorInt >> 8) & 0xFF,
+            colorInt & 0xFF
         );
     }
+
+    // 工具方法:获取中文字体(推荐将字体文件打包进 resources)
+    private static BaseFont getChineseFont(String fontName) throws Exception {
+        // 方式1:使用系统字体(Windows)
+        // return BaseFont.createFont("C:/Windows/Fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+        switch (fontName) {
+            case "simhei":
+                return BaseFont.createFont("classpath:fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+            case "simsun":
+                return BaseFont.createFont("classpath:fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+            default:
+                return BaseFont.createFont("classpath:fonts/msyhbd.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
+        }
+    }
 }

+ 35 - 13
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java

@@ -2,31 +2,29 @@ package org.dromara.system.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
-import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
-import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.system.domain.GameAthlete;
 import org.dromara.system.domain.GameEvent;
+import org.dromara.system.domain.GameTeam;
+import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.system.domain.constant.GameEventConstant;
-import org.dromara.system.domain.vo.GameEventProjectVo;
+import org.dromara.system.domain.vo.GameTeamVo;
 import org.dromara.system.mapper.GameAthleteMapper;
 import org.dromara.system.mapper.GameEventMapper;
-import org.springframework.stereotype.Service;
-import org.dromara.system.domain.bo.GameTeamBo;
-import org.dromara.system.domain.vo.GameTeamVo;
-import org.dromara.system.domain.GameTeam;
 import org.dromara.system.mapper.GameTeamMapper;
 import org.dromara.system.service.IGameTeamService;
+import org.springframework.stereotype.Service;
 
-import javax.swing.text.html.Option;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -151,7 +149,6 @@ public class GameTeamServiceImpl implements IGameTeamService {
             }
         }
         LambdaQueryWrapper<GameTeam> lqw = buildQueryWrapper(bo);
-        lqw.like(GameTeam::getTeamName, bo.getTeamName());
         List<GameTeamVo> list = baseMapper.selectVoList(lqw);
         list.forEach(vo -> {
             if (vo.getAthleteValue() != null) {
@@ -347,4 +344,29 @@ public class GameTeamServiceImpl implements IGameTeamService {
             .collect(Collectors.toMap(GameTeamVo::getTeamId, GameTeamVo::getTeamName));
         return map;
     }
+
+    @Override
+    public Long countByEventId(Long eventId) {
+        return baseMapper.selectCount(
+            Wrappers.lambdaQuery(GameTeam.class)
+                .eq(GameTeam::getEventId, eventId)
+        );
+    }
+
+    /**
+     * 根据队伍id查询队伍名称映射
+     *
+     * @param teamIds
+     * @return
+     */
+    @Override
+    public Map<Long, String> queryTeamIdAndName(Set<Long> teamIds) {
+        List<GameTeamVo> list = baseMapper.selectVoList(
+            Wrappers.lambdaQuery(GameTeam.class)
+                .in(GameTeam::getEventId, teamIds)
+        );
+        Map<Long, String> map = list.stream()
+            .collect(Collectors.toMap(GameTeamVo::getTeamId, GameTeamVo::getTeamName));
+        return map;
+    }
 }

+ 46 - 18
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/IEnrollServiceImpl.java

@@ -1,5 +1,8 @@
 package org.dromara.system.service.impl;
 
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import cn.idev.excel.annotation.ExcelProperty;
 import jakarta.servlet.http.HttpServletRequest;
@@ -11,8 +14,11 @@ import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.ObjectUtils;
 import org.dromara.system.controller.TestPoi;
 import org.dromara.system.domain.bo.GameAthleteBo;
+import org.dromara.system.domain.bo.GameEventProjectBo;
 import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.system.domain.vo.EnrollProjectVo;
 import org.dromara.system.service.IEnrollService;
@@ -51,6 +57,7 @@ public class IEnrollServiceImpl implements IEnrollService {
      * @param eventId
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void downloadTemplateForPoi(HttpServletRequest request, HttpServletResponse response, Long eventId) {
         //1.加载Excel模板文件
         String template = "template/enroll_template.xlsx";
@@ -176,6 +183,15 @@ public class IEnrollServiceImpl implements IEnrollService {
      */
     @Override
     public Boolean importDataForPoi(MultipartFile file, Long eventId) {
+        //查询赛事下是否已经有报名信息,如果有就禁止导入
+        Long count = gameAthleteService.countByEventId(eventId);
+        if (count > 0) {
+            throw new ServiceException("该赛事下已经有报名信息,禁止导入");
+        }
+        count = gameTeamService.countByEventId(eventId);
+        if (count > 0) {
+            throw new ServiceException("该赛事下已经有报名信息,禁止导入");
+        }
         //1.解析报名信息
         List<EnrollProjectVo> enrollList = parseData(file);
         //2.保存报名信息
@@ -375,11 +391,14 @@ public class IEnrollServiceImpl implements IEnrollService {
         // 1. 根据队伍分类成Map
         Map<String, List<EnrollProjectVo>> groupedByTeam = dataList.stream()
             .collect(Collectors.groupingBy(EnrollProjectVo::getTeamName));
-
+        // 查询当前赛事下的项目 名称和id映射关系
+        Map<String, Long> projectList = gameEventProjectService.mapProjectAndProjectId(eventId);
         // 1.2 根据队伍生成号码段
         Map<String, String> numberRanges = new HashMap<>();
         Map<String, AtomicInteger> currentNumbers = new HashMap<>(); // 记录每个队伍当前分配到的号码
         AtomicInteger teamIndex = new AtomicInteger(1);
+        Snowflake snowflake = IdUtil.createSnowflake(1, 1);
+
         for (Map.Entry<String, List<EnrollProjectVo>> entry : groupedByTeam.entrySet()) {
             String teamName = entry.getKey();
             String range = generateNumberRange(dataList.size(), teamIndex.getAndIncrement());
@@ -389,37 +408,23 @@ public class IEnrollServiceImpl implements IEnrollService {
             int startNum = Integer.parseInt(range.split("-")[0]);
             currentNumbers.put(teamName, new AtomicInteger(startNum));
         }
-
         // 2. 保存参赛队伍 & 队员
         for (Map.Entry<String, List<EnrollProjectVo>> entry : groupedByTeam.entrySet()) {
             String teamName = entry.getKey();
             List<EnrollProjectVo> athletes = entry.getValue();
-            //todo 判断是否存在该队伍 存在就更新 人数为原本+1 号码段无需重新分配
             GameTeamBo gameTeamBo = new GameTeamBo();
-            gameTeamBo.setEventId(eventId);
-            gameTeamBo.setTeamName(teamName);
-            // gameTeamBo.setTeamCode("");
-            gameTeamBo.setLeader(athletes.get(0).getLeader());
-            gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(athletes.stream().map(EnrollProjectVo::getName).collect(Collectors.toList())));
-            gameTeamBo.setAthleteNum(Long.valueOf(athletes.size()));
-            gameTeamBo.setNumberRange(numberRanges.get(teamName));
-            gameTeamBo.setStatus("0");
-            gameTeamService.insertByBo(gameTeamBo);
-
+            gameTeamBo.setTeamId(snowflake.nextId());
             Long teamId = gameTeamBo.getTeamId();
-
             // 获取该队伍的当前编号计数器
             AtomicInteger currentNumber = currentNumbers.get(teamName);
             int width = (dataList.size() > 100) ? 5 : 4; // 决定格式化宽度
-
+            List<Long> athletesId = new ArrayList<>();
             // 3. 保存参赛队员
             for (EnrollProjectVo enrollInfo : athletes) {
                 GameAthleteBo gameAthleteBo = new GameAthleteBo();
                 gameAthleteBo.setEventId(eventId);
                 gameAthleteBo.setTeamId(teamId);
                 gameAthleteBo.setTeamName(teamName);
-                //todo 如果存在队伍 即续分配
-
                 // 分配编号:从当前计数器获取并递增
                 int assignedNumber = currentNumber.getAndIncrement();
                 String formattedCode = String.format("%0" + width + "d", assignedNumber);
@@ -430,11 +435,34 @@ public class IEnrollServiceImpl implements IEnrollService {
                 gameAthleteBo.setAge(Long.valueOf(enrollInfo.getAge()));
                 gameAthleteBo.setPhone(enrollInfo.getPhone());
                 gameAthleteBo.setUnit(teamName);
-                gameAthleteBo.setProjectValue(JSONUtil.toJsonStr(enrollInfo.getProjectSelections()));
+                Map<String, Boolean> selectProjects = enrollInfo.getProjectSelections();
+                //查询出对应的赛事id
+                List<Long> selectionProjectIds = new ArrayList<>();
+                for (Map.Entry<String, Boolean> selectProject : selectProjects.entrySet()) {
+                    Long projectId = projectList.get(selectProject.getKey());
+                    if (ObjectUtils.isNotEmpty(projectId)) {
+                        selectionProjectIds.add(projectId);
+                    }
+                }
+                gameAthleteBo.setProjectValue(JSONUtil.toJsonStr(selectionProjectIds));
+
                 gameAthleteBo.setStatus("0");
 
                 gameAthleteService.insertByBo(gameAthleteBo);
+                athletesId.add(gameAthleteBo.getAthleteId());
             }
+
+            gameTeamBo.setEventId(eventId);
+            gameTeamBo.setTeamName(teamName);
+            // gameTeamBo.setTeamCode("");
+            gameTeamBo.setLeader(athletes.get(0).getLeader());
+            gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(athletesId));
+            gameTeamBo.setAthleteNum(Long.valueOf(athletes.size()));
+            gameTeamBo.setNumberRange(numberRanges.get(teamName));
+            gameTeamBo.setStatus("0");
+            gameTeamService.insertByBo(gameTeamBo);
+
+
         }
         return true;
     }

BIN
ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyh.ttc


BIN
ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyhbd.ttc


BIN
ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/msyhl.ttc


BIN
ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/simhei.ttf


BIN
ruoyi-modules/ruoyi-game-event/src/main/resources/fonts/simsun.ttc


+ 0 - 11
ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/AppGlobalTextMapper.xml

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="org.dromara.system.mapper.AppGlobalTextMapper">
-
-    <select id="getTextByEventAndType" resultType="AppGlobalText">
-        SELECT * FROM app_global_text
-        WHERE event_id = #{eventId} AND type = #{type}
-    </select>
-</mapper>

+ 25 - 1
本周改动.md

@@ -20,7 +20,31 @@
 <li>修改赛事列表操作列位置</li>
 <li>修改菜单栏图标显示</li>
 <li>还原赛事-备份、恢复</li>
-<li>活动预热与默认赛事关联</li>
 <li>补充数据测试功能</li>
 <li>修改各模块列表项映射格式,方便后期维护</li>
 <li>修复成绩模块显示逻辑bug</li>
+
+
+
+<h2>8.20</h2>
+
+<li>完善运动员模块与队伍的关联</li>
+<li>优化赛事界面布局和操作流程</li>
+<li>添加组别详细信息,如人数、组数、道数等</li>
+<li>实现分组自动计算和时间验证功能</li>
+
+
+<h2>8.21</h2>
+
+<li>增加报名表导入校验-仅允许无报名数据的赛事进行导入</li>
+<li>号码对照表生成</li>
+<li>号码布开发</li>
+<li>优化成绩编辑界面,适配不同项目类型</li>
+<li>重构个人和团体项目的排名和积分计算方法</li>
+
+<h2>8.22</h2>
+
+<li>生成二维码</li>
+<li>完成号码布开发导出zip</li>
+<li>增加权限校验</li>
+<li>修复部分bug</li>