8 次代码提交 2176feee30 ... cc2f563015

作者 SHA1 备注 提交日期
  zhou cc2f563015 feat(game): 实现计时类项目成绩格式转换功能 1 月之前
  zhou 16138933e9 feat(game): 支持运动员多分组展示 1 月之前
  zhou 5489a7f70b feat(game):优化分组运动员分配逻辑并修复性别校验问题 1 月之前
  zhou e3488a8461 feat(game): 增加比赛阶段字典数据支持 1 月之前
  zhou 3ec56fcfc2 refactor(game-event):优化成绩上传与队伍号码分配逻辑 1 月之前
  zhou 8576929757 fix(game-event):优化单元格选择判断逻辑并调整接口返回格式 1 月之前
  zhou 2f8d33f8dc feat(game-event): 添加澳亚特设备成绩上传功能 1 月之前
  zhou 7e38f1fb24 feat(game-event): 新增体质测试数据上传接口 1 月之前
共有 26 个文件被更改,包括 1250 次插入243 次删除
  1. 4 4
      pom.xml
  2. 32 11
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/ElectrometerController.java
  3. 14 7
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/PhysicalController.java
  4. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/GameTeam.java
  5. 59 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/app/AvtScoreItem.java
  6. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameAthleteBo.java
  7. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameTeamBo.java
  8. 1 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameTeamVo.java
  9. 39 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/AvtScoreUploadVo.java
  10. 4 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/DistanceScoreUploadVo.java
  11. 8 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/ElectrometerDataItemVo.java
  12. 4 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/ElectrometerScoreUploadVo.java
  13. 8 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/PhysicalTestVo.java
  14. 3 1
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameAthleteCompetitionGroupMapper.java
  15. 66 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameEventMapper.java
  16. 2 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameScoreMapper.java
  17. 7 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameTeamMapper.java
  18. 10 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/app/IElectrometerService.java
  19. 1 0
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameAthleteServiceImpl.java
  20. 141 25
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventGroupServiceImpl.java
  21. 4 2
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java
  22. 178 49
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameScoreServiceImpl.java
  23. 4 3
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java
  24. 190 71
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/IEnrollServiceImpl.java
  25. 434 64
      ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/app/ElectrometerServiceImpl.java
  26. 34 0
      ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/GameTeamMapper.xml

+ 4 - 4
pom.xml

@@ -80,10 +80,6 @@
                 <monitor.username>ruoyi</monitor.username>
                 <monitor.password>123456</monitor.password>
             </properties>
-            <activation>
-                <!-- 默认环境 -->
-                <activeByDefault>true</activeByDefault>
-            </activation>
 
         </profile>
         <profile>
@@ -105,6 +101,10 @@
                 <monitor.username>ruoyi</monitor.username>
                 <monitor.password>123456</monitor.password>
             </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
 
         </profile>
     </profiles>

+ 32 - 11
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/ElectrometerController.java

@@ -3,6 +3,7 @@ package org.dromara.system.controller.app;
 import cn.dev33.satoken.annotation.SaIgnore;
 import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
+import org.dromara.system.domain.vo.app.AvtScoreUploadVo;
 import org.dromara.system.domain.vo.app.DistanceScoreUploadVo;
 import org.dromara.system.domain.vo.app.ElectrometerDataItemVo;
 import org.dromara.system.domain.vo.app.ElectrometerScoreUploadVo;
@@ -53,17 +54,17 @@ public class ElectrometerController {
      * @param eventId 赛事ID (通过请求头 X-Dbname 传递)
      * @return R<String> 上传结果
      */
-    @PostMapping("/aytuploadscore/timer")
-    public R<String> uploadScore(
-        @RequestBody List<ElectrometerScoreUploadVo> scoreUpload,
-        @RequestHeader(value = "X-Dbname") Long eventId) {
-        try {
-            String result = electrometerService.uploadScore(eventId, scoreUpload);
-            return R.ok(result);
-        } catch (Exception e) {
-            return R.fail("计时类项目成绩上传失败:" + e.getMessage());
-        }
-    }
+//    @PostMapping("/aytuploadscore/timer")
+//    public R<String> uploadScore(
+//        @RequestBody List<ElectrometerScoreUploadVo> scoreUpload,
+//        @RequestHeader(value = "X-Dbname") Long eventId) {
+//        try {
+//            String result = electrometerService.uploadScore(eventId, scoreUpload);
+//            return R.ok(result);
+//        } catch (Exception e) {
+//            return R.fail("计时类项目成绩上传失败:" + e.getMessage());
+//        }
+//    }
 
     /**
      * 远度距离类项目成绩上传
@@ -83,4 +84,24 @@ public class ElectrometerController {
             return R.fail("远度距离类项目成绩上传失败:" + e.getMessage());
         }
     }
+
+    /**
+     * 径赛成绩上传(电计设备)- 新格式
+     * 对应图片中的接口格式:POST /api/electrometer/avtuploadscore
+     *
+     * @param scoreUpload 成绩上传数据,包含cc_dm、zc、cj字段
+     * @param eventId 赛事ID (通过请求头 X-Dbname 传递)
+     * @return R<String> 上传结果
+     */
+    @PostMapping("/avtuploadscore/timer")
+    public R<String> uploadAvtScore(
+        @RequestBody AvtScoreUploadVo scoreUpload,
+        @RequestHeader(value = "X-Dbname") String eventId) {
+        try {
+            String result = electrometerService.uploadAvtScore(eventId, scoreUpload);
+            return R.ok(result);
+        } catch (Exception e) {
+            return R.fail("径赛成绩上传失败:" + e.getMessage());
+        }
+    }
 }

+ 14 - 7
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/PhysicalController.java

@@ -29,6 +29,9 @@ public class PhysicalController {
     // 默认关联的赛事ID
     private static final Long DEFAULT_EVENT_ID = 1950743780869537804L;
 
+    /**
+     * 体质测试数据上传接口(json格式版)
+     */
     @PostMapping("/upload")
     public ResponseEntity<Map<String, Object>> uploadTestData(@RequestBody List<PhysicalTestVo> results) {
         try {
@@ -61,11 +64,14 @@ public class PhysicalController {
         }
     }
 
+    /**
+     * 1.13上传该仪器测试成绩
+     */
     @PostMapping("/SendResults")
-    public ResponseEntity<Map<String, Object>> handleSendResults(
-        @RequestParam String testName,
-        @RequestParam int recordCount,
-        @RequestParam String resultInfo) {
+    public String handleSendResults(
+        @RequestParam("TestName") String testName,
+        @RequestParam("RecordCount") int recordCount,
+        @RequestParam("ResultInfo") String resultInfo) {
         try {
             // 2. 调用解析方法,根据 TestName 不同采用不同解析策略
             PhysicalDeviceVo deviceVo = parseAndSaveResults(testName, recordCount, resultInfo);
@@ -73,14 +79,15 @@ public class PhysicalController {
             // 3. 返回成功
             if (res){
                 log.info("设备体质测试数据处理成功,共处理 {} 条数据", recordCount);
-                return ResponseEntity.ok(Map.of("success", 1, "msg", "数据上传成功"));
+                return "1,;";
+//                return ResponseEntity.ok(Map.of("success", 1, "msg", "数据上传成功"));
             }else {
                 log.error("设备体质测试数据处理失败");
-                return ResponseEntity.ok(Map.of("success", 0, "msg", "数据处理失败"));
+                return "0,数据处理失败;";
             }
         } catch (Exception e) {
             log.error("设备体质测试数据上传异常", e);
-            return ResponseEntity.ok(Map.of("success", 0, "msg", e.getMessage()));
+            return  "0,"+e.getMessage();
         }
     }
 

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

@@ -65,7 +65,7 @@ public class GameTeam extends TenantEntity {
     /**
      * 人数
      */
-    private Long athleteNum;
+    private Integer athleteNum;
 
     /**
      * 号码段

+ 59 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/app/AvtScoreItem.java

@@ -0,0 +1,59 @@
+package org.dromara.system.domain.app;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class AvtScoreItem  implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 场次代码(分组ID)
+     */
+    private Long ccdm;
+
+    /**
+     * 运动员编号
+     */
+    private String athleteCode;
+
+    /**
+     * 名次
+     */
+    private Integer rank;
+
+    /**
+     * 成绩(以分钟为单位)
+     */
+    private String score;
+
+    /**
+     * 风速
+     */
+    private String windSpeed;
+
+    /**
+     * 反应时
+     */
+    private String reactionTime;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 组次
+     */
+    private Long groupIndex;
+
+    /**
+     * 道次
+     */
+    private Long trackIndex;
+
+}

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

@@ -19,7 +19,7 @@ import java.util.List;
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
-@AutoMapper(target = GameAthlete.class, reverseConvertGenerate = false)
+@AutoMapper(target = GameAthlete.class)
 public class GameAthleteBo extends BaseEntity {
 
     /**

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

@@ -78,7 +78,7 @@ public class GameTeamBo extends BaseEntity {
      * 人数
      */
 //    @NotNull(message = "人数不能为空", groups = { AddGroup.class, EditGroup.class })
-    private Long athleteNum;
+    private Integer athleteNum;
 
     /**
      * 号码段

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

@@ -89,7 +89,7 @@ public class GameTeamVo implements Serializable {
      * 人数
      */
     @ExcelProperty(value = "人数")
-    private Long athleteNum;
+    private Integer athleteNum;
 
     /**
      * 号码段

+ 39 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/AvtScoreUploadVo.java

@@ -0,0 +1,39 @@
+package org.dromara.system.domain.vo.app;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 澳亚特设备成绩上传请求视图对象(新格式)
+ * 对应图片中的JSON格式:{cc_dm:"464",zc:"1",cj:"464,0302,1,12.313,,,,1,4|464,0219,2,21.313,,,,1,1|..."}
+ *
+ * @author zlt
+ * @date 2025-01-27
+ */
+@Data
+public class AvtScoreUploadVo implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 场次代码(分组ID)
+     */
+    @JsonProperty("cc_dm")
+    private String ccDm;
+
+    /**
+     * 组次
+     */
+    @JsonProperty("zc")
+    private String zc;
+
+    /**
+     * 成绩数据
+     * 格式:场次代码,编号,名次,成绩,风速,反应时,状态,组次,道次|场次代码,编号,名次,成绩,风速,反应时,状态,组次,道次......
+     */
+    @JsonProperty("cj")
+    private String cj;
+}

+ 4 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/DistanceScoreUploadVo.java

@@ -1,5 +1,6 @@
 package org.dromara.system.domain.vo.app;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -16,16 +17,19 @@ public class DistanceScoreUploadVo {
     /**
      * 场次代码(分组ID)
      */
+    @JsonProperty("cc_dm")
     private Long ccdm;
 
     /**
      * 组次
      */
+    @JsonProperty("zc")
     private Long groupIndex;
 
     /**
      * 成绩数据(格式:序号,后三轮序号,号码,姓名,成绩1,风速1,成绩2,风速2,成绩3,风速3,成绩4,风速4,成绩5,风速5,成绩6,风速6,名次|......)
      */
+    @JsonProperty("cj")
     private String cj;
 
     @Data

+ 8 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/ElectrometerDataItemVo.java

@@ -1,5 +1,6 @@
 package org.dromara.system.domain.vo.app;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 import java.io.Serializable;
 import java.util.Date;
@@ -17,6 +18,7 @@ public class ElectrometerDataItemVo implements Serializable {
     /**
      * 项目类型(计算规则), 对应 game_event_project.score_rule
      */
+    @JsonProperty("tjs_dm")
     private String tjsDm;
 
     /**
@@ -47,21 +49,25 @@ public class ElectrometerDataItemVo implements Serializable {
     /**
      * 组别名称, 甲组, 对应分组名称
      */
+    @JsonProperty("zb_mc")
     private String zbMc;
 
     /**
      * 项目名称, 对应 game_event_project.project_name
      */
+    @JsonProperty("xm_mc")
     private String xmMc;
 
     /**
-     * 赛次名称(预赛, 复赛, 半决赛, 决赛), 对应比赛轮次
+     * 赛次名称(预赛, 复赛, 半决赛, 决赛), 对应比赛阶段
      */
+    @JsonProperty("sc_mc")
     private String scMc;
 
     /**
      * 场次代码, 编排给的比赛分组唯一标识, 对应 game_athlete_competition_group.group_id
      */
+    @JsonProperty("cc_dm")
     private String ccDm;
 
     /**
@@ -82,5 +88,6 @@ public class ElectrometerDataItemVo implements Serializable {
     /**
      * 比赛单元, 对应比赛单元
      */
+    @JsonProperty("bs_dm")
     private String bsDm;
 }

+ 4 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/ElectrometerScoreUploadVo.java

@@ -1,6 +1,9 @@
 package org.dromara.system.domain.vo.app;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
+
+import java.io.Serial;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.List;
@@ -13,6 +16,7 @@ import java.util.List;
  */
 @Data
 public class ElectrometerScoreUploadVo implements Serializable {
+    @Serial
     private static final long serialVersionUID = 1L;
 
     /**

+ 8 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/PhysicalTestVo.java

@@ -1,24 +1,31 @@
 package org.dromara.system.domain.vo.app;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
-import java.util.Date;
 
 @Data
 public class PhysicalTestVo {
     //项目名称
+    @JsonProperty("testname")
     private String testName;
     //项目key
+    @JsonProperty("testkey")
     private String testKey;
     //对应学生身份证号
+    @JsonProperty("user_id")
     private String userId;
     //学生姓名
+    @JsonProperty("name")
     private String name;
     //测试时间
+    @JsonProperty("timestamp")
     private String testTime;
     //测试结果
+    @JsonProperty("result_data")
     private String resultData;
     //测试分数
+    @JsonProperty("result_score")
     private BigDecimal resultScore;
 }

+ 3 - 1
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameAthleteCompetitionGroupMapper.java

@@ -1,5 +1,6 @@
 package org.dromara.system.mapper;
 
+import org.apache.ibatis.annotations.Mapper;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.system.domain.GameAthleteCompetitionGroup;
 import org.dromara.system.domain.vo.GameAthleteCompetitionGroupVo;
@@ -10,6 +11,7 @@ import org.dromara.system.domain.vo.GameAthleteCompetitionGroupVo;
  * @author zlt
  * @date 2025-08-28
  */
+@Mapper
 public interface GameAthleteCompetitionGroupMapper extends BaseMapperPlus<GameAthleteCompetitionGroup, GameAthleteCompetitionGroupVo> {
 
-} 
+}

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

@@ -1,10 +1,18 @@
 package org.dromara.system.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
 import org.dromara.system.domain.GameEvent;
 import org.dromara.system.domain.vo.GameEventVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
+import java.util.List;
+
 /**
  * 赛事基本信息Mapper接口
  *
@@ -14,4 +22,62 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 @Mapper
 public interface GameEventMapper extends BaseMapperPlus<GameEvent, GameEventVo> {
 
+    /**
+     * 分页查询赛事列表,并进行数据权限控制
+     * 注意:如果赛事表没有部门字段,需要根据业务需求调整
+     * 例如:如果赛事通过创建人关联,可以使用 userName 来控制
+     * 如果赛事通过部门关联,可以使用 deptName 来控制
+     *
+     * @param page         分页参数
+     * @param queryWrapper 查询条件
+     * @return 分页的赛事信息
+     */
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
+    default Page<GameEventVo> selectPageEventList(Page<GameEvent> page, Wrapper<GameEvent> queryWrapper) {
+        return this.selectVoPage(page, queryWrapper);
+    }
+
+    /**
+     * 查询赛事列表,并进行数据权限控制
+     *
+     * @param queryWrapper 查询条件
+     * @return 赛事信息集合
+     */
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
+    default List<GameEventVo> selectEventList(Wrapper<GameEvent> queryWrapper) {
+        return this.selectVoList(queryWrapper);
+    }
+
+    /**
+     * 更新赛事数据,并进行数据权限控制
+     *
+     * @param event         要更新的赛事实体
+     * @param updateWrapper 更新条件封装器
+     * @return 更新操作影响的行数
+     */
+    @Override
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
+    int update(@Param(Constants.ENTITY) GameEvent event, @Param(Constants.WRAPPER) Wrapper<GameEvent> updateWrapper);
+
+    /**
+     * 根据ID更新赛事数据,并进行数据权限控制
+     *
+     * @param event 要更新的赛事实体
+     * @return 更新操作影响的行数
+     */
+    @Override
+    @DataPermission({
+        @DataColumn(key = "deptName", value = "create_dept"),
+        @DataColumn(key = "userName", value = "create_by")
+    })
+    int updateById(@Param(Constants.ENTITY) GameEvent event);
 }

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

@@ -1,5 +1,6 @@
 package org.dromara.system.mapper;
 
+import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.dromara.system.domain.GameScore;
@@ -16,6 +17,7 @@ import java.util.Map;
  * @author zlt
  * @date 2025-07-30
  */
+@Mapper
 public interface GameScoreMapper extends BaseMapperPlus<GameScore, GameScoreVo> {
 
     /**

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

@@ -48,4 +48,11 @@ public interface GameTeamMapper extends BaseMapperPlus<GameTeam, GameTeamVo> {
         "</foreach>" +")"+
         "</script>")
     List<GameTeamBo> findByAthleteIds(Collection<Long> athleteIds);
+
+    /**
+     * 查询队伍列表并包含分组名
+     * @param bo 查询条件
+     * @return 队伍列表
+     */
+    List<GameTeamVo> selectVoListWithGroupName(GameTeamBo bo);
 }

+ 10 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/app/IElectrometerService.java

@@ -1,5 +1,6 @@
 package org.dromara.system.service.app;
 
+import org.dromara.system.domain.vo.app.AvtScoreUploadVo;
 import org.dromara.system.domain.vo.app.DistanceScoreUploadVo;
 import org.dromara.system.domain.vo.app.ElectrometerDataItemVo;
 import org.dromara.system.domain.vo.app.ElectrometerScoreUploadVo;
@@ -40,4 +41,13 @@ public interface IElectrometerService {
      * @return 上传结果
      */
     String uploadDistanceScore(Long eventId, DistanceScoreUploadVo scoreUpload);
+
+    /**
+     * 上传径赛成绩(电计设备)- 新格式
+     *
+     * @param eventId 赛事ID
+     * @param scoreUpload 成绩上传数据,包含cc_dm、zc、cj字段
+     * @return 上传结果
+     */
+    String uploadAvtScore(String eventId, AvtScoreUploadVo scoreUpload);
 }

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

@@ -496,6 +496,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         //校验运动员编号是否存在重复值
         if (entity.getAthleteCode() != null){
             List<GameAthlete> list = baseMapper.selectList(new LambdaQueryWrapper<GameAthlete>()
+                .eq(GameAthlete::getEventId, entity.getEventId())
                 .eq(GameAthlete::getAthleteCode, entity.getAthleteCode())
                 .ne( entity.getAthleteId() != null, GameAthlete::getAthleteId, entity.getAthleteId())
             );

+ 141 - 25
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventGroupServiceImpl.java

@@ -1,6 +1,7 @@
 package org.dromara.system.service.impl;
 
 import cn.hutool.json.JSONUtil;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -11,23 +12,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.system.domain.bo.GameTeamBo;
-import org.dromara.system.domain.vo.GameEventProjectVo;
-import org.dromara.system.service.IGameEventProjectService;
-import org.dromara.system.service.IGameAthleteService;
-import org.dromara.system.service.IGameTeamService;
-import org.dromara.system.domain.vo.GameAthleteVo;
-import org.dromara.system.domain.vo.GameTeamVo;
+import org.dromara.system.domain.vo.*;
+import org.dromara.system.service.*;
 import org.springframework.stereotype.Service;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.InitializingBean;
 import org.dromara.system.domain.bo.GameEventGroupBo;
 import org.dromara.system.domain.constant.GameEventConstant;
-import org.dromara.system.domain.vo.GameEventGroupVo;
+import org.dromara.system.domain.constant.ProjectClassification;
 import org.dromara.system.domain.GameEventGroup;
 import org.dromara.system.mapper.GameEventGroupMapper;
-import org.dromara.system.service.IGameEventGroupService;
-import org.dromara.system.service.IGameAthleteCompetitionGroupService;
 import jakarta.annotation.PostConstruct;
 import org.dromara.system.domain.bo.GameAthleteBo;
 import org.dromara.common.core.utils.SpringUtils;
@@ -42,6 +37,7 @@ import java.util.stream.Collectors;
  * @date 2025-07-30
  */
 @Slf4j
+@RequiredArgsConstructor
 @Service
 public class GameEventGroupServiceImpl implements IGameEventGroupService {
 
@@ -57,6 +53,8 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
 
     private IGameAthleteCompetitionGroupService gameAthleteCompetitionGroupService;
 
+    private final ISysDictTypeService dictTypeService;
+
     @PostConstruct
     public void init() {
         // 延迟初始化依赖,避免循环依赖
@@ -244,6 +242,7 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
         if (isValid) {
             //TODO 做一些业务上的校验,判断是否需要校验
         }
+        ids.forEach(id -> gameAthleteCompetitionGroupService.deleteGroupResultByGroupId(id));
         return baseMapper.deleteByIds(ids) > 0;
     }
 
@@ -283,6 +282,8 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
         Map<String, Object> result = new HashMap<>();
 
         try {
+            //获取性别字典数据
+            List<SysDictDataVo> genderDict = dictTypeService.selectDictDataByType("sys_group_sex");
             // 获取分组信息
             GameEventGroupVo groupInfo = queryById(groupId);
             if (groupInfo == null) {
@@ -339,8 +340,11 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
                     }
 
                     // 检查性别是否匹配
-                    if (StringUtils.isNotBlank(groupInfo.getMemberGender()) &&
-                        !"0".equals(groupInfo.getMemberGender())) {
+                    Optional<String> mixGender = genderDict.stream()
+                        .filter(g -> g.getDictLabel().equals("混合"))
+                        .map(SysDictDataVo::getDictValue)
+                        .findFirst();
+                    if (StringUtils.isNotBlank(groupInfo.getMemberGender())) {
                         if (!groupInfo.getMemberGender().equals(
                             athlete.getGender() != null ? athlete.getGender().toString() : null)) {
                             return false;
@@ -370,7 +374,14 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
             // 按组别和道次分配运动员
             for (int groupIndex = 1; groupIndex <= groupInfo.getIncludeGroupNum(); groupIndex++) {
                 final int currentGroupIndex = groupIndex; // 创建final变量用于lambda表达式
-                for (int track = 1; track <= groupInfo.getTrackNum(); track++) {
+                // 每组分配 personNum 个运动员,但不超过 trackNum
+                Long personsPerGroup = groupInfo.getPersonNum() != null ? groupInfo.getPersonNum() : groupInfo.getTrackNum();
+                // 如果每组人数超过道数,限制为道数(否则无法显示)
+                if (personsPerGroup > groupInfo.getTrackNum()) {
+                    log.warn("每组人数({})超过道数({}),将限制为道数", personsPerGroup, groupInfo.getTrackNum());
+                    personsPerGroup = groupInfo.getTrackNum();
+                }
+                for (int track = 1; track <= personsPerGroup; track++) {
                     // 寻找可用的运动员
                     GameAthleteVo selectedAthlete = null;
                     int athleteIndex = 0;
@@ -384,19 +395,31 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
                             continue;
                         }
 
-                        // 检查同一组中是否已有同一队伍的运动员
-                        boolean hasSameTeamInGroup = groupResult.entrySet().stream()
-                            .anyMatch(entry -> {
-                                String[] keyParts = entry.getKey().split("-");
-                                if (keyParts.length >= 1) {
-                                    String existingGroup = keyParts[0];
-                                    return existingGroup.equals(String.valueOf(currentGroupIndex)) &&
-                                           entry.getValue().getTeamId().equals(candidateAthlete.getTeamId());
-                                }
-                                return false;
-                            });
-
-                        if (!hasSameTeamInGroup) {
+                        // 只有项目属于团体项目才检查同一组中是否已有同一队伍的运动员
+                        // 个人项目(classification="0")不限制同一队伍,团体项目(classification="1")才限制
+                        boolean isValidCandidate = true;
+                        if (project != null && ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
+                            // 团体项目:检查同一组中是否已有同一队伍的运动员,避免同一队伍在同一组中重复分配
+                            boolean hasSameTeamInGroup = groupResult.entrySet().stream()
+                                .anyMatch(entry -> {
+                                    String[] keyParts = entry.getKey().split("-");
+                                    if (keyParts.length >= 1) {
+                                        String existingGroup = keyParts[0];
+                                        return existingGroup.equals(String.valueOf(currentGroupIndex)) &&
+                                               entry.getValue().getTeamId() != null &&
+                                               entry.getValue().getTeamId().equals(candidateAthlete.getTeamId());
+                                    }
+                                    return false;
+                                });
+                            if (hasSameTeamInGroup) {
+                                isValidCandidate = false;
+                                log.debug("第{}组拒绝运动员{}:团体项目中同一组不能有同一队伍({})的运动员",
+                                    currentGroupIndex, candidateAthlete.getAthleteCode(), candidateAthlete.getTeamId());
+                            }
+                        }
+                        // 个人项目(classification="0"或null):允许同一组中有同一队伍的运动员,不进行队伍限制检查
+
+                        if (isValidCandidate) {
                             selectedAthlete = candidateAthlete;
                             // 标记运动员为已分配
                             assignedAthleteIds.add(candidateAthlete.getAthleteId());
@@ -409,10 +432,103 @@ public class GameEventGroupServiceImpl implements IGameEventGroupService {
                     if (selectedAthlete != null) {
                         String key = currentGroupIndex + "-" + track;
                         groupResult.put(key, selectedAthlete);
+                        log.debug("分配成功 - 第{}组第{}道: 运动员编号={}, 姓名={}, 队伍={}",
+                            currentGroupIndex, track, selectedAthlete.getAthleteCode(),
+                            selectedAthlete.getName(), selectedAthlete.getTeamName());
+                    } else {
+                        // 检查是否有可用但未分配的运动员
+                        long availableCount = eligibleAthletes.stream()
+                            .filter(a -> !assignedAthleteIds.contains(a.getAthleteId()))
+                            .count();
+                        if (availableCount > 0) {
+                            log.warn("第{}组第{}道未找到合适的运动员,但仍有{}个符合条件的运动员未分配",
+                                currentGroupIndex, track, availableCount);
+                        } else {
+                            log.warn("第{}组第{}道未找到合适的运动员,所有符合条件的运动员都已被分配",
+                                currentGroupIndex, track);
+                        }
                     }
                 }
             }
 
+            // 记录所有运动员的分配情况
+            log.info("========== 分组分配详情 - 分组ID: {} ==========", groupId);
+            log.info("符合条件的运动员总数: {}", eligibleAthletes.size());
+
+            // 记录已分配的运动员
+            log.info("【已分配的运动员】");
+            groupResult.forEach((key, athlete) -> {
+                log.info("  已分配 - 位置: {}, 运动员编号: {}, 姓名: {}, 队伍ID: {}, 队伍名称: {}",
+                    key, athlete.getAthleteCode(), athlete.getName(),
+                    athlete.getTeamId(), athlete.getTeamName());
+            });
+            log.info("已分配运动员数量: {}", groupResult.size());
+
+            // 找出并记录未分配的运动员及原因
+            log.info("【未分配的运动员】");
+            List<GameAthleteVo> unassignedAthletes = eligibleAthletes.stream()
+                .filter(athlete -> !assignedAthleteIds.contains(athlete.getAthleteId()))
+                .toList();
+
+            if (unassignedAthletes.isEmpty()) {
+                log.info("  所有符合条件的运动员都已成功分配");
+            } else {
+                // 计算需要的道次总数(每组人数 × 组数,但不超过道数)
+                int personsPerGroup = groupInfo.getPersonNum() != null &&
+                    groupInfo.getPersonNum() <= groupInfo.getTrackNum()
+                        ? groupInfo.getPersonNum().intValue()
+                        : groupInfo.getTrackNum().intValue();
+                Long totalTracksNeeded = groupInfo.getIncludeGroupNum() * personsPerGroup;
+
+                for (GameAthleteVo athlete : unassignedAthletes) {
+                    String reason;
+
+                    // 分析未分配原因
+                    if (groupResult.size() >= totalTracksNeeded) {
+                        reason = String.format("道次已满(需要%d个道次,已分配%d个运动员)", totalTracksNeeded, groupResult.size());
+                    } else if (project != null && ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
+                        // 团体项目:分析每个组中是否有该队伍
+                        Map<Integer, Set<Long>> groupTeamMap = new HashMap<>();
+                        groupResult.forEach((key, assignedAthlete) -> {
+                            String[] keyParts = key.split("-");
+                            if (keyParts.length >= 1) {
+                                int group = Integer.parseInt(keyParts[0]);
+                                groupTeamMap.computeIfAbsent(group, k -> new HashSet<>()).add(assignedAthlete.getTeamId());
+                            }
+                        });
+
+                        // 检查该运动员的队伍是否在每个组中都已存在
+                        boolean blockedByTeamRule = false;
+                        StringBuilder groupDetails = new StringBuilder();
+                        for (int g = 1; g <= groupInfo.getIncludeGroupNum(); g++) {
+                            Set<Long> teamsInGroup = groupTeamMap.getOrDefault(g, new HashSet<>());
+                            if (teamsInGroup.contains(athlete.getTeamId())) {
+                                blockedByTeamRule = true;
+                                if (!groupDetails.isEmpty()) groupDetails.append("、");
+                                groupDetails.append("第").append(g).append("组");
+                            }
+                        }
+
+                        if (blockedByTeamRule) {
+                            reason = String.format("团体项目限制:队伍(%s)在第%s组已有运动员,同组不能重复分配",
+                                athlete.getTeamName(), groupDetails.toString());
+                        } else {
+                            reason = "未知原因:可能是分配算法逻辑问题";
+                        }
+                    } else {
+                        // 个人项目:道次未满但没有分配,可能是算法问题
+                        reason = String.format("道次未满(已分配%d/%d个道次),可能是分配算法限制",
+                            groupResult.size(), totalTracksNeeded);
+                    }
+
+                    log.warn("  未分配 - 运动员编号: {}, 姓名: {}, 队伍ID: {}, 队伍名称: {}, 原因: {}",
+                        athlete.getAthleteCode(), athlete.getName(),
+                        athlete.getTeamId(), athlete.getTeamName(), reason);
+                }
+                log.info("未分配运动员数量: {}", unassignedAthletes.size());
+            }
+            log.info("==========================================");
+
             // 保存分组结果到数据库
             try {
                 boolean saveSuccess = getGameAthleteCompetitionGroupService().saveGroupResult(groupId, groupResult, groupInfo);

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

@@ -133,7 +133,8 @@ public class GameEventServiceImpl implements IGameEventService {
     @Override
     public TableDataInfo<GameEventVo> queryPageList(GameEventBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<GameEvent> lqw = buildQueryWrapper(bo);
-        Page<GameEventVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        // 使用带数据权限注解的方法
+        Page<GameEventVo> result = baseMapper.selectPageEventList(pageQuery.build(), lqw);
         return TableDataInfo.build(result);
     }
 
@@ -146,7 +147,8 @@ public class GameEventServiceImpl implements IGameEventService {
     @Override
     public List<GameEventVo> queryList(GameEventBo bo) {
         LambdaQueryWrapper<GameEvent> lqw = buildQueryWrapper(bo);
-        return baseMapper.selectVoList(lqw);
+        // 使用带数据权限注解的方法
+        return baseMapper.selectEventList(lqw);
     }
 
     private LambdaQueryWrapper<GameEvent> buildQueryWrapper(GameEventBo bo) {

+ 178 - 49
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameScoreServiceImpl.java

@@ -33,6 +33,8 @@ import java.util.stream.Collectors;
 import jakarta.servlet.http.HttpServletResponse;
 import org.dromara.system.domain.bo.GameEventProjectBo;
 import org.dromara.system.domain.bo.GameTeamBo;
+import org.dromara.system.service.ISysDictTypeService;
+import org.dromara.system.domain.vo.SysDictDataVo;
 
 import java.net.URLEncoder;
 
@@ -57,6 +59,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
     private final GameAthleteMapper gameAthleteMapper;
     private final GameEventProjectMapper projectMapper;
     private final IGameEventProjectService gameEventProjectService;
+    private final ISysDictTypeService dictTypeService;
 
 
     /**
@@ -308,7 +311,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
         // 获取当前页的数据
         List<Map<String, Object>> pageData = new ArrayList<>();
-        if (startIndex < total) {
+        if (startIndex <= total) {
             pageData = allDataList.subList(startIndex, endIndex);
             log.info("当前页数据量: {}", pageData.size());
         } else {
@@ -331,6 +334,9 @@ public class GameScoreServiceImpl implements IGameScoreService {
         log.info("开始获取个人项目数据: eventId={}, projectId={}, searchValue={}", eventId, projectId, searchValue);
 
         List<Map<String, Object>> resultList = new ArrayList<>();
+        // 获取项目信息,判断是否为计时类项目
+        GameEventProjectVo project = gameEventProjectService.queryById(projectId);
+        boolean isTiming = isTimingProject(project);
 
         // 查询参与该项目的运动员
         List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, searchValue);
@@ -369,7 +375,13 @@ public class GameScoreServiceImpl implements IGameScoreService {
             if (score != null) {
                 data.put("scoreId", score.getScoreId());
                 data.put("scorePoint", score.getScorePoint());
-                data.put("individualPerformance", score.getIndividualPerformance());
+                // 如果是计时类项目,转换为时间格式显示
+                if (isTiming && score.getIndividualPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(score.getIndividualPerformance());
+                    data.put("individualPerformance", timeFormat != null ? timeFormat : score.getIndividualPerformance());
+                } else {
+                    data.put("individualPerformance", score.getIndividualPerformance());
+                }
                 data.put("teamPerformance", score.getTeamPerformance());
                 data.put("scoreRank", score.getScoreRank());
                 data.put("updateTime", score.getUpdateTime());
@@ -377,7 +389,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             } else {
                 data.put("scoreId", 0);
                 data.put("scorePoint", 0);
-                data.put("individualPerformance", 0.0);
+                data.put("individualPerformance", isTiming ? "00:00:00.000" : 0.0);
                 data.put("teamPerformance", 0.0);
                 data.put("scoreRank", 0);
                 data.put("updateTime", "");
@@ -424,18 +436,28 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
             // 查询成绩信息(使用第一个运动员的成绩作为队伍成绩)
             GameScoreVo score = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), projectId);
+            // 获取项目信息,判断是否为计时类项目
+            GameEventProjectVo project = gameEventProjectService.queryById(projectId);
+            boolean isTiming = isTimingProject(project);
+            
             if (score != null) {
                 data.put("scoreId", score.getScoreId());
                 data.put("scorePoint", score.getScorePoint());
                 data.put("individualPerformance", score.getIndividualPerformance());
-                data.put("teamPerformance", score.getTeamPerformance());
+                // 如果是计时类项目,转换为时间格式显示
+                if (isTiming && score.getTeamPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(score.getTeamPerformance());
+                    data.put("teamPerformance", timeFormat != null ? timeFormat : score.getTeamPerformance());
+                } else {
+                    data.put("teamPerformance", score.getTeamPerformance());
+                }
                 data.put("scoreRank", score.getScoreRank());
                 data.put("updateTime", score.getUpdateTime());
             } else {
                 data.put("scoreId", 0);
                 data.put("scorePoint", 0);
                 data.put("individualPerformance", 0.0);
-                data.put("teamPerformance", 0.0);
+                data.put("teamPerformance", isTiming ? "00:00:00.000" : 0.0);
                 data.put("scoreRank", 0);
                 data.put("updateTime", "");
             }
@@ -987,9 +1009,14 @@ public class GameScoreServiceImpl implements IGameScoreService {
                     vo.setClassification("");
                 }
 
-                // 设置成绩信息
-                vo.setIndividualPerformance(convertToBigDecimal(score.getIndividualPerformance()));
-                vo.setTeamPerformance(convertToBigDecimal(score.getTeamPerformance()));
+                // 设置成绩信息 - 如果是计时类项目,需要特殊处理
+                BigDecimal individualPerf = convertToBigDecimal(score.getIndividualPerformance());
+                BigDecimal teamPerf = convertToBigDecimal(score.getTeamPerformance());
+                
+                // 注意:AppScoreVo 的字段类型是 BigDecimal,但如果是计时类项目,我们仍然存储 BigDecimal
+                // 前端可以根据项目类型决定如何显示
+                vo.setIndividualPerformance(individualPerf);
+                vo.setTeamPerformance(teamPerf);
                 vo.setScorePoint(score.getScorePoint() != null ? score.getScorePoint() : 0);
                 vo.setScoreRank(score.getScoreRank() != null ? score.getScoreRank() : 0);
 
@@ -1503,33 +1530,33 @@ public class GameScoreServiceImpl implements IGameScoreService {
     public void exportScoresDetail(Long eventId, HttpServletResponse response) {
         try {
             log.info("开始导出成绩详情,eventId: {}", eventId);
-            
+
             // 1. 获取所有项目
             List<GameEventProjectVo> projects = gameEventProjectService.queryListByEventId(eventId);
             if (projects.isEmpty()) {
                 throw new RuntimeException("未找到赛事项目");
             }
-            
+
             // 2. 创建Excel工作簿
             try (Workbook workbook = new XSSFWorkbook()) {
-                
+
                 // 3. 创建样式
                 CellStyle headerStyle = createHeaderStyle(workbook);
                 CellStyle dataStyleOdd = createDataStyle(workbook, IndexedColors.WHITE);
                 CellStyle dataStyleEven = createDataStyle(workbook, IndexedColors.GREY_25_PERCENT);
-                
+
                 // 4. 为每个项目创建Sheet页
                 for (GameEventProjectVo project : projects) {
                     createProjectSheet(workbook, project, eventId, headerStyle, dataStyleOdd, dataStyleEven);
                 }
-                
+
                 // 5. 设置响应头并输出
                 setResponseHeaders(response, "成绩详情表");
                 workbook.write(response.getOutputStream());
             }
-            
+
             log.info("成绩详情导出完成");
-            
+
         } catch (Exception e) {
             log.error("导出成绩详情失败", e);
             throw new RuntimeException("导出失败:" + e.getMessage());
@@ -1539,7 +1566,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
     /**
      * 为单个项目创建Sheet页
      */
-    private void createProjectSheet(Workbook workbook, GameEventProjectVo project, Long eventId, 
+    private void createProjectSheet(Workbook workbook, GameEventProjectVo project, Long eventId,
         CellStyle headerStyle, CellStyle dataStyleOdd, CellStyle dataStyleEven) {
 
         // 1. 创建Sheet页,名称限制在31个字符内
@@ -1548,8 +1575,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
 
         // 2. 获取项目详细数据
         List<Map<String, Object>> projectData = getProjectScoreDataAll(
-            eventId, 
-            project.getProjectId(), 
+            eventId,
+            project.getProjectId(),
             project.getClassification()
         );
 
@@ -1568,7 +1595,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
      */
     private List<Map<String, Object>> getProjectScoreDataAll(Long eventId, Long projectId, String classification) {
         log.info("获取项目全部数据: eventId={}, projectId={}, classification={}", eventId, projectId, classification);
-        
+
         if ("0".equals(classification)) {
             // 个人项目:获取所有运动员数据
             return getIndividualProjectDataAll(eventId, projectId);
@@ -1583,13 +1610,16 @@ public class GameScoreServiceImpl implements IGameScoreService {
      */
     private List<Map<String, Object>> getIndividualProjectDataAll(Long eventId, Long projectId) {
         List<Map<String, Object>> resultList = new ArrayList<>();
-        
+        // 获取项目信息,判断是否为计时类项目
+        GameEventProjectVo project = gameEventProjectService.queryById(projectId);
+        boolean isTiming = isTimingProject(project);
+
         // 查询参与该项目的所有运动员(不分页)
         List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, null);
-        
+
         for (GameAthleteVo athlete : athletes) {
             Map<String, Object> data = new HashMap<>();
-            
+
             // 基础信息
             data.put("athleteId", athlete.getAthleteId());
             data.put("userId", athlete.getUserId());
@@ -1600,7 +1630,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
             data.put("name", athlete.getName());
             data.put("unit", athlete.getUnit());
             data.put("groupType", athlete.getGroupType());
-            
+
             // 队伍信息
             if (athlete.getTeamId() != null) {
                 GameTeamVo team = gameTeamService.queryById(athlete.getTeamId());
@@ -1609,13 +1639,19 @@ public class GameScoreServiceImpl implements IGameScoreService {
                     data.put("teamCode", team.getTeamCode());
                 }
             }
-            
+
             // 成绩信息
             GameScoreVo score = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), projectId);
             if (score != null) {
                 data.put("scoreId", score.getScoreId());
                 data.put("scorePoint", score.getScorePoint());
-                data.put("individualPerformance", score.getIndividualPerformance());
+                // 如果是计时类项目,转换为时间格式显示
+                if (isTiming && score.getIndividualPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(score.getIndividualPerformance());
+                    data.put("individualPerformance", timeFormat != null ? timeFormat : score.getIndividualPerformance());
+                } else {
+                    data.put("individualPerformance", score.getIndividualPerformance());
+                }
                 data.put("teamPerformance", score.getTeamPerformance());
                 data.put("scoreRank", score.getScoreRank());
                 data.put("updateTime", score.getUpdateTime());
@@ -1623,15 +1659,15 @@ public class GameScoreServiceImpl implements IGameScoreService {
                 // 设置默认值
                 data.put("scoreId", 0);
                 data.put("scorePoint", 0);
-                data.put("individualPerformance", 0.0);
+                data.put("individualPerformance", isTiming ? "00:00:00.000" : 0.0);
                 data.put("teamPerformance", 0.0);
                 data.put("scoreRank", 0);
                 data.put("updateTime", "");
             }
-            
+
             resultList.add(data);
         }
-        
+
         return resultList;
     }
 
@@ -1641,51 +1677,61 @@ public class GameScoreServiceImpl implements IGameScoreService {
     private List<Map<String, Object>> getTeamProjectDataAll(Long eventId, Long projectId) {
         List<Map<String, Object>> resultList = new ArrayList<>();
         Set<Long> processedTeamIds = new HashSet<>();
-        
+
+        // 获取项目信息,判断是否为计时类项目
+        GameEventProjectVo project = gameEventProjectService.queryById(projectId);
+        boolean isTiming = isTimingProject(project);
+
         // 查询参与该项目的所有运动员
         List<GameAthleteVo> athletes = gameAthleteService.queryListByEventIdAndProjectId(eventId, projectId, null);
-        
+
         for (GameAthleteVo athlete : athletes) {
             if (athlete.getTeamId() == null || processedTeamIds.contains(athlete.getTeamId())) {
                 continue;
             }
-            
+
             processedTeamIds.add(athlete.getTeamId());
-            
+
             // 查询队伍信息
             GameTeamVo team = gameTeamService.queryById(athlete.getTeamId());
             if (team == null) {
                 continue;
             }
-            
+
             Map<String, Object> data = new HashMap<>();
             data.put("teamId", team.getTeamId());
             data.put("teamName", team.getTeamName());
             data.put("teamCode", team.getTeamCode());
             data.put("eventId", eventId);
             data.put("projectId", projectId);
-            
+
             // 查询成绩信息
             GameScoreVo score = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), projectId);
             if (score != null) {
                 data.put("scoreId", score.getScoreId());
                 data.put("scorePoint", score.getScorePoint());
                 data.put("individualPerformance", score.getIndividualPerformance());
-                data.put("teamPerformance", score.getTeamPerformance());
+                // 如果是计时类项目,转换为时间格式显示
+                if (isTiming && score.getTeamPerformance() != null) {
+                    String timeFormat = convertDecimalToTimeScore(score.getTeamPerformance());
+                    data.put("teamPerformance", timeFormat != null ? timeFormat : score.getTeamPerformance());
+                } else {
+                    data.put("teamPerformance", score.getTeamPerformance());
+                }
                 data.put("scoreRank", score.getScoreRank());
                 data.put("updateTime", score.getUpdateTime());
             } else {
                 data.put("scoreId", 0);
                 data.put("scorePoint", 0);
                 data.put("individualPerformance", 0.0);
-                data.put("teamPerformance", 0.0);
+                data.put("teamPerformance", isTiming ? "00:00:00.000" : 0.0);
                 data.put("scoreRank", 0);
                 data.put("updateTime", "");
             }
-            
+
             resultList.add(data);
         }
-        
+
         return resultList;
     }
 
@@ -1695,15 +1741,15 @@ public class GameScoreServiceImpl implements IGameScoreService {
     private void createProjectHeaderRow(Sheet sheet, GameEventProjectVo project, CellStyle headerStyle) {
         Row headerRow = sheet.createRow(0);
         int colIndex = 0;
-        
+
         // 根据项目类型确定列标题
         if ("0".equals(project.getClassification())) {
             // 个人项目列标题
             String[] headers = {
-                "序号", "队伍编号", "队伍名称", "姓名", "号码", 
+                "序号", "队伍编号", "队伍名称", "姓名", "号码",
                 "个人成绩", "积分", "排名", "更新时间"
             };
-            
+
             for (String header : headers) {
                 Cell cell = headerRow.createCell(colIndex++);
                 cell.setCellValue(header);
@@ -1712,10 +1758,10 @@ public class GameScoreServiceImpl implements IGameScoreService {
         } else {
             // 团体项目列标题
             String[] headers = {
-                "序号", "队伍编号", "队伍名称", "团队成绩", 
+                "序号", "队伍编号", "队伍名称", "团队成绩",
                 "积分", "排名", "更新时间"
             };
-            
+
             for (String header : headers) {
                 Cell cell = headerRow.createCell(colIndex++);
                 cell.setCellValue(header);
@@ -1727,7 +1773,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
     /**
      * 填充项目数据行
      */
-    private void fillProjectDataRows(Sheet sheet, List<Map<String, Object>> projectData, 
+    private void fillProjectDataRows(Sheet sheet, List<Map<String, Object>> projectData,
     GameEventProjectVo project, CellStyle dataStyleOdd, CellStyle dataStyleEven) {
 
         int rowIndex = 1;
@@ -1771,7 +1817,7 @@ public class GameScoreServiceImpl implements IGameScoreService {
         if (projectName == null) {
             return "项目";
         }
-        
+
         // 替换Excel不允许的特殊字符
         String cleanName = projectName
             .replace("*", "×")     // 星号替换为乘号
@@ -1782,22 +1828,22 @@ public class GameScoreServiceImpl implements IGameScoreService {
             .replace("]", ")")     // 右方括号替换为右括号
             .replace(":", ":")    // 英文冒号替换为中文冒号
             .trim();               // 去除首尾空格
-        
+
         // 处理单引号问题
         if (cleanName.startsWith("'") || cleanName.endsWith("'")) {
             cleanName = cleanName.replace("'", "_");
         }
-        
+
         // 如果处理后为空,使用默认名称
         if (cleanName.isEmpty()) {
             cleanName = "项目";
         }
-        
+
         // 限制长度在31个字符内
         if (cleanName.length() > 31) {
             cleanName = cleanName.substring(0, 31);
         }
-        
+
         return cleanName;
     }
 
@@ -1832,4 +1878,87 @@ public class GameScoreServiceImpl implements IGameScoreService {
         String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
         response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
     }
+
+    /**
+     * 判断项目是否为计时类项目
+     * 根据项目的计算规则(scoreRule)判断
+     *
+     * @param project 项目信息
+     * @return true表示是计时类项目,false表示不是
+     */
+    private boolean isTimingProject(GameEventProjectVo project) {
+        if (project == null || project.getScoreRule() == null) {
+            return false;
+        }
+        try {
+            List<SysDictDataVo> gameScoreType = dictTypeService.selectDictDataByType("game_score_type");
+            String scoreRule = project.getScoreRule().toLowerCase();
+            Optional<String> rule = gameScoreType.stream()
+                .filter(data -> data.getDictLabel().contains("计时"))
+                .map(SysDictDataVo::getDictValue)
+                .findFirst();
+            // 判断计算规则是否为计时类
+            return rule.isPresent() && scoreRule.equals(rule.get().toLowerCase());
+        } catch (Exception e) {
+            log.warn("判断项目是否为计时类失败: {}", project.getProjectId(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 将小数格式的成绩转换成时间格式显示(时:分:秒.毫秒)
+     * 格式:xx:xx:xx.nnn (时:分:秒.毫秒)
+     *
+     * @param decimalScore 以秒为单位的小数值
+     * @return 时间格式字符串,如 "01:23:45.678"
+     */
+    private String convertDecimalToTimeScore(BigDecimal decimalScore) {
+        if (decimalScore == null || decimalScore.compareTo(BigDecimal.ZERO) < 0) {
+            return null;
+        }
+
+        try {
+            // 计算小时
+            BigDecimal hoursBigDecimal = decimalScore.divide(BigDecimal.valueOf(3600), 0, java.math.RoundingMode.DOWN);
+            int hours = hoursBigDecimal.intValue();
+            BigDecimal remaining = decimalScore.remainder(BigDecimal.valueOf(3600));
+
+            // 计算分钟
+            BigDecimal minutesBigDecimal = remaining.divide(BigDecimal.valueOf(60), 0, java.math.RoundingMode.DOWN);
+            int minutes = minutesBigDecimal.intValue();
+            remaining = remaining.remainder(BigDecimal.valueOf(60));
+
+            // 秒和毫秒
+            int seconds = remaining.intValue();
+            // 计算毫秒:取小数部分乘以1000并四舍五入
+            BigDecimal millisecondsBigDecimal = remaining.subtract(BigDecimal.valueOf(seconds))
+                    .multiply(BigDecimal.valueOf(1000))
+                    .setScale(0, java.math.RoundingMode.HALF_UP);
+            int milliseconds = millisecondsBigDecimal.intValue();
+
+            // 处理毫秒溢出(理论上不应该发生,但保险起见)
+            if (milliseconds >= 1000) {
+                seconds += milliseconds / 1000;
+                milliseconds = milliseconds % 1000;
+            }
+
+            // 处理秒溢出(理论上不应该发生,但保险起见)
+            if (seconds >= 60) {
+                minutes += seconds / 60;
+                seconds = seconds % 60;
+            }
+
+            // 处理分钟溢出(理论上不应该发生,但保险起见)
+            if (minutes >= 60) {
+                hours += minutes / 60;
+                minutes = minutes % 60;
+            }
+
+            // 格式化输出:时:分:秒.毫秒
+            return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
+        } catch (Exception e) {
+            log.warn("转换小数为时间格式失败: {}", decimalScore, e);
+            return null;
+        }
+    }
 }

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

@@ -154,8 +154,8 @@ public class GameTeamServiceImpl implements IGameTeamService {
                 bo.setEventId((Long) cacheObject);
             }
         }
-        LambdaQueryWrapper<GameTeam> lqw = buildQueryWrapper(bo);
-        List<GameTeamVo> list = baseMapper.selectVoList(lqw);
+        // 使用包含分组名的查询方法
+        List<GameTeamVo> list = baseMapper.selectVoListWithGroupName(bo);
         list.forEach(vo -> {
             if (vo.getAthleteValue() != null) {
                 vo.setAthleteList(JSONUtil.toList(vo.getAthleteValue(), Long.class));
@@ -270,6 +270,7 @@ public class GameTeamServiceImpl implements IGameTeamService {
         if (entity.getTeamCode() != null){
             Long count = baseMapper.selectCount(
                 Wrappers.lambdaQuery(GameTeam.class)
+                    .eq(GameTeam::getEventId, entity.getEventId())
                     .eq(GameTeam::getTeamCode, entity.getTeamCode())
                     .ne(entity.getTeamId() != null,GameTeam::getTeamId, entity.getTeamId())
             );
@@ -386,7 +387,7 @@ public class GameTeamServiceImpl implements IGameTeamService {
         // 将运动员ID列表转换为JSON字符串
         String athleteValue = JSONUtil.toJsonStr(athleteIds);
         team.setAthleteValue(athleteValue);
-        team.setAthleteNum((long) athleteIds.size());
+        team.setAthleteNum(athleteIds.size());
 
         // 更新队伍信息
         return baseMapper.updateById(team) > 0;

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

@@ -225,7 +225,7 @@ public class IEnrollServiceImpl implements IEnrollService {
             gameTeamBo.setTeamName(teamName);
             gameTeamBo.setLeader(name);
             gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(Arrays.asList(athleteId)));
-            gameTeamBo.setAthleteNum(1L);
+            gameTeamBo.setAthleteNum(1);
             gameTeamBo.setNumberRange(startNumber + "-" + endNumber);
             gameTeamBo.setCreateDept(-1L);
             gameTeamBo.setCreateBy(-1L);
@@ -390,8 +390,9 @@ public class IEnrollServiceImpl implements IEnrollService {
     }
 
     /**
-     * 判断单元格是否表示“已选择”
+     * 判断单元格是否表示"已选择"
      * 支持:是、yes、true、1、✔、✅、√ 等
+     * 不支持:×、否、no、false、0 等表示不选择的值
      */
     private static boolean isCellSelected(Cell cell) {
         if (cell == null) return false;
@@ -401,14 +402,21 @@ public class IEnrollServiceImpl implements IEnrollService {
             return cell.getBooleanCellValue();
         }
         if (cell.getCellType() == CellType.NUMERIC) {
-            return cell.getNumericCellValue() == 1;
+            return cell.getNumericCellValue() == 1 || cell.getNumericCellValue() == 1.0;
         }
 
         String value = getCellValueAsString(cell).trim().toLowerCase();
+
+        // 明确排除表示"不选择"的值
+        if (value.isBlank() || value.equals("×") || value.equals("否") || value.equals("no") || value.equals("false") || value.equals("0") || value.equals("n")) {
+            return false;
+        }
+
+        // 只有明确表示"选择"的值才返回true
         return !value.isEmpty() &&
             (value.equals("是") || value.equals("yes") || value.equals("true") ||
                 value.equals("1") || value.contains("✔") || value.contains("✅") ||
-                value.contains("√") || value.equals("×"));
+                value.contains("√"));
     }
 
     /**
@@ -505,77 +513,31 @@ public class IEnrollServiceImpl implements IEnrollService {
             AtomicInteger currentNumber;
             boolean isNewTeam = false;
             String numberRange = "";
+            boolean needUpdateNumberRange = false;
 
             // 判断队伍是否已存在
-            if (existingTeamMap.containsKey(teamName)) {
-                // 队伍已存在,使用现有队伍的号码段
-                GameTeam existingTeam = existingTeamMap.get(teamName);
+            GameTeam existingTeam = existingTeamMap.get(teamName);
+            if (existingTeam != null) {
+                // 队伍已存在
                 teamId = existingTeam.getTeamId();
                 numberRange = existingTeam.getNumberRange();
 
-                try {
-                    // 尝试查询该队伍的最大队员编号
-                    String maxNumberStr = gameAthleteService.queryMaxNumber(teamId);
-                    Integer maxNumber = Integer.valueOf(maxNumberStr);
-
-                    // 如果最大编号为0,说明队伍实际上没有队员,从号码段起始开始
-                    if (maxNumber == 0) {
-                        String[] rangeParts = numberRange.split("-");
-                        int startNumber = Integer.valueOf(rangeParts[0]);
-                        currentNumber = new AtomicInteger(startNumber);
-                    } else {
-                        // 成功获取到有效编号,续着分配
-                        currentNumber = new AtomicInteger(maxNumber + 1);
-                    }
-                } catch (NumberFormatException e) {
-                    log.warn("解析队伍{}的号码段失败: {},从号码段起始开始分配", teamName, numberRange, e);
-                    String[] rangeParts = numberRange.split("-");
-                    int startNumber = Integer.valueOf(rangeParts[0]);
-                    currentNumber = new AtomicInteger(startNumber);
-                } catch (Exception e) {
-                    log.error("查询队伍{}的最大队员编号时发生异常", teamName, e);
-                    String[] rangeParts = numberRange.split("-");
-                    int startNumber = Integer.valueOf(rangeParts[0]);
-                    currentNumber = new AtomicInteger(startNumber);
+                // 检查号码段是否为空或无效
+                if (StringUtils.isBlank(numberRange) || !isValidNumberRange(numberRange)) {
+                    log.warn("队伍{}存在但号码段为空或无效: {},将为队伍生成新的号码段", teamName, numberRange);
+                    numberRange = generateOrGetNumberRange(athletes, eventId, teamId, dataList.size(), teamIndexCounter);
+                    currentNumber = initializeCurrentNumber(numberRange, teamId, teamName);
+                    needUpdateNumberRange = true;
+                } else {
+                    // 号码段有效,使用现有号码段
+                    currentNumber = getCurrentNumberFromExistingTeam(numberRange, teamId, teamName);
                 }
             } else {
-                // 队伍不存在,创建新队伍并分配新的300号码段
+                // 队伍不存在,创建新队伍
                 isNewTeam = true;
                 teamId = snowflake.nextId();
-
-                // 检查是否有填写的号码
-                List<String> providedCodes = athletes.stream()
-                .map(EnrollProjectVo::getAthleteCode)
-                .filter(StringUtils::isNotBlank)
-                .map(code -> code.trim())
-                .filter(code -> StringUtils.isNotBlank(code))
-                .collect(Collectors.toList());
-
-                if (!providedCodes.isEmpty()) {
-                    // 有填写的号码,使用ASCII值比较来确定号码段
-                    numberRange = generateCustomNumberRangeWithChars(providedCodes, eventId, null);
-
-                    if (numberRange != null) {
-                        // 成功生成自定义号码段
-                        currentNumber = null; // 对于字符号码,不使用数字计数器
-                        log.info("队伍 {} 使用自定义字符号码段: {}", teamName, numberRange);
-                    } else {
-                        // 生成失败,使用默认方式
-                        int teamIndex = teamIndexCounter.getAndIncrement();
-                        numberRange = generateNumberRange(dataList.size(), teamIndex);
-                        String[] rangeParts = numberRange.split("-");
-                        int startNumber = Integer.valueOf(rangeParts[0]);
-                        currentNumber = new AtomicInteger(startNumber);
-                        log.warn("队伍 {} 自定义号码段生成失败,使用默认号码段: {}", teamName, numberRange);
-                    }
-                } else {
-                    // 没有填写的号码,使用默认的300号码段
-                    int teamIndex = teamIndexCounter.getAndIncrement();
-                    numberRange = generateNumberRange(dataList.size(), teamIndex);
-                    String[] rangeParts = numberRange.split("-");
-                    int startNumber = Integer.valueOf(rangeParts[0]);
-                    currentNumber = new AtomicInteger(startNumber);
-                }
+                numberRange = generateOrGetNumberRange(athletes, eventId, null, dataList.size(), teamIndexCounter);
+                currentNumber = initializeCurrentNumber(numberRange, null, teamName);
             }
 
             // 保存队员信息
@@ -595,13 +557,17 @@ public class IEnrollServiceImpl implements IEnrollService {
                 gameTeamBo.setTeamName(teamName);
                 gameTeamBo.setLeader(athletes.get(0).getLeader());
                 gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(athletesId));
-                gameTeamBo.setAthleteNum(Long.valueOf(athletes.size()));
+                gameTeamBo.setAthleteNum(athletes.size());
                 gameTeamBo.setNumberRange(numberRange); // 设置300个号码段
                 gameTeamBo.setStatus("0");
                 gameTeamService.insertByBo(gameTeamBo);
             } else {
                 // 更新现有队伍的队员列表和数量
-                updateExistingTeam(teamId, athletesId, athletes.size());
+                if (needUpdateNumberRange) {
+                    updateExistingTeamWithNumberRange(teamId, athletesId, athletes.size(), numberRange);
+                } else {
+                    updateExistingTeam(teamId, athletesId, athletes.size());
+                }
             }
         }
 
@@ -939,9 +905,8 @@ public class IEnrollServiceImpl implements IEnrollService {
         Map<String, Boolean> selectProjects = enrollInfo.getProjectSelections();
         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);
+            if (selectProject.getValue()) {
+                selectionProjectIds.add(projectList.get(selectProject.getKey()));
             }
         }
         gameAthleteBo.setProjectValue(JSONUtil.toJsonStr(selectionProjectIds));
@@ -973,6 +938,160 @@ public class IEnrollServiceImpl implements IEnrollService {
         }
     }
 
+    /**
+     * 更新现有队伍信息(包括号码段)
+     */
+    private void updateExistingTeamWithNumberRange(Long teamId, List<Long> newAthletesId, int newAthleteCount, String numberRange) {
+        GameTeamVo existingTeam = gameTeamService.queryById(teamId);
+        if (existingTeam != null) {
+            // 合并现有队员ID和新队员ID
+            List<Long> existingAthletes = JSONUtil.toList(existingTeam.getAthleteValue(), Long.class);
+            List<Long> allAthletes = new ArrayList<>();
+            if (existingAthletes != null) {
+                allAthletes.addAll(existingAthletes);
+            }
+            allAthletes.addAll(newAthletesId);
+
+            // 更新队伍信息
+            GameTeamBo updateTeamBo = new GameTeamBo();
+            updateTeamBo.setTeamId(teamId);
+            updateTeamBo.setAthleteValue(JSONUtil.toJsonStr(allAthletes));
+            updateTeamBo.setAthleteNum(existingTeam.getAthleteNum() + newAthleteCount);
+            updateTeamBo.setNumberRange(numberRange); // 更新号码段
+            gameTeamService.updateByBo(updateTeamBo);
+        }
+    }
+
+    /**
+     * 验证号码段格式是否有效
+     * 号码段格式为:xxxx-yyyy(如:0001-0300)
+     *
+     * @param numberRange 号码段
+     * @return 是否有效
+     */
+    private boolean isValidNumberRange(String numberRange) {
+        if (StringUtils.isBlank(numberRange)) {
+            return false;
+        }
+
+        // 检查是否包含"-"分隔符
+        if (!numberRange.contains("-")) {
+            return false;
+        }
+
+        // 检查是否能够正确分割
+        String[] parts = numberRange.split("-");
+        if (parts.length != 2) {
+            return false;
+        }
+
+        // 检查起始和结束号码都不为空
+        if (StringUtils.isBlank(parts[0]) || StringUtils.isBlank(parts[1])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 生成或获取号码段
+     *
+     * @param athletes 运动员列表
+     * @param eventId 赛事ID
+     * @param teamId 队伍ID(可为null)
+     * @param totalSize 总人数
+     * @param teamIndexCounter 队伍索引计数器
+     * @return 号码段
+     */
+    private String generateOrGetNumberRange(List<EnrollProjectVo> athletes, Long eventId, Long teamId,
+                                           int totalSize, AtomicInteger teamIndexCounter) {
+        // 收集填写的号码
+        List<String> providedCodes = athletes.stream()
+            .map(EnrollProjectVo::getAthleteCode)
+            .filter(StringUtils::isNotBlank)
+            .map(String::trim)
+            .filter(code -> StringUtils.isNotBlank(code))
+            .collect(Collectors.toList());
+
+        if (!providedCodes.isEmpty()) {
+            // 尝试生成自定义号码段
+            String customRange = generateCustomNumberRangeWithChars(providedCodes, eventId, teamId);
+            if (customRange != null) {
+                log.info("使用自定义号码段: {}", customRange);
+                return customRange;
+            }
+        }
+
+        // 生成默认号码段
+        int teamIndex = teamIndexCounter.getAndIncrement();
+        return generateNumberRange(totalSize, teamIndex);
+    }
+
+    /**
+     * 初始化当前号码
+     *
+     * @param numberRange 号码段
+     * @param teamId 队伍ID(可为null)
+     * @param teamName 队伍名称
+     * @return 当前号码
+     */
+    private AtomicInteger initializeCurrentNumber(String numberRange, Long teamId, String teamName) {
+        // 尝试分割号码段
+        String[] rangeParts = numberRange.split("-");
+        if (rangeParts.length == 2) {
+            try {
+                int startNumber = Integer.valueOf(rangeParts[0]);
+                return new AtomicInteger(startNumber);
+            } catch (NumberFormatException e) {
+                log.warn("解析号码段起始号码失败: {}, 使用默认值", numberRange);
+            }
+        }
+
+        // 如果解析失败,尝试从队伍查询
+        if (teamId != null) {
+            try {
+                String maxNumberStr = gameAthleteService.queryMaxNumber(teamId);
+                Integer maxNumber = Integer.valueOf(maxNumberStr);
+                return new AtomicInteger(maxNumber > 0 ? maxNumber + 1 : 1);
+            } catch (Exception e) {
+                log.warn("查询队伍{}的最大号码失败", teamName, e);
+            }
+        }
+
+        return new AtomicInteger(1);
+    }
+
+    /**
+     * 从现有队伍获取当前号码
+     *
+     * @param numberRange 号码段
+     * @param teamId 队伍ID
+     * @param teamName 队伍名称
+     * @return 当前号码
+     */
+    private AtomicInteger getCurrentNumberFromExistingTeam(String numberRange, Long teamId, String teamName) {
+        try {
+            // 查询队伍的最大队员编号
+            String maxNumberStr = gameAthleteService.queryMaxNumber(teamId);
+            Integer maxNumber = Integer.valueOf(maxNumberStr);
+
+            // 如果最大编号为0,从号码段起始开始
+            if (maxNumber == 0) {
+                String[] rangeParts = numberRange.split("-");
+                int startNumber = Integer.valueOf(rangeParts[0]);
+                return new AtomicInteger(startNumber);
+            } else {
+                // 续着分配
+                return new AtomicInteger(maxNumber + 1);
+            }
+        } catch (Exception e) {
+            log.warn("查询队伍{}的号码失败: {},从号码段起始开始", teamName, numberRange, e);
+            String[] rangeParts = numberRange.split("-");
+            int startNumber = Integer.valueOf(rangeParts[0]);
+            return new AtomicInteger(startNumber);
+        }
+    }
+
     private String generateNumberRangeByMax(int maxNumber) {
         // 每个队伍固定分配300个号码
         int start = maxNumber + 1;

+ 434 - 64
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/app/ElectrometerServiceImpl.java

@@ -3,10 +3,14 @@ package org.dromara.system.service.impl.app;
 import com.esotericsoftware.minlog.Log;
 import lombok.RequiredArgsConstructor;
 import org.dromara.system.domain.*;
+import org.dromara.system.domain.app.AvtScoreItem;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.domain.vo.app.AvtScoreUploadVo;
 import org.dromara.system.domain.vo.app.DistanceScoreUploadVo;
 import org.dromara.system.domain.vo.app.ElectrometerDataItemVo;
 import org.dromara.system.domain.vo.app.ElectrometerScoreUploadVo;
 import org.dromara.system.mapper.*;
+import org.dromara.system.service.ISysDictTypeService;
 import org.dromara.system.service.app.IElectrometerService;
 import org.springframework.stereotype.Service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -32,6 +36,7 @@ public class ElectrometerServiceImpl implements IElectrometerService {
     private final GameAthleteCompetitionGroupMapper gameAthleteCompetitionGroupMapper;
     private final GameEventGroupMapper gameEventGroupMapper;
     private final GameScoreMapper gameScoreMapper;
+    private final ISysDictTypeService dictTypeService;
 
     /**
      * 获取比赛道路信息
@@ -56,7 +61,7 @@ public class ElectrometerServiceImpl implements IElectrometerService {
         // 2. 查询项目信息
         LambdaQueryWrapper<GameEventProject> projectWrapper = Wrappers.lambdaQuery(GameEventProject.class)
                 .eq(GameEventProject::getEventId, eventId)
-                .eq(GameEventProject::getScoreRule,tjsdm);
+                .eq(GameEventProject::getProjectType,tjsdm);
 
         List<GameEventProject> projects = gameEventProjectMapper.selectList(projectWrapper);
         Map<Long, GameEventProject> projectMap = projects.stream()
@@ -65,10 +70,12 @@ public class ElectrometerServiceImpl implements IElectrometerService {
         // 3. 查询运动员比赛分组信息
         List<Long> athleteIds = athletes.stream().map(GameAthlete::getAthleteId).collect(Collectors.toList());
         LambdaQueryWrapper<GameAthleteCompetitionGroup> groupWrapper = Wrappers.lambdaQuery(GameAthleteCompetitionGroup.class)
-                .in(GameAthleteCompetitionGroup::getAthleteId, athleteIds);
+                .in(GameAthleteCompetitionGroup::getAthleteId, athleteIds)
+            .in(GameAthleteCompetitionGroup::getProjectId, projects.stream().map(GameEventProject::getProjectId).collect(Collectors.toList()));
         List<GameAthleteCompetitionGroup> competitionGroups = gameAthleteCompetitionGroupMapper.selectList(groupWrapper);
-        Map<Long, GameAthleteCompetitionGroup> groupMap = competitionGroups.stream()
-                .collect(Collectors.toMap(GameAthleteCompetitionGroup::getAthleteId, g -> g));
+        // 支持一个运动员对应多个分组
+        Map<Long, List<GameAthleteCompetitionGroup>> groupMap = competitionGroups.stream()
+                .collect(Collectors.groupingBy(GameAthleteCompetitionGroup::getAthleteId));
 
         // 4. 查询分组信息
         List<Long> groupIds = competitionGroups.stream().map(GameAthleteCompetitionGroup::getGroupId).collect(Collectors.toList());
@@ -85,47 +92,61 @@ public class ElectrometerServiceImpl implements IElectrometerService {
 
         // 5. 组装数据
 //        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
-        return athletes.stream().map(athlete -> {
-            ElectrometerDataItemVo item = new ElectrometerDataItemVo();
-
-            // 基本信息
-            item.setHm(athlete.getAthleteCode());
-            item.setXm(athlete.getName());
-            item.setXb(athlete.getGender());
-            item.setDw(athlete.getUnit());
-
-            // 获取比赛分组信息
-            GameAthleteCompetitionGroup competitionGroup = groupMap.get(athlete.getAthleteId());
-            if (competitionGroup != null) {
-                item.setCcDm(String.valueOf(competitionGroup.getGroupId()));
-                item.setZc(String.valueOf(competitionGroup.getGroupIndex()));
-                item.setDc(String.valueOf(competitionGroup.getTrackIndex()));
-
-                // 组别信息
-                if (!eventGroupMap.isEmpty()) {
-                    GameEventGroup eventGroup = eventGroupMap.get(competitionGroup.getGroupId());
-                    if (eventGroup != null) {
-                        item.setZbMc(eventGroup.getGroupName());
-                        if (eventGroup.getBeginTime() != null) {
-                            item.setTm(eventGroup.getBeginTime());
+        List<SysDictDataVo> gameStage = dictTypeService.selectDictDataByType("game_stage");
+        List<ElectrometerDataItemVo> result = new ArrayList<>();
+
+        for (GameAthlete athlete : athletes) {
+            // 获取该运动员的所有分组
+            List<GameAthleteCompetitionGroup> athleteGroups = groupMap.get(athlete.getAthleteId());
+            if (athleteGroups != null && !athleteGroups.isEmpty()) {
+                // 为每个分组创建一条记录
+                for (GameAthleteCompetitionGroup competitionGroup : athleteGroups) {
+                    ElectrometerDataItemVo item = new ElectrometerDataItemVo();
+
+                    // 基本信息
+                    item.setHm(athlete.getAthleteCode());
+                    item.setXm(athlete.getName());
+                    item.setXb(athlete.getGender());
+                    item.setDw(athlete.getUnit());
+
+                    // 比赛分组信息
+                    item.setCcDm(String.valueOf(competitionGroup.getGroupId()));
+                    item.setZc(String.valueOf(competitionGroup.getGroupIndex()));
+                    item.setDc(String.valueOf(competitionGroup.getTrackIndex()));
+
+                    // 组别信息
+                    if (!eventGroupMap.isEmpty()) {
+                        GameEventGroup eventGroup = eventGroupMap.get(competitionGroup.getGroupId());
+                        if (eventGroup != null) {
+                            item.setZbMc(eventGroup.getGroupName());
+                            if (eventGroup.getBeginTime() != null) {
+                                item.setTm(eventGroup.getBeginTime());
+                            }
                         }
                     }
-                }
 
-                // 项目信息 - 根据比赛分组中的项目ID查找对应项目
-                GameEventProject project = projectMap.get(competitionGroup.getProjectId());
-                if (project != null) {
-                    item.setXmMc(project.getProjectName());
-                    item.setTjsDm(project.getProjectType());
-                    item.setSx(project.getScoreRule()); // 计算规则-项目属性
-                    if (project.getStartTime() != null) {
-                        item.setTm(project.getStartTime());
+                    // 项目信息 - 根据比赛分组中的项目ID查找对应项目
+                    GameEventProject project = projectMap.get(competitionGroup.getProjectId());
+                    if (project != null) {
+                        item.setXmMc(project.getProjectName());
+                        item.setTjsDm(project.getProjectType());
+                        item.setSx(project.getScoreRule()); // 计算规则-项目属性
+                        Optional<String> label = gameStage.stream()
+                            .filter(d -> d.getDictValue().equals(project.getGameStage()))
+                            .map(SysDictDataVo::getDictLabel)
+                            .findFirst();
+                        item.setScMc(label.orElse("")); //比赛阶段
+                        if (project.getStartTime() != null) {
+                            item.setTm(project.getStartTime());
+                        }
                     }
+
+                    result.add(item);
                 }
             }
+        }
 
-            return item;
-        }).collect(Collectors.toList());
+        return result;
     }
 
     /**
@@ -142,24 +163,59 @@ public class ElectrometerServiceImpl implements IElectrometerService {
 //            Log.info("开始上传成绩,赛事ID: {}, 场次代码: {}", eventId, scoreUpload.getCcDm());
             // 5. 保存成绩数据
             int successCount = 0;
+            // 2. 根据场次代码获取分组信息
+            LambdaQueryWrapper<GameAthleteCompetitionGroup> groupWrapper = Wrappers.lambdaQuery(GameAthleteCompetitionGroup.class)
+                .eq(GameAthleteCompetitionGroup::getGroupId, scoreUpload.get(0).getCcdm())
+                .eq(GameAthleteCompetitionGroup::getEventId, eventId);
+            List<GameAthleteCompetitionGroup> groupInfo = gameAthleteCompetitionGroupMapper.selectList(groupWrapper);
+            if (groupInfo == null || groupInfo.isEmpty()) {
+                return "未找到对应的比赛分组信息";
+            }
+            Map<String, GameAthleteCompetitionGroup> groupMap = groupInfo.stream()
+                    .collect(Collectors.toMap(GameAthleteCompetitionGroup::getAthleteCode, g -> g));
+
+            // 3. 获取项目信息
+            GameEventProject project = gameEventProjectMapper.selectById(groupInfo.get(0).getProjectId());
+            if (project == null) {
+                return "未找到对应的项目信息";
+            }
             for (ElectrometerScoreUploadVo scoreItem : scoreUpload) {
-                // 2. 根据场次代码获取分组信息
-                GameAthleteCompetitionGroup groupInfo = gameAthleteCompetitionGroupMapper.selectById(scoreItem.getCcdm());
-                if (groupInfo == null) {
-                    return "未找到对应的比赛分组信息";
-                }
-                // 3. 获取项目信息
-                GameEventProject project = gameEventProjectMapper.selectById(groupInfo.getProjectId());
-                if (project == null) {
-                    return "未找到对应的项目信息";
-                }
+
                 // 4. 验证成绩数据与分组信息的一致性
-                String validationResult = validateScoreData(scoreItem, groupInfo);
+                String validationResult;
+                if (groupMap.containsKey(scoreItem.getAthleteCode())){
+                    validationResult = validateScoreData(scoreItem, groupMap.get(scoreItem.getAthleteCode()));
+                }else {
+                    validationResult = String.format("该分组未找到对应的运动员信息:%s", scoreItem.getAthleteCode());
+                }
+
                 if (!"OK".equals(validationResult)) {
                     return validationResult;
                 }
-                if (saveScoreRecord(eventId, project, scoreItem)) {
-                    successCount++;
+                // 判断是否为计时类项目,如果是则应用时间格式转换
+                if (isTimingProject(project)) {
+                    // 将成绩从字符串转换为时间格式再转换为小数
+                    String scoreStr = scoreItem.getScore() != null ? scoreItem.getScore().toString() : null;
+                    if (scoreStr != null && !scoreStr.isEmpty()) {
+                        BigDecimal convertedScore = convertTimeScoreToDecimal(scoreStr);
+                        if (convertedScore != null) {
+                            ElectrometerScoreUploadVo convertedItem = new ElectrometerScoreUploadVo();
+                            convertedItem.setAthleteCode(scoreItem.getAthleteCode());
+                            convertedItem.setTrackIndex(scoreItem.getTrackIndex());
+                            convertedItem.setGroupIndex(scoreItem.getGroupIndex());
+                            convertedItem.setRank(scoreItem.getRank());
+                            convertedItem.setScore(convertedScore);
+                            if (saveScoreRecord(eventId, project, convertedItem)) {
+                                successCount++;
+                            }
+                        } else {
+                            Log.warn("成绩格式转换失败,跳过记录: {}", scoreStr);
+                        }
+                    }
+                } else {
+                    if (saveScoreRecord(eventId, project, scoreItem)) {
+                        successCount++;
+                    }
                 }
             }
 //            Log.info("成绩上传完成,成功保存 {} 条记录", successCount);
@@ -176,26 +232,29 @@ public class ElectrometerServiceImpl implements IElectrometerService {
     public String uploadDistanceScore(Long eventId, DistanceScoreUploadVo scoreUpload) {
         try{
             //根据场次代码获取分组信息
-            GameAthleteCompetitionGroup groupInfo = gameAthleteCompetitionGroupMapper.selectById(scoreUpload.getCcdm());
+            LambdaQueryWrapper<GameAthleteCompetitionGroup> groupWrapper = Wrappers.lambdaQuery(GameAthleteCompetitionGroup.class)
+                .eq(GameAthleteCompetitionGroup::getGroupId, scoreUpload.getCcdm())
+                .eq(GameAthleteCompetitionGroup::getEventId, eventId);
+            List<GameAthleteCompetitionGroup> groupInfo = gameAthleteCompetitionGroupMapper.selectList(groupWrapper);
             if (groupInfo == null) {
                 return "未找到对应的比赛分组信息";
             }
             //根据分组信息获取项目信息
-            GameEventProject project = gameEventProjectMapper.selectById(groupInfo.getProjectId());
+            GameEventProject project = gameEventProjectMapper.selectById(groupInfo.get(0).getProjectId());
             if (project == null) {
                 return "未找到对应的项目";
             }
             //根据分组信息获取运动员信息
             List<DistanceScoreUploadVo.ScoreItem> scoreList = parseScoreData(scoreUpload.getCj());
-            GameAthlete athlete = gameAthleteMapper.selectById(groupInfo.getAthleteId());
-            if (athlete == null) {
-                return "未找到对应运动员";
-            }
+//            GameAthlete athlete = gameAthleteMapper.selectById(groupInfo.getAthleteId());
+//            if (athlete == null) {
+//                return "未找到对应运动员";
+//            }
             int successCount = saveScoreRecord(eventId, project, scoreList);
             if(successCount > 0){
-                return String.format("成绩上传成功,共保存 %d 条记录", successCount);
+                return String.format("成绩上传成功,共保存 %d 条记录", successCount);
             }else{
-                return "成绩上传失败";
+                return String.format("成绩上传成功,但符合数据库环境的保存记录数为:%d", successCount);
             }
 
         } catch (Exception e) {
@@ -313,8 +372,13 @@ public class ElectrometerServiceImpl implements IElectrometerService {
 
             if (existingScore != null) {
                 // 更新现有记录
-                existingScore.setIndividualPerformance(scoreItem.getScore());
-                existingScore.setScoreRank(scoreItem.getRank());
+                // scoreItem.getScore() 已经是 BigDecimal 类型,可以直接使用
+                if (scoreItem.getScore() != null) {
+                    existingScore.setIndividualPerformance(scoreItem.getScore());
+                }
+                if (scoreItem.getRank() != null) {
+                    existingScore.setScoreRank(scoreItem.getRank());
+                }
                 existingScore.setRemark("电计设备上传");
 
                 return gameScoreMapper.updateById(existingScore) > 0;
@@ -325,8 +389,12 @@ public class ElectrometerServiceImpl implements IElectrometerService {
                 newScore.setProjectId(project.getProjectId());
                 newScore.setAthleteId(athlete.getAthleteId());
                 newScore.setTeamId(athlete.getTeamId());
-                newScore.setIndividualPerformance(scoreItem.getScore());
-                newScore.setScoreRank(scoreItem.getRank());
+                if (scoreItem.getScore() != null) {
+                    newScore.setIndividualPerformance(scoreItem.getScore());
+                }
+                if (scoreItem.getRank() != null) {
+                    newScore.setScoreRank(scoreItem.getRank());
+                }
                 newScore.setScoreType("individual");
                 newScore.setStatus("0"); // 正常
                 newScore.setRemark("电计设备上传");
@@ -351,10 +419,11 @@ public class ElectrometerServiceImpl implements IElectrometerService {
                     .eq(GameAthlete::getEventId, eventId)
                     .eq(GameAthlete::getAthleteCode, scoreItem.getAthleteCode())
                     .eq(GameAthlete::getStatus, "0")
-                    .eq(GameAthlete::getDelFlag, "0");
+                    .eq(GameAthlete::getDelFlag, "0"); // 0-存在
                 GameAthlete athlete = gameAthleteMapper.selectOne(athleteWrapper);
 
                 if (athlete == null) {
+                    Log.warn("数据库中未找到运动员: "+scoreItem.getAthleteCode());
                     continue;
                 }
 
@@ -406,4 +475,305 @@ public class ElectrometerServiceImpl implements IElectrometerService {
             return 0;
         }
     }
+
+    /**
+     * 上传径赛成绩(电计设备)- 新格式
+     *
+     * @param eventId 赛事ID
+     * @param scoreUpload 成绩上传数据,包含cc_dm、zc、cj字段
+     * @return 上传结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String uploadAvtScore(String eventId, AvtScoreUploadVo scoreUpload) {
+        try {
+            Log.info("开始上传径赛成绩,赛事ID: {}", eventId);
+
+            // 解析cj字段中的数据
+            List<AvtScoreItem> scoreItems = parseAvtScoreData(scoreUpload.getCj());
+            if (scoreItems.isEmpty()) {
+                return "没有有效的成绩数据";
+            }
+
+            // 根据场次代码获取分组信息
+            LambdaQueryWrapper<GameAthleteCompetitionGroup> groupWrapper = Wrappers.lambdaQuery(GameAthleteCompetitionGroup.class)
+                .eq(GameAthleteCompetitionGroup::getGroupId, scoreUpload.getCcDm())
+                .eq(GameAthleteCompetitionGroup::getEventId, eventId);
+            List<GameAthleteCompetitionGroup> groupInfo = gameAthleteCompetitionGroupMapper.selectList(groupWrapper);
+            if (groupInfo == null) {
+                return "未找到对应的比赛分组信息";
+            }
+
+            // 获取项目信息
+            GameEventProject project = gameEventProjectMapper.selectById(groupInfo.get(0).getProjectId());
+            if (project == null) {
+                return "未找到对应的项目信息";
+            }
+
+            // 保存成绩数据
+            int successCount = 0;
+            for (AvtScoreItem scoreItem : scoreItems) {
+                if (saveAvtScoreRecord(Long.valueOf(eventId), project, scoreItem)) {
+                    successCount++;
+                }
+            }
+
+            Log.info("径赛成绩上传完成,成功保存"+successCount+"条记录");
+            return String.format("成绩上传成功,共保存 %d 条记录。", successCount);
+
+        } catch (Exception e) {
+            Log.error("径赛成绩上传失败", e);
+            throw new RuntimeException("径赛成绩上传失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 解析AVT成绩数据字符串
+     * 格式:场次代码,编号,名次,成绩,风速,反应时,状态,组次,道次|场次代码,编号,名次,成绩,风速,反应时,状态,组次,道次......
+     */
+    private List<AvtScoreItem> parseAvtScoreData(String scoreData) {
+        List<AvtScoreItem> scoreList = new ArrayList<>();
+
+        if (scoreData == null || scoreData.trim().isEmpty()) {
+            return scoreList;
+        }
+
+        String[] items = scoreData.split("\\|");
+        for (String item : items) {
+            if (item.trim().isEmpty()) {
+                continue;
+            }
+
+            String[] fields = item.split(",");
+            if (fields.length >= 9) {
+                try {
+                    AvtScoreItem scoreItem = new AvtScoreItem();
+                    scoreItem.setCcdm(Long.valueOf(fields[0])); // 场次代码
+                    scoreItem.setAthleteCode(fields[1]); // 编号
+                    scoreItem.setRank(fields[2].isEmpty() ? null : Integer.valueOf(fields[2])); // 名次
+                    scoreItem.setScore(fields[3].isEmpty() ? null : fields[3]); // 成绩
+                    scoreItem.setWindSpeed(fields[4].isEmpty() ? null : fields[4]); // 风速
+                    scoreItem.setReactionTime(fields[5].isEmpty() ? null : fields[5]); // 反应时
+                    scoreItem.setStatus(fields[6].isEmpty() ? null : fields[6]); // 状态
+                    scoreItem.setGroupIndex(fields[7].isEmpty() ? null : Long.valueOf(fields[7])); // 组次
+                    scoreItem.setTrackIndex(fields[8].isEmpty() ? null : Long.valueOf(fields[8])); // 道次
+
+                    scoreList.add(scoreItem);
+                } catch (Exception e) {
+                    Log.warn("解析AVT成绩数据失败: {}", item, e);
+                }
+            }
+        }
+
+        return scoreList;
+    }
+
+    /**
+     * 保存AVT成绩记录
+     */
+    private boolean saveAvtScoreRecord(Long eventId, GameEventProject project, AvtScoreItem scoreItem) {
+        try {
+            // 根据运动员编号查找运动员ID
+            LambdaQueryWrapper<GameAthlete> athleteWrapper = Wrappers.lambdaQuery(GameAthlete.class)
+                .eq(GameAthlete::getEventId, eventId)
+                .eq(GameAthlete::getAthleteCode, scoreItem.getAthleteCode())
+                .eq(GameAthlete::getStatus, "0")
+                .eq(GameAthlete::getDelFlag, "0");
+            GameAthlete athlete = gameAthleteMapper.selectOne(athleteWrapper);
+
+            if (athlete == null) {
+                Log.warn("未找到运动员: {}", scoreItem.getAthleteCode());
+                return false;
+            }
+
+            // 检查是否已存在成绩记录
+            LambdaQueryWrapper<GameScore> scoreWrapper = Wrappers.lambdaQuery(GameScore.class)
+                .eq(GameScore::getEventId, eventId)
+                .eq(GameScore::getProjectId, project.getProjectId())
+                .eq(GameScore::getAthleteId, athlete.getAthleteId());
+
+            GameScore existingScore = gameScoreMapper.selectOne(scoreWrapper);
+
+            // 处理成绩数据 - 如果是计时类项目,应用时间格式转换
+            BigDecimal scoreValue = null;
+            if (scoreItem.getScore() != null && !scoreItem.getScore().isEmpty()) {
+                // 判断是否为计时类项目
+                if (isTimingProject(project)) {
+                    // 使用统一的时间格式转换方法
+                    scoreValue = convertTimeScoreToDecimal(scoreItem.getScore());
+                } else {
+                    // 非计时类项目直接转换
+                    try {
+                        scoreValue = BigDecimal.valueOf(Double.parseDouble(scoreItem.getScore()));
+                    } catch (NumberFormatException e) {
+                        Log.warn("解析成绩失败: {}", scoreItem.getScore(), e);
+                    }
+                }
+            }
+
+            if (existingScore != null) {
+                // 更新现有记录
+                if (scoreValue != null) {
+                    existingScore.setIndividualPerformance(scoreValue);
+                }
+                if (scoreItem.getRank() != null) {
+                    existingScore.setScoreRank(scoreItem.getRank());
+                }
+                existingScore.setRemark("AVT电计设备上传");
+
+                return gameScoreMapper.updateById(existingScore) > 0;
+            } else {
+                // 创建新记录
+                GameScore newScore = new GameScore();
+                newScore.setEventId(eventId);
+                newScore.setProjectId(project.getProjectId());
+                newScore.setAthleteId(athlete.getAthleteId());
+                newScore.setTeamId(athlete.getTeamId());
+                if (scoreValue != null) {
+                    newScore.setIndividualPerformance(scoreValue);
+                }
+                if (scoreItem.getRank() != null) {
+                    newScore.setScoreRank(scoreItem.getRank());
+                }
+                newScore.setScoreType("individual");
+                newScore.setStatus("0");
+                newScore.setRemark("AVT电计设备上传");
+
+                return gameScoreMapper.insert(newScore) > 0;
+            }
+
+        } catch (Exception e) {
+            Log.error("保存AVT成绩记录失败: {}", e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 判断项目是否为计时类项目
+     * 根据项目的计算规则(scoreRule)判断,常见值如:"timing"、"计时"、"time"等
+     *
+     * @param project 项目信息
+     * @return true表示是计时类项目,false表示不是
+     */
+    private boolean isTimingProject(GameEventProject project) {
+        if (project == null || project.getScoreRule() == null) {
+            return false;
+        }
+        List<SysDictDataVo> gameScoreType = dictTypeService.selectDictDataByType("game_score_type");
+        String scoreRule = project.getScoreRule().toLowerCase();
+        Optional<String> rule = gameScoreType.stream()
+            .filter(data -> data.getDictLabel().contains("计时"))
+            .map(SysDictDataVo::getDictValue)
+            .findFirst();
+        // 判断计算规则是否为计时类(可以根据实际字典值调整)
+        return rule.isPresent() && scoreRule.equals(rule.get().toLowerCase());
+    }
+
+    /**
+     * 将各种时间格式成绩转换成小数格式存储(以秒为单位)
+     * 支持格式:
+     * - 0.111 (已经是小数格式)
+     * - 11.000 (已经是小数格式)
+     * - 11:00.000 (分:秒.毫秒格式)
+     * - 11:00:00.000 (时:分:秒.毫秒格式)
+     *
+     * @param timeScore 时间格式的成绩字符串
+     * @return 转换为秒为单位的小数值,如果解析失败返回null
+     */
+    public BigDecimal convertTimeScoreToDecimal(String timeScore) {
+        if (timeScore == null || timeScore.trim().isEmpty()) {
+            return null;
+        }
+
+        try {
+            String trimmed = timeScore.trim();
+
+            // 如果包含冒号,说明是时间格式(时:分:秒.毫秒 或 分:秒.毫秒)
+            if (trimmed.contains(":")) {
+                String[] parts = trimmed.split(":");
+                double totalSeconds = 0.0;
+
+                if (parts.length == 2) {
+                    // 格式:分:秒.毫秒 (如 11:00.000)
+                    double minutes = Double.parseDouble(parts[0]);
+                    double seconds = Double.parseDouble(parts[1]);
+                    totalSeconds = minutes * 60 + seconds;
+                } else if (parts.length == 3) {
+                    // 格式:时:分:秒.毫秒 (如 11:00:00.000)
+                    double hours = Double.parseDouble(parts[0]);
+                    double minutes = Double.parseDouble(parts[1]);
+                    double seconds = Double.parseDouble(parts[2]);
+                    totalSeconds = hours * 3600 + minutes * 60 + seconds;
+                } else {
+                    Log.warn("不支持的时间格式: {}", timeScore);
+                    return null;
+                }
+
+                return BigDecimal.valueOf(totalSeconds);
+            } else {
+                // 已经是小数格式,直接转换
+                return BigDecimal.valueOf(Double.parseDouble(trimmed));
+            }
+        } catch (NumberFormatException e) {
+            Log.warn("解析时间成绩失败: {}", timeScore, e);
+            return null;
+        }
+    }
+
+    /**
+     * 将小数格式的成绩转换成时间格式显示(时:分:秒.毫秒)
+     * 格式:xx:xx:xx.nn (时:分:秒.毫秒)
+     *
+     * @param decimalScore 以秒为单位的小数值
+     * @return 时间格式字符串,如 "01:23:45.678"
+     */
+    public String convertDecimalToTimeScore(BigDecimal decimalScore) {
+        if (decimalScore == null) {
+            return null;
+        }
+
+        try {
+            // 计算小时
+            BigDecimal hoursBigDecimal = decimalScore.divide(BigDecimal.valueOf(3600), 0, java.math.RoundingMode.DOWN);
+            int hours = hoursBigDecimal.intValue();
+            BigDecimal remaining = decimalScore.remainder(BigDecimal.valueOf(3600));
+
+            // 计算分钟
+            BigDecimal minutesBigDecimal = remaining.divide(BigDecimal.valueOf(60), 0, java.math.RoundingMode.DOWN);
+            int minutes = minutesBigDecimal.intValue();
+            remaining = remaining.remainder(BigDecimal.valueOf(60));
+
+            // 秒和毫秒
+            int seconds = remaining.intValue();
+            // 计算毫秒:取小数部分乘以1000并四舍五入
+            BigDecimal millisecondsBigDecimal = remaining.subtract(BigDecimal.valueOf(seconds))
+                    .multiply(BigDecimal.valueOf(1000))
+                    .setScale(0, java.math.RoundingMode.HALF_UP);
+            int milliseconds = millisecondsBigDecimal.intValue();
+
+            // 处理毫秒溢出(理论上不应该发生,但保险起见)
+            if (milliseconds >= 1000) {
+                seconds += milliseconds / 1000;
+                milliseconds = milliseconds % 1000;
+            }
+
+            // 处理秒溢出(理论上不应该发生,但保险起见)
+            if (seconds >= 60) {
+                minutes += seconds / 60;
+                seconds = seconds % 60;
+            }
+
+            // 处理分钟溢出(理论上不应该发生,但保险起见)
+            if (minutes >= 60) {
+                hours += minutes / 60;
+                minutes = minutes % 60;
+            }
+
+            // 格式化输出:时:分:秒.毫秒
+            return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
+        } catch (Exception e) {
+            Log.warn("转换小数为时间格式失败: {}", String.valueOf(decimalScore), e);
+            return null;
+        }
+    }
 }

+ 34 - 0
ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/GameTeamMapper.xml

@@ -12,4 +12,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
         AND del_flag = '0'
     </update>
+
+    <!-- 查询队伍列表并包含分组名 -->
+    <select id="selectVoListWithGroupName" resultType="org.dromara.system.domain.vo.GameTeamVo">
+        SELECT 
+            gt.team_id,
+            gt.event_id,
+            gt.rg_id,
+            grg.rg_name as rgName,
+            gt.team_name,
+            gt.team_code,
+            gt.leader,
+            gt.athlete_value as athleteValue,
+            gt.project_value as projectValue,
+            gt.athlete_num as athleteNum,
+            gt.number_range as numberRange,
+            gt.team_describe as teamDescribe,
+            gt.status,
+            gt.remark,
+            gt.create_time as createTime,
+            gt.update_time as updateTime
+        FROM game_team gt
+        LEFT JOIN game_rank_group grg ON gt.rg_id = grg.rg_id
+        WHERE gt.del_flag = '0' and gt.status = '0'
+        <if test="eventId != null">
+            AND gt.event_id = #{eventId}
+        </if>
+        <if test="teamName != null and teamName != ''">
+            AND gt.team_name LIKE CONCAT('%', #{teamName}, '%')
+        </if>
+        <if test="teamCode != null and teamCode != ''">
+            AND gt.team_code LIKE CONCAT('%', #{teamCode}, '%')
+        </if>
+        ORDER BY gt.team_id DESC
+    </select>
 </mapper>