xujunwei пре 5 месеци
родитељ
комит
3b353b38c2
34 измењених фајлова са 743 додато и 130 уклоњено
  1. 22 11
      src/main/java/com/mrxu/admin/controller/yolo/YoloClassController.java
  2. 87 0
      src/main/java/com/mrxu/admin/controller/yolo/YoloGroupController.java
  3. 6 2
      src/main/java/com/mrxu/admin/controller/yolo/YoloImageController.java
  4. 1 1
      src/main/java/com/mrxu/admin/controller/yolo/YoloMarkController.java
  5. 0 2
      src/main/java/com/mrxu/admin/controller/yolo/YoloStreamController.java
  6. 4 0
      src/main/java/com/mrxu/admin/controller/yolo/YoloTrainController.java
  7. 6 5
      src/main/java/com/mrxu/yolo/entity/YoloClass.java
  8. 51 0
      src/main/java/com/mrxu/yolo/entity/YoloGroup.java
  9. 10 5
      src/main/java/com/mrxu/yolo/entity/YoloImage.java
  10. 9 0
      src/main/java/com/mrxu/yolo/entity/YoloTrain.java
  11. 2 3
      src/main/java/com/mrxu/yolo/mapper/YoloClassMapper.java
  12. 19 0
      src/main/java/com/mrxu/yolo/mapper/YoloGroupMapper.java
  13. 2 3
      src/main/java/com/mrxu/yolo/mapper/YoloImageMapper.java
  14. 2 3
      src/main/java/com/mrxu/yolo/mapper/YoloTrainMapper.java
  15. 16 0
      src/main/java/com/mrxu/yolo/python/YoloBusinessFeignClient.java
  16. 5 3
      src/main/java/com/mrxu/yolo/query/YoloClassDto.java
  17. 22 0
      src/main/java/com/mrxu/yolo/query/YoloGroupDto.java
  18. 3 0
      src/main/java/com/mrxu/yolo/query/YoloImageDto.java
  19. 5 3
      src/main/java/com/mrxu/yolo/query/YoloTrainDto.java
  20. 5 3
      src/main/java/com/mrxu/yolo/service/YoloClassService.java
  21. 33 0
      src/main/java/com/mrxu/yolo/service/YoloGroupService.java
  22. 15 1
      src/main/java/com/mrxu/yolo/service/YoloImageService.java
  23. 5 1
      src/main/java/com/mrxu/yolo/service/YoloStreamService.java
  24. 0 1
      src/main/java/com/mrxu/yolo/service/YoloStreamTaskService.java
  25. 9 6
      src/main/java/com/mrxu/yolo/service/YoloTrainService.java
  26. 3 0
      src/main/resources/mapper/yolo/YoloClassMapper.xml
  27. 32 0
      src/main/resources/mapper/yolo/YoloGroupMapper.xml
  28. 12 8
      src/main/resources/mapper/yolo/YoloImageMapper.xml
  29. 12 8
      src/main/resources/mapper/yolo/YoloTrainMapper.xml
  30. 16 12
      src/main/resources/static/business/yoloMark.js
  31. 108 36
      src/main/resources/templates/yolo/class.html
  32. 163 0
      src/main/resources/templates/yolo/group.html
  33. 35 9
      src/main/resources/templates/yolo/image.html
  34. 23 4
      src/main/resources/templates/yolo/train.html

+ 22 - 11
src/main/java/com/mrxu/admin/controller/yolo/YoloClassController.java

@@ -1,9 +1,15 @@
 package com.mrxu.admin.controller.yolo;
 
-import javax.validation.Valid;
-import java.util.List;
-
-import com.mrxu.framework.common.MrxuConst;
+import com.mrxu.admin.controller.AdminBaseController;
+import com.mrxu.framework.boot.bean.LayuiPage;
+import com.mrxu.framework.boot.bean.PageResult;
+import com.mrxu.framework.boot.bean.ResponseObj;
+import com.mrxu.yolo.entity.YoloClass;
+import com.mrxu.yolo.entity.YoloGroup;
+import com.mrxu.yolo.query.YoloClassDto;
+import com.mrxu.yolo.query.YoloGroupDto;
+import com.mrxu.yolo.service.YoloClassService;
+import com.mrxu.yolo.service.YoloGroupService;
 import io.swagger.annotations.Api;
 import lombok.RequiredArgsConstructor;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -14,13 +20,8 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
-import com.mrxu.admin.controller.AdminBaseController;
-import com.mrxu.framework.boot.bean.LayuiPage;
-import com.mrxu.framework.boot.bean.PageResult;
-import com.mrxu.framework.boot.bean.ResponseObj;
-import com.mrxu.yolo.service.YoloClassService;
-import com.mrxu.yolo.entity.YoloClass;
-import com.mrxu.yolo.query.YoloClassDto;
+import javax.validation.Valid;
+import java.util.List;
 
 @Api(tags = "分类管理")
 @Controller
@@ -29,6 +30,8 @@ import com.mrxu.yolo.query.YoloClassDto;
 public class YoloClassController extends AdminBaseController {
 
 	private final YoloClassService yoloClassService;
+
+	private final YoloGroupService groupService;
 	
 	@RequiresPermissions("yolo:class:read")
 	@RequestMapping("index.html")
@@ -44,6 +47,14 @@ public class YoloClassController extends AdminBaseController {
 		return renderLayuiPage(rs);
 	}
 
+	@RequiresPermissions("yolo:class:read")
+	@ResponseBody
+	@RequestMapping("/groupPage.json")
+	public LayuiPage<YoloGroup> groupPage(YoloGroupDto queryDto) {
+		PageResult<YoloGroup> rs = groupService.page(getTenantId(),queryDto);
+		return renderLayuiPage(rs);
+	}
+
 	@RequiresPermissions("yolo:class:read")
 	@ResponseBody
 	@RequestMapping("/getById.json")

+ 87 - 0
src/main/java/com/mrxu/admin/controller/yolo/YoloGroupController.java

@@ -0,0 +1,87 @@
+package com.mrxu.admin.controller.yolo;
+
+import com.mrxu.admin.controller.AdminBaseController;
+import com.mrxu.framework.boot.bean.LayuiPage;
+import com.mrxu.framework.boot.bean.PageResult;
+import com.mrxu.framework.boot.bean.ResponseObj;
+import com.mrxu.framework.common.MrxuConst;
+import com.mrxu.yolo.entity.YoloGroup;
+import com.mrxu.yolo.query.YoloGroupDto;
+import com.mrxu.yolo.service.YoloGroupService;
+import io.swagger.annotations.Api;
+import lombok.RequiredArgsConstructor;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@Api(tags = "训练分组管理")
+@Controller
+@RequestMapping("/yolo/group")
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class YoloGroupController extends AdminBaseController {
+
+	private final YoloGroupService groupService;
+	
+	@RequiresPermissions("yolo:group:read")
+	@RequestMapping("index.html")
+	public String index(Model model) {
+		return "yolo/group.html";
+	}
+	
+	@RequiresPermissions("yolo:group:read")
+	@ResponseBody
+	@RequestMapping("/page.json")
+	public LayuiPage<YoloGroup> page(YoloGroupDto queryDto) {
+		PageResult<YoloGroup> rs = groupService.page(getTenantId(),queryDto);
+		return renderLayuiPage(rs);
+	}
+
+	@RequiresPermissions("yolo:group:read")
+	@ResponseBody
+	@RequestMapping("/getById.json")
+	public ResponseObj<YoloGroup> getById(Integer id) {
+		return success(groupService.getById(getTenantId(),id));
+	}
+
+	@RequiresPermissions("yolo:group:update")
+	@ResponseBody
+	@RequestMapping("/save.json")
+	public ResponseObj<Boolean> save(@Valid @RequestBody YoloGroup bean) {
+		if(bean.getStatus() == null) {
+			bean.setStatus(MrxuConst.disable);
+		}
+		return success(groupService.saveOrUpdate(getTenantId(),bean,getUsername()));
+	}
+
+	@RequiresPermissions("yolo:group:update")
+	@ResponseBody
+	@RequestMapping("/updateStatus.json")
+	public ResponseObj<Boolean> updateStatus(Integer id, Integer status) {
+		YoloGroup bean = new YoloGroup();
+		bean.setId(id);
+		bean.setStatus(status);
+		return success(groupService.saveOrUpdate(getTenantId(),bean,getUsername()));
+	}
+
+	@RequiresPermissions("yolo:group:remove")
+	@ResponseBody
+	@RequestMapping("/remove.json")
+	public ResponseObj<Boolean> remove(Integer id) {
+		return success(groupService.remove(getTenantId(),id));
+	}
+
+	@RequiresPermissions("yolo:group:remove")
+	@ResponseBody
+	@RequestMapping("/removeBatch.json")
+	public ResponseObj<Boolean> removeBatch(@RequestBody List<Integer> ids) { 
+		return success(groupService.removeBatch(getTenantId(),ids));
+	}
+
+}

+ 6 - 2
src/main/java/com/mrxu/admin/controller/yolo/YoloImageController.java

@@ -6,6 +6,7 @@ import com.mrxu.framework.boot.bean.PageResult;
 import com.mrxu.framework.boot.bean.ResponseObj;
 import com.mrxu.yolo.entity.YoloImage;
 import com.mrxu.yolo.query.YoloImageDto;
+import com.mrxu.yolo.service.YoloGroupService;
 import com.mrxu.yolo.service.YoloImageService;
 import io.swagger.annotations.Api;
 import lombok.RequiredArgsConstructor;
@@ -32,11 +33,14 @@ import java.util.List;
 @RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class YoloImageController extends AdminBaseController {
 
+	private final YoloGroupService groupService;
+
 	private final YoloImageService yoloImageService;
 	
 	@RequiresPermissions("yolo:image:read")
 	@RequestMapping("index.html")
 	public String index(Model model) {
+		model.addAttribute("groupList",groupService.list(getTenantId()));
 		return "yolo/image.html";
 	}
 	
@@ -68,8 +72,8 @@ public class YoloImageController extends AdminBaseController {
 	@RequiresPermissions("yolo:image:update")
 	@ResponseBody
 	@RequestMapping("/uploadImages.json")
-	public ResponseObj<Object> uploadImages(@RequestParam("files") List<MultipartFile> files) {
-		yoloImageService.uploadImages(getTenantId(),files,getUsername());
+	public ResponseObj<Object> uploadImages(@RequestParam("yoloGroup") Integer yoloGroup,@RequestParam("files") List<MultipartFile> files) {
+		yoloImageService.uploadImages(getTenantId(),yoloGroup,files,getUsername());
 		return success();
 	}
 

+ 1 - 1
src/main/java/com/mrxu/admin/controller/yolo/YoloMarkController.java

@@ -44,7 +44,7 @@ public class YoloMarkController extends AdminBaseController {
     @ResponseBody
     @GetMapping("/classList.json")
     public ResponseObj<List<YoloClass>> classList() {
-        return success(yoloClassService.list(getTenantId()));
+        return success(yoloClassService.list(getTenantId(),null));
     }
 
     @RequiresPermissions("yolo:mark:read")

+ 0 - 2
src/main/java/com/mrxu/admin/controller/yolo/YoloStreamController.java

@@ -3,8 +3,6 @@ package com.mrxu.admin.controller.yolo;
 import com.mrxu.admin.controller.AdminBaseController;
 import com.mrxu.framework.boot.bean.ResponseObj;
 import com.mrxu.yolo.python.request.StreamRequest;
-import com.mrxu.yolo.query.YoloPredictDto;
-import com.mrxu.yolo.service.YoloPredictService;
 import com.mrxu.yolo.service.YoloStreamService;
 import com.mrxu.yolo.service.YoloTrainService;
 import io.swagger.annotations.Api;

+ 4 - 0
src/main/java/com/mrxu/admin/controller/yolo/YoloTrainController.java

@@ -7,6 +7,7 @@ import com.mrxu.framework.boot.bean.ResponseObj;
 import com.mrxu.yolo.entity.YoloTrain;
 import com.mrxu.yolo.enums.TrainStatus;
 import com.mrxu.yolo.query.YoloTrainDto;
+import com.mrxu.yolo.service.YoloGroupService;
 import com.mrxu.yolo.service.YoloImageService;
 import com.mrxu.yolo.service.YoloTrainService;
 import io.swagger.annotations.Api;
@@ -31,6 +32,8 @@ import javax.validation.Valid;
 @RequiredArgsConstructor(onConstructor = @__(@Autowired))
 public class YoloTrainController extends AdminBaseController {
 
+	private final YoloGroupService groupService;
+
 	private final YoloTrainService trainService;
 
 	private final YoloImageService imageService;
@@ -38,6 +41,7 @@ public class YoloTrainController extends AdminBaseController {
 	@RequiresPermissions("yolo:train:read")
 	@RequestMapping("index.html")
 	public String index(Model model) {
+		model.addAttribute("groupList",groupService.list(getTenantId()));
 		model.addAttribute("imageSrcPre", "/yolo/image/image?path="+imageService.getModelPath(getTenantId()));
 		return "yolo/train.html";
 	}

+ 6 - 5
src/main/java/com/mrxu/yolo/entity/YoloClass.java

@@ -1,18 +1,15 @@
 package com.mrxu.yolo.entity;
 
-import javax.validation.constraints.Size;
-
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.mrxu.framework.boot.bean.BaseEntity;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.mrxu.framework.boot.bean.BaseEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
-import java.math.BigDecimal;
+import javax.validation.constraints.Size;
 import java.util.Date;
 
 @TableName("yolo_class")
@@ -28,6 +25,10 @@ public class YoloClass extends BaseEntity {
 	@Size(max=32)
 	private String tenantId;
 
+	@ApiModelProperty(value = "分组id")
+	@TableField("yolo_group")
+	private Integer yoloGroup;
+
 	@ApiModelProperty(value = "名称")
 	@TableField("name")
 	@Size(max=64)

+ 51 - 0
src/main/java/com/mrxu/yolo/entity/YoloGroup.java

@@ -0,0 +1,51 @@
+package com.mrxu.yolo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.mrxu.framework.boot.bean.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.Size;
+import java.util.Date;
+
+@TableName("yolo_group")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel("训练分组")
+public class YoloGroup extends BaseEntity {
+
+    private static final long serialVersionUID = -1L;
+
+	@ApiModelProperty(value = "租户id")
+	@TableField("tenant_id")
+	@Size(max=32)
+	private String tenantId;
+
+	@ApiModelProperty(value = "名称")
+	@TableField("name")
+	@Size(max=64)
+	private String name;
+
+	@ApiModelProperty(value = "排序字段")
+	@TableField("sort_number")
+	private Integer sortNumber;
+
+	@ApiModelProperty(value = "是否启用,0否,1是")
+	@TableField("status")
+	private Integer status;
+
+	@ApiModelProperty(value = "更新时间")
+	@TableField("update_time")
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+	private Date updateTime;
+
+	@ApiModelProperty(value = "更新人")
+	@TableField("update_person")
+	@Size(max=32)
+	private String updatePerson;
+
+}

+ 10 - 5
src/main/java/com/mrxu/yolo/entity/YoloImage.java

@@ -1,14 +1,11 @@
 package com.mrxu.yolo.entity;
 
-import javax.validation.constraints.Size;
-
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.mrxu.framework.boot.bean.BaseEntity;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.mrxu.framework.boot.bean.BaseEntity;
 import com.mrxu.framework.common.util.StrFunc;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -16,7 +13,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.SneakyThrows;
 
-import java.math.BigDecimal;
+import javax.validation.constraints.Size;
 import java.net.URLEncoder;
 import java.util.Date;
 
@@ -33,6 +30,10 @@ public class YoloImage extends BaseEntity {
 	@Size(max=32)
 	private String tenantId;
 
+	@ApiModelProperty(value = "分组id")
+	@TableField("yolo_group")
+	private Integer yoloGroup;
+
 	@ApiModelProperty(value = "名称")
 	@TableField("name")
 	@Size(max=64)
@@ -62,6 +63,10 @@ public class YoloImage extends BaseEntity {
 	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
 	private Date updateTime;
 
+	@ApiModelProperty(value = "分组名")
+	@TableField(exist=false)
+	private String groupName;
+
 	public JSONArray getBoxes() {
 		if(StrFunc.isEmpty(markInfo)) {
 			return null;

+ 9 - 0
src/main/java/com/mrxu/yolo/entity/YoloTrain.java

@@ -28,6 +28,10 @@ public class YoloTrain extends BaseEntity {
 	@Size(max=32)
 	private String tenantId;
 
+	@ApiModelProperty(value = "分组id")
+	@TableField("yolo_group")
+	private Integer yoloGroup;
+
 	@ApiModelProperty(value = "名称")
 	@TableField("name")
 	@Size(max=64)
@@ -108,6 +112,11 @@ public class YoloTrain extends BaseEntity {
 	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
 	private Date updateTime;
 
+
+	@ApiModelProperty(value = "分组名")
+	@TableField(exist=false)
+	private String groupName;
+
 	public String getTrainStatusCaption() {
 		return TrainStatus.getCaption(trainStatus);
 	}

+ 2 - 3
src/main/java/com/mrxu/yolo/mapper/YoloClassMapper.java

@@ -1,12 +1,11 @@
 package com.mrxu.yolo.mapper;
 
-import java.util.List;
-
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
 import com.mrxu.yolo.entity.YoloClass;
 import com.mrxu.yolo.query.YoloClassDto;
 
+import java.util.List;
+
 /**
  * 分类Mapper接口
  */

+ 19 - 0
src/main/java/com/mrxu/yolo/mapper/YoloGroupMapper.java

@@ -0,0 +1,19 @@
+package com.mrxu.yolo.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.mrxu.yolo.entity.YoloGroup;
+import com.mrxu.yolo.query.YoloGroupDto;
+
+import java.util.List;
+
+/**
+ * 训练分组Mapper接口
+ */
+public interface YoloGroupMapper extends BaseMapper<YoloGroup> {
+
+	/**
+	 * 分页查询
+	 */
+	List<YoloGroup> page(YoloGroupDto page);
+
+}

+ 2 - 3
src/main/java/com/mrxu/yolo/mapper/YoloImageMapper.java

@@ -1,12 +1,11 @@
 package com.mrxu.yolo.mapper;
 
-import java.util.List;
-
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
 import com.mrxu.yolo.entity.YoloImage;
 import com.mrxu.yolo.query.YoloImageDto;
 
+import java.util.List;
+
 /**
  * 训练图片Mapper接口
  */

+ 2 - 3
src/main/java/com/mrxu/yolo/mapper/YoloTrainMapper.java

@@ -1,12 +1,11 @@
 package com.mrxu.yolo.mapper;
 
-import java.util.List;
-
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
 import com.mrxu.yolo.entity.YoloTrain;
 import com.mrxu.yolo.query.YoloTrainDto;
 
+import java.util.List;
+
 /**
  * 训练模型Mapper接口
  */

+ 16 - 0
src/main/java/com/mrxu/yolo/python/YoloBusinessFeignClient.java

@@ -0,0 +1,16 @@
+package com.mrxu.yolo.python;
+
+import com.mrxu.framework.boot.bean.ResponseObj;
+import com.mrxu.yolo.python.request.StreamRequest;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient(name = "YoloBusinessFeignClient",
+        url = "${yolo.businessUrl:http://127.0.0.1:8001/yolo/business}")
+public interface YoloBusinessFeignClient {
+
+    @PostMapping("/stream")
+    ResponseObj<String> stream(@RequestBody StreamRequest request);
+
+}

+ 5 - 3
src/main/java/com/mrxu/yolo/query/YoloClassDto.java

@@ -1,12 +1,11 @@
 package com.mrxu.yolo.query;
 
+import com.mrxu.framework.boot.bean.PageParam;
+import com.mrxu.yolo.entity.YoloClass;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
-import com.mrxu.framework.boot.bean.PageParam;
-import com.mrxu.yolo.entity.YoloClass;
-
 @ApiModel("分类查询参数")
 @Data
 public class YoloClassDto extends PageParam<YoloClass> {
@@ -17,4 +16,7 @@ public class YoloClassDto extends PageParam<YoloClass> {
     @ApiModelProperty(value = "名称")
     private String name;
 
+    @ApiModelProperty(value = "分组ID")
+    private Integer yoloGroup;
+
 }

+ 22 - 0
src/main/java/com/mrxu/yolo/query/YoloGroupDto.java

@@ -0,0 +1,22 @@
+package com.mrxu.yolo.query;
+
+import com.mrxu.framework.boot.bean.PageParam;
+import com.mrxu.yolo.entity.YoloGroup;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel("训练分组查询参数")
+@Data
+public class YoloGroupDto extends PageParam<YoloGroup> {
+
+    @ApiModelProperty(value = "租户id")
+    private String tenantId;
+
+    @ApiModelProperty(value = "名称")
+    private String name;
+
+    @ApiModelProperty(value = "状态OO")
+    private Integer status;
+
+}

+ 3 - 0
src/main/java/com/mrxu/yolo/query/YoloImageDto.java

@@ -13,6 +13,9 @@ public class YoloImageDto extends PageParam<YoloImage> {
     @ApiModelProperty(value = "租户id")
     private String tenantId;
 
+    @ApiModelProperty(value = "分组id")
+    private Integer yoloGroup;
+
     @ApiModelProperty(value = "名称")
     private String name;
 

+ 5 - 3
src/main/java/com/mrxu/yolo/query/YoloTrainDto.java

@@ -1,12 +1,11 @@
 package com.mrxu.yolo.query;
 
+import com.mrxu.framework.boot.bean.PageParam;
+import com.mrxu.yolo.entity.YoloTrain;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
-import com.mrxu.framework.boot.bean.PageParam;
-import com.mrxu.yolo.entity.YoloTrain;
-
 @ApiModel("训练模型查询参数")
 @Data
 public class YoloTrainDto extends PageParam<YoloTrain> {
@@ -14,6 +13,9 @@ public class YoloTrainDto extends PageParam<YoloTrain> {
     @ApiModelProperty(value = "租户id")
     private String tenantId;
 
+    @ApiModelProperty(value = "分组id")
+    private Integer yoloGroup;
+
     @ApiModelProperty(value = "名称")
     private String name;
 

+ 5 - 3
src/main/java/com/mrxu/yolo/service/YoloClassService.java

@@ -23,22 +23,24 @@ public class YoloClassService extends BaseService<YoloClassMapper,YoloClass> {
 		return new PageResult(list,dto);
 	}
 
-	public List<YoloClass> list(String tenantId) {
+	public List<YoloClass> list(String tenantId,Integer yoloGroup) {
 		LambdaQueryWrapper<YoloClass> qw = new LambdaQueryWrapper();
 		qw.eq(YoloClass :: getTenantId,tenantId);
+		qw.eq(yoloGroup != null,YoloClass :: getYoloGroup,yoloGroup);
 		qw.orderByAsc(YoloClass :: getIndexNumber);
 		return list(qw);
 	}
 
-	public YoloClass getByIndex(String tenantId,Integer index) {
+	public YoloClass getByIndex(String tenantId,Integer yoloGroup,Integer index) {
 		LambdaQueryWrapper<YoloClass> qw = new LambdaQueryWrapper();
 		qw.eq(YoloClass :: getTenantId,tenantId);
+		qw.eq(YoloClass :: getYoloGroup,yoloGroup);
 		qw.eq(YoloClass :: getIndexNumber,index);
 		return getOne(qw);
 	}
 
 	public boolean saveOrUpdate(String tenantId, YoloClass bean, String username) {
-		YoloClass existBean = getByIndex(tenantId,bean.getIndexNumber());
+		YoloClass existBean = getByIndex(tenantId,bean.getYoloGroup(),bean.getIndexNumber());
 		MrxuAssert.isTrue(existBean == null || existBean.getId().equals(bean.getId()),"索引:"+bean.getIndexNumber()+"已存在");
 		return super.saveOrUpdate(tenantId,bean,username);
 	}

+ 33 - 0
src/main/java/com/mrxu/yolo/service/YoloGroupService.java

@@ -0,0 +1,33 @@
+package com.mrxu.yolo.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.mrxu.framework.boot.bean.PageResult;
+import com.mrxu.framework.boot.web.BaseService;
+import com.mrxu.framework.common.MrxuConst;
+import com.mrxu.yolo.entity.YoloGroup;
+import com.mrxu.yolo.mapper.YoloGroupMapper;
+import com.mrxu.yolo.query.YoloGroupDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class YoloGroupService extends BaseService<YoloGroupMapper,YoloGroup> {
+
+	public PageResult<YoloGroup> page(String tenantId,YoloGroupDto dto) {
+		dto.setTenantId(tenantId);
+		List<YoloGroup> list = baseMapper.page(dto);
+		return new PageResult(list,dto);
+	}
+
+	public List<YoloGroup> list(String tenantId) {
+		LambdaQueryWrapper<YoloGroup> qw = new LambdaQueryWrapper<>();
+		qw.eq(YoloGroup :: getTenantId,tenantId);
+		qw.eq(YoloGroup :: getStatus, MrxuConst.enable);
+		return baseMapper.selectList(qw);
+	}
+
+}

+ 15 - 1
src/main/java/com/mrxu/yolo/service/YoloImageService.java

@@ -56,6 +56,19 @@ public class YoloImageService extends BaseService<YoloImageMapper,YoloImage> {
 		return getOne(qw);
 	}
 
+	public List<String> getNameList(String tenantId,Integer yoloGroup) {
+		LambdaQueryWrapper<YoloImage> qw = new LambdaQueryWrapper();
+		qw.eq(YoloImage :: getTenantId,tenantId);
+		qw.eq(YoloImage :: getYoloGroup,yoloGroup);
+		qw.select(YoloImage :: getName);
+		List<YoloImage> list = list(qw);
+		List<String> result = new ArrayList<>();
+		for(YoloImage bean : list) {
+			result.add(bean.getName());
+		}
+		return result;
+	}
+
 	public Map<String,YoloImage> getAllImages(String tenantId) {
 		LambdaQueryWrapper<YoloImage> qw = new LambdaQueryWrapper<>();
 		qw.eq(YoloImage :: getTenantId,tenantId);
@@ -68,7 +81,7 @@ public class YoloImageService extends BaseService<YoloImageMapper,YoloImage> {
 	}
 
 	@SneakyThrows
-	public void uploadImages(String tenantId, List<MultipartFile> files,String username) {
+	public void uploadImages(String tenantId,Integer yoloGroup, List<MultipartFile> files,String username) {
 		// 确保目录存在
 		FileFunc.surePathExists(getImagesPath(tenantId));
 		for(MultipartFile file : files) {
@@ -82,6 +95,7 @@ public class YoloImageService extends BaseService<YoloImageMapper,YoloImage> {
 			// 保存数据库
 			YoloImage bean = new YoloImage();
 			bean.setTenantId(tenantId);
+			bean.setYoloGroup(yoloGroup);
 			bean.setName(file.getOriginalFilename());
 			bean.setPath(path);
 			bean.setMarked(MrxuConst.disable);

+ 5 - 1
src/main/java/com/mrxu/yolo/service/YoloStreamService.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.mrxu.framework.boot.bean.ResponseObj;
 import com.mrxu.framework.common.util.BaseCode;
 import com.mrxu.framework.common.util.MrxuAssert;
+import com.mrxu.yolo.python.YoloBusinessFeignClient;
 import com.mrxu.yolo.python.YoloV12FeignClient;
 import com.mrxu.yolo.python.request.StreamRequest;
 import lombok.SneakyThrows;
@@ -19,10 +20,13 @@ public class YoloStreamService {
     @Autowired
     private YoloV12FeignClient yoloV12FeignClient;
 
+    @Autowired
+    private YoloBusinessFeignClient yoloFeignClient;
+
     @SneakyThrows
     public void doPredict(String tenantId, StreamRequest dto, String username) {
         log.info("租户:{}用户:{}执行实时视频推理参数:{}",tenantId,username, JSONObject.toJSONString(dto));
-        ResponseObj<String> response = yoloV12FeignClient.stream(dto);
+        ResponseObj<String> response = yoloFeignClient.stream(dto);
         log.info("推理请求返回结果:{}", JSONObject.toJSONString(response));
         MrxuAssert.isTrue(BaseCode.OK.getCode() == response.getCode(),response.getMsg());
     }

+ 0 - 1
src/main/java/com/mrxu/yolo/service/YoloStreamTaskService.java

@@ -5,7 +5,6 @@ import com.mrxu.framework.boot.bean.LayuiPage;
 import com.mrxu.framework.boot.bean.ResponseObj;
 import com.mrxu.framework.common.util.BaseCode;
 import com.mrxu.framework.common.util.MrxuAssert;
-import com.mrxu.yolo.entity.YoloClass;
 import com.mrxu.yolo.python.YoloV12FeignClient;
 import com.mrxu.yolo.python.response.StreamTaskResponse;
 import lombok.extern.slf4j.Slf4j;

+ 9 - 6
src/main/java/com/mrxu/yolo/service/YoloTrainService.java

@@ -181,18 +181,20 @@ public class YoloTrainService extends BaseService<YoloTrainMapper,YoloTrain> {
 		// 验证是否有任务正在训练
 		YoloTrain training = getTraining(tenantId);
 		MrxuAssert.isTrue(training == null,"当前有训练任务正在执行中");
+		YoloTrain train = getById(tenantId,id);
 
 		// 1 生成data.yaml文件
-		createDataYaml(tenantId);
+		createDataYaml(tenantId,train.getYoloGroup());
 
 		// 2 验证图片是否已经完成标记
 		YoloImageDto dto = new YoloImageDto();
 		dto.setMarked(MrxuConst.disable);
+		dto.setYoloGroup(train.getYoloGroup());
 		PageResult<YoloImage> page = imageService.page(tenantId,dto);
 		MrxuAssert.isTrue(page.getCount() == MrxuConst.zero,"还有"+page.getCount()+"张图片未标记,请先完成标记");
 
 		// 3 创建训练、验证和测试数据集
-		createTrainFile(tenantId);
+		createTrainFile(tenantId,train.getYoloGroup());
 
 		// 4 启动训练
 		YoloTrainService proxy = applicationContext.getBean(YoloTrainService.class);
@@ -267,9 +269,10 @@ public class YoloTrainService extends BaseService<YoloTrainMapper,YoloTrain> {
 
 	// 3 生成训练、验证和测试数据集 验证:20% 测试:10%
 	@SneakyThrows
-	private void createTrainFile(String tenantId) {
+	private void createTrainFile(String tenantId,Integer yoloGroup) {
 		// 获取所有图片
-		List<String> fileNames = FileFunc.getFileNamesFromFolder(imageService.getImagesPath(tenantId));
+		List<String> fileNames = imageService.getNameList(tenantId,yoloGroup);
+		// List<String> fileNames = FileFunc.getFileNamesFromFolder(imageService.getImagesPath(tenantId));
 		MrxuAssert.isTrue(fileNames.size()>= 10,"图片数量需要大于10");
 		// 获取所有标记信息
 		Map<String, String> markMap = FileFunc.readTxtFilesToMap(imageService.getLabelsPath(tenantId));
@@ -332,8 +335,8 @@ public class YoloTrainService extends BaseService<YoloTrainMapper,YoloTrain> {
 	}
 
 	// 生成data.yaml文件
-	private String createDataYaml(String tenantId) {
-		List<YoloClass> classes = classService.list(tenantId);
+	private String createDataYaml(String tenantId,Integer yoloGroup) {
+		List<YoloClass> classes = classService.list(tenantId,yoloGroup);
 		MrxuAssert.isTrue(classes.size()> MrxuConst.zero,"请先标记分类");
 		JSONArray names = new JSONArray();
 		for(int i=0;i<classes.size();i++) {

+ 3 - 0
src/main/resources/mapper/yolo/YoloClassMapper.xml

@@ -15,6 +15,9 @@
             <if test="tenantId!=null and tenantId!=''">
                 AND tenant_id = #{tenantId}
             </if>
+            <if test="yoloGroup!=null">
+                AND yolo_group = #{yoloGroup}
+            </if>
             <if test="name!=null and name!='' ">
                 AND name LIKE CONCAT('%', #{name}, '%')
             </if>

+ 32 - 0
src/main/resources/mapper/yolo/YoloGroupMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
+<mapper namespace="com.mrxu.yolo.mapper.YoloGroupMapper">
+
+    <!-- 关联查询sql -->
+    <sql id="relSelect">
+        SELECT *
+        FROM yolo_group
+    </sql>
+
+    <!-- 分页查询 -->
+    <select id="page" resultType="com.mrxu.yolo.entity.YoloGroup">
+        <include refid="relSelect"></include>
+        <where>
+            <if test="tenantId!=null and tenantId!=''">
+                AND tenant_id = #{tenantId}
+            </if>
+            <if test="name!=null and name!='' ">
+                AND name LIKE CONCAT('%', #{name}, '%')
+            </if>
+            <if test="startTime!=null">
+            AND create_time <![CDATA[ >= ]]> #{startTime,jdbcType=VARCHAR}
+            </if>
+            <if test="endTime!=null">
+                AND create_time <![CDATA[ < ]]> #{endTime,jdbcType=VARCHAR}
+            </if>
+            ORDER BY sort_number ASC
+            <!-- AND deleted = 0 -->
+        </where>
+    </select>
+
+</mapper>

+ 12 - 8
src/main/resources/mapper/yolo/YoloImageMapper.xml

@@ -4,8 +4,9 @@
 
     <!-- 关联查询sql -->
     <sql id="relSelect">
-        SELECT *
-        FROM yolo_image
+        SELECT yi.*,yg.name AS groupName
+        FROM yolo_image yi
+        LEFT JOIN yolo_group yg ON yi.yolo_group = yg.id
     </sql>
 
     <!-- 分页查询 -->
@@ -13,21 +14,24 @@
         <include refid="relSelect"></include>
         <where>
             <if test="tenantId!=null and tenantId!=''">
-                AND tenant_id = #{tenantId}
+                AND yi.tenant_id = #{tenantId}
+            </if>
+            <if test="yoloGroup!=null">
+                AND yi.yolo_group = #{yoloGroup}
             </if>
             <if test="name!=null and name!='' ">
-                AND name LIKE CONCAT('%', #{name}, '%')
+                AND yi.name LIKE CONCAT('%', #{name}, '%')
             </if>
             <if test="marked!=null">
-                AND marked = #{marked}
+                AND yi.marked = #{marked}
             </if>
             <if test="startTime!=null">
-                AND create_time <![CDATA[ >= ]]> #{startTime,jdbcType=VARCHAR}
+                AND yi.create_time <![CDATA[ >= ]]> #{startTime,jdbcType=VARCHAR}
             </if>
             <if test="endTime!=null">
-                AND create_time <![CDATA[ < ]]> #{endTime,jdbcType=VARCHAR}
+                AND yi.create_time <![CDATA[ < ]]> #{endTime,jdbcType=VARCHAR}
             </if>
-            ORDER BY id ASC
+            ORDER BY yi.id ASC
         </where>
     </select>
 

+ 12 - 8
src/main/resources/mapper/yolo/YoloTrainMapper.xml

@@ -4,8 +4,9 @@
 
     <!-- 关联查询sql -->
     <sql id="relSelect">
-        SELECT *
-        FROM yolo_train
+        SELECT yt.*,yg.name AS groupName
+        FROM yolo_train yt
+        LEFT JOIN yolo_group yg ON yt.yolo_group = yg.id
     </sql>
 
     <!-- 分页查询 -->
@@ -13,21 +14,24 @@
         <include refid="relSelect"></include>
         <where>
             <if test="tenantId!=null and tenantId!=''">
-                AND tenant_id = #{tenantId}
+                AND yt.tenant_id = #{tenantId}
+            </if>
+            <if test="yoloGroup!=null">
+                AND yt.yolo_group = #{yoloGroup}
             </if>
             <if test="trainStatus!=null and trainStatus!=''">
-                AND train_status = #{trainStatus}
+                AND yt.train_status = #{trainStatus}
             </if>
             <if test="name!=null and name!='' ">
-                AND name LIKE CONCAT('%', #{name}, '%')
+                AND yt.name LIKE CONCAT('%', #{name}, '%')
             </if>
             <if test="startTime!=null">
-            AND create_time <![CDATA[ >= ]]> #{startTime,jdbcType=VARCHAR}
+            AND yt.create_time <![CDATA[ >= ]]> #{startTime,jdbcType=VARCHAR}
             </if>
             <if test="endTime!=null">
-                AND create_time <![CDATA[ < ]]> #{endTime,jdbcType=VARCHAR}
+                AND yt.create_time <![CDATA[ < ]]> #{endTime,jdbcType=VARCHAR}
             </if>
-            ORDER BY id DESC
+            ORDER BY yt.id DESC
             <!-- AND deleted = 0 -->
         </where>
     </select>

+ 16 - 12
src/main/resources/static/business/yoloMark.js

@@ -144,7 +144,7 @@ function drawAll() {
             ctx.fillStyle = 'rgba(255,200,0,0.18)'; // 选中填充
             ctx.strokeStyle = 'orange'; // 选中边框
         } else {
-            const conf = classConfig.find(c => c.index === box.classId) || { boxColor: 'red' };
+            const conf = classConfig.find(c => c.yoloGroup === imgUrlArr[currentImgIndex].yoloGroup && c.index === box.classId) || { boxColor: 'red' };
             ctx.fillStyle = hexToRgba(conf.boxColor, 0.18);
             ctx.strokeStyle = conf.boxColor;
         }
@@ -152,7 +152,7 @@ function drawAll() {
         ctx.fillRect(c.x, c.y, cw, ch);
         ctx.strokeRect(c.x, c.y, cw, ch);
         // 在边框外左上角绘制分类名称,颜色与边框一致
-        const conf = classConfig.find(cg => cg.index === box.classId) || { name: '未知', boxColor: 'black' };
+        const conf = classConfig.find(cg => cg.yoloGroup === imgUrlArr[currentImgIndex].yoloGroup && cg.index === box.classId) || { name: '未知', boxColor: 'black' };
         ctx.font = 'bold 15px Arial';
         ctx.fillStyle = conf.boxColor;
         let textX = c.x;
@@ -218,7 +218,7 @@ function updateBoxesList() {
                 ${idx+1}
                 <div style="width:90px;">
                     <select class='class-select' lay-filter="boxSelect" data-idx='${idx}'>
-                        ${classConfig.map(c => `<option value="${c.index}"${box.classId==c.index?' selected':''}>${c.name}</option>`).join('')}
+                        ${classConfig.filter(c => c.yoloGroup == imgUrlArr[currentImgIndex].yoloGroup).map(c => `<option value="${c.index}"${box.classId==c.index?' selected':''}>${c.name}</option>`).join('')}
                     </select>
                 </div>
                 <button data-idx="${idx}" class="layui-btn layui-btn-danger layui-btn-sm del-btn">删除</button>
@@ -232,6 +232,17 @@ function updateBoxesList() {
     $('#exportBtn').prop('disabled', boxes.length === 0);
 }
 
+// 动态生成右侧分类下拉框选项
+function renderClassSelect() {
+    let html = classConfig
+        .filter(c => c.yoloGroup == imgUrlArr[currentImgIndex].yoloGroup)
+        .map(c => `<option value="${c.index}">${c.name}</option>`).join('');
+    $('#classSelect').html(html);
+    layui.form.render('select', 'classSelectForm');
+}
+
+
+
 // -------------------- 坐标换算函数 --------------------
 // canvas坐标转图片坐标
 function toImageCoord(canvasX, canvasY) {
@@ -343,6 +354,7 @@ function loadCurrentImage() {
         selectedBoxIndex = -1;
         drawAll();
         updateBoxesList();
+        renderClassSelect();
     };
     img.onerror = function() {
         error('图片加载失败,请检查URL是否正确');
@@ -763,13 +775,5 @@ function initYoloAnnotator() {
     $('.nextImgBtn').on('click', function() {
         $('#nextImgBtn').click();
     });
-
-
-    // 动态生成右侧分类下拉框选项
-    function renderClassSelect() {
-        let html = classConfig.map(c => `<option value="${c.index}">${c.name}</option>`).join('');
-        $('#classSelect').html(html);
-        layui.form.render('select', 'classSelectForm');
-    }
-    renderClassSelect();
+    // renderClassSelect();
 }

+ 108 - 36
src/main/resources/templates/yolo/class.html

@@ -1,46 +1,83 @@
 <#import "/tag.html" as tag/>
 <@tag.page title="分类管理">
     <!-- 正文开始 -->
-    <div class="layui-fluid">
-        <div class="layui-card">
-            <div class="layui-card-body">
-                <!-- 表格工具栏 -->
-                <form class="layui-form toolbar">
-                    <div class="layui-form-item">
-                        <div class="layui-inline">
-                            <label class="layui-form-label" style="width:auto;">名称:</label>
-                            <div class="layui-input-inline">
-                                <input name="name" autocomplete="off" class="layui-input" placeholder="名称模糊匹配"/>
+    <div class="layui-fluid" style="padding-bottom: 0;">
+        <div class="layui-row layui-col-space15">
+            <!-- 左边列表 -->
+            <div class="layui-col-md3">
+                <div class="layui-card">
+                    <div class="layui-card-body">
+                        <!-- 左边头部搜索 -->
+                        <form class="layui-form toolbar">
+                            <input name="groupId" id="leftIdSearch" type="hidden"/>
+                            <div class="layui-form-item">
+                                <div class="layui-inline" style="max-width: 140px;">
+                                    <input name="name" class="layui-input" placeholder="输入分类名称"/>
+                                </div>
+                                <div class="layui-inline">
+                                    <button class="layui-btn icon-btn" lay-filter="leftTableSearch" lay-submit>
+                                        <i class="layui-icon">&#xe615;</i>搜索
+                                    </button>
+                                </div>
                             </div>
-                        </div>
-                        <div class="layui-inline">&emsp;
-                            <button class="layui-btn icon-btn" lay-filter="mrxu_list_search" lay-submit>
-                                <i class="layui-icon">&#xe615;</i>搜索
-                            </button>
-                            <#if so.hasPermission('yolo:class:update')>
-                                <span onclick="tableDataAdd(this)" table-filter="mrxu_list_table" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon">&#xe654;</i>添加</span>&nbsp;
-                            </#if>
+                        </form>
+                        <!-- 左边数据表格 -->
+                        <table class="layui-table" lay-data="{url:'groupPage.json',page:false,defaultToolbar:[],limit:100,height: 'full-100'}" lay-filter="leftTable" id="leftTable">
+                            <thead>
+                            <tr>
+                                <th lay-data="{type:'numbers',width:50}">序号</th>
+                                <th lay-data="{templet:'#left_list_name'}">名称</th>
+                            </tr>
+                            </thead>
+                        </table>
+                    </div>
+                </div>
+            </div>
+            <script type="text/html" id="left_list_name">{{d.status==1?d.name:('<text style="color:red;">'+d.name+'</text>')}}</script>
+
+            <!--右边表格-->
+            <div class="layui-col-md9">
+                <div class="layui-card">
+                    <div class="layui-card-body">
+                        <!-- 表格工具栏 -->
+                        <form class="layui-form toolbar">
+                            <div class="layui-form-item">
+                                <div class="layui-inline">
+                                    <label class="layui-form-label" style="width:auto;">名称:</label>
+                                    <div class="layui-input-inline">
+                                        <input name="name" autocomplete="off" class="layui-input" placeholder="名称模糊匹配"/>
+                                    </div>
+                                </div>
+                                <div class="layui-inline">&emsp;
+                                    <button class="layui-btn icon-btn" lay-filter="mrxu_list_search" lay-submit>
+                                        <i class="layui-icon">&#xe615;</i>搜索
+                                    </button>
+                                    <#if so.hasPermission('yolo:class:update')>
+                                    <span onclick="tableDataAdd(this)" table-filter="mrxu_list_table" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon">&#xe654;</i>添加</span>&nbsp;
+                                    </#if>
+                                    <#if so.hasPermission('yolo:class:remove')>
+                                    <span onclick="tableDataDel(this)" key="id" table-filter="mrxu_list_table" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</span>&nbsp;
+                                    </#if>
+                                </div>
+                            </div>
+                        </form>
+                        <table class="layui-table" lay-data="{url:'page.json',page:true,height: 'full-100'}" lay-filter="mrxu_list_table" id="mrxu_list_table">
+                            <thead>
+                            <tr>
                             <#if so.hasPermission('yolo:class:remove')>
-                                <span onclick="tableDataDel(this)" key="id" table-filter="mrxu_list_table" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</span>&nbsp;
+                            <th lay-data="{type:'checkbox',fixed:'left'}"></th>
                             </#if>
-                        </div>
+                            <th lay-data="{field:'name',width:120}">名称</th>
+                            <th lay-data="{field:'indexNumber'}">索引</th>
+                            <th lay-data="{field:'boxColor',templet:'#list_boxColor',width:90}">颜色</th>
+                            <th lay-data="{field:'createTime',width:170}">创建时间</th>
+                            <th lay-data="{field:'updateTime',width:170}">更新时间</th>
+                            <th lay-data="{templet:'#mrxu_list_table_oper',width:195,fixed:'right'}">操作</th>
+                            </tr>
+                            </thead>
+                        </table>
                     </div>
-                </form>
-                <table class="layui-table" lay-data="{url:'page.json',page:true,height: 'full-100'}" lay-filter="mrxu_list_table" id="mrxu_list_table">
-                    <thead>
-                    <tr>
-                        <#if so.hasPermission('yolo:class:remove')>
-                        <th lay-data="{type:'checkbox'}"></th>
-                        </#if>
-                        <th lay-data="{field:'name',width:170}">名称</th>
-                        <th lay-data="{field:'indexNumber'}">索引</th>
-                        <th lay-data="{field:'boxColor',templet:'#list_boxColor'}">颜色</th>
-                        <th lay-data="{field:'createTime',width:170}">创建时间</th>
-                        <th lay-data="{field:'updateTime',width:170}">更新时间</th>
-                        <th lay-data="{templet:'#mrxu_list_table_oper',width:195}">操作</th>
-                    </tr>
-                    </thead>
-                </table>
+                </div>
             </div>
         </div>
     </div>
@@ -79,6 +116,7 @@
     <script type="text/html" id="editDialog">
         <form id="editForm" lay-filter="editForm" class="layui-form model-form">
             <input name="id" type="hidden"/>
+            <input name="yoloGroup" id="leftId" type="hidden"/>
             <div class="layui-form-item">
                 <label class="layui-form-label layui-form-required">名称:</label>
                 <div class="layui-input-block">
@@ -155,7 +193,34 @@
     </script>
 </@tag.page>
 <script type="text/javascript">
+    let selectObj;  // 左表选中数据
+    $(function() {
+        // 初始化左边表格
+        initMrxuPage("leftTable","leftTableSearch","null","","leftEditDialog","leftEditForm","leftEditSubmit");
+        // 左边表格绑定点击事件
+        layui.use(['table'], function () {
+            let table = layui.table;
+            /* 监听行单击事件 */
+            table.on('row(leftTable)', function (obj) {
+                selectObj = obj;
+                $("#leftIdSearch").val(obj.data.id);
+                obj.tr.addClass('layui-table-click').siblings().removeClass('layui-table-click');
+                table.reload('mrxu_list_table',{where: {yoloGroup: obj.data.id},page: {curr: 1}});
+            });
+        });
+    });
+
+    // 右边表格点击编辑后 给默认值
     function editFormInit(data) {
+        // 新增数据需要校验是否选择了左边菜单
+        if(!data) {
+            if(!selectObj) {
+                layui.admin.closeDialog("#leftId");
+                error("请先选择左边分组")
+                return;
+            }
+            $("#leftId").val(selectObj.data.id);
+        }
         let colorpicker = layui.colorpicker;
         colorpicker.render({
             elem: '#boxColor-form'
@@ -168,4 +233,11 @@
     }
     function readFormInit(data) {
     }
+
+    // 点击第一行
+    function clickFirstRow(){
+        if ($("div[lay-id='leftTable']").find("tr[data-index='0']").length>0) {
+            $("div[lay-id='leftTable']").find("tr[data-index='0']").click();
+        }
+    }
 </script>

+ 163 - 0
src/main/resources/templates/yolo/group.html

@@ -0,0 +1,163 @@
+<#import "/tag.html" as tag/>
+<@tag.page title="训练分组管理">
+    <!-- 正文开始 -->
+    <div class="layui-fluid">
+        <div class="layui-card">
+            <div class="layui-card-body">
+                <!-- 表格工具栏 -->
+                <form class="layui-form toolbar">
+                    <div class="layui-form-item">
+                        <div class="layui-inline">
+                            <label class="layui-form-label" style="width:auto;">时间:</label>
+                            <div class="layui-input-inline">
+                                <input name="timeRange" dateType="datetime" autocomplete="off" class="layui-input mrxu_date" range="~" placeholder="选择时间范围"/>
+                            </div>
+                            <label class="layui-form-label" style="width:auto;">名称:</label>
+                            <div class="layui-input-inline">
+                                <input name="name" autocomplete="off" class="layui-input" placeholder="名称模糊匹配"/>
+                            </div>
+                        </div>
+                        <div class="layui-inline">&emsp;
+                            <button class="layui-btn icon-btn" lay-filter="mrxu_list_search" lay-submit>
+                                <i class="layui-icon">&#xe615;</i>搜索
+                            </button>
+                            <#if so.hasPermission('yolo:group:update')>
+                                <span onclick="tableDataAdd(this)" table-filter="mrxu_list_table" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon">&#xe654;</i>添加</span>&nbsp;
+                            </#if>
+                            <#if so.hasPermission('yolo:group:remove')>
+                                <span onclick="tableDataDel(this)" key="id" table-filter="mrxu_list_table" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</span>&nbsp;
+                            </#if>
+                        </div>
+                    </div>
+                </form>
+                <table class="layui-table" lay-data="{url:'page.json',page:true,height: 'full-100'}" lay-filter="mrxu_list_table" id="mrxu_list_table">
+                    <thead>
+                    <tr>
+                        <#if so.hasPermission('yolo:group:remove')>
+                        <th lay-data="{type:'checkbox'}"></th>
+                        </#if>
+                        <th lay-data="{field:'name',width:170}">名称</th>
+                        <th lay-data="{field:'sortNumber'}">排序</th>
+                        <th lay-data="{field:'status',templet:'#list_status'}">启用</th>
+                        <th lay-data="{field:'createTime',width:170}">创建时间</th>
+                        <th lay-data="{field:'updateTime',width:170}">更新时间</th>
+                        <th lay-data="{templet:'#mrxu_list_table_oper',width:195}">操作</th>
+                    </tr>
+                    </thead>
+                </table>
+            </div>
+        </div>
+    </div>
+
+    <script type="text/html" id="list_status">
+        <input type="checkbox" <#if !so.hasPermission('yolo:group:update')>disabled</#if> lay-filter="mrxu_list_status_ck" value="{{d.id}}" lay-skin="switch" lay-text="正常|锁定" {{d.status==1?'checked':''}}/>
+    </script>
+
+    <script type="text/html" id="mrxu_list_table_oper">
+        <#if so.hasPermission('yolo:group:read')>
+        <span lay-event="read" dialog_area="['620px','430px']" dialog_after="readFormInit" class="layui-btn layui-btn-xs"><i class="layui-icon layui-icon-search"></i>详情</span>
+        </#if>
+        <#if so.hasPermission('yolo:group:update')>
+        <span lay-event="edit" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm layui-btn-xs"><i class="layui-icon layui-icon-edit"></i>编辑</span>
+        </#if>
+        <#if so.hasPermission('yolo:group:remove')>
+        <span data-dropdown="#mrxu_list_del_{{d.LAY_INDEX}}" no-shade="true" class="layui-btn layui-btn-danger layui-btn-xs"><i class="layui-icon layui-icon-delete"></i>删除</span>
+        <!--删除确认-->
+        <div class="dropdown-menu-nav dropdown-popconfirm dropdown-top-right layui-hide"
+             id="mrxu_list_del_{{d.LAY_INDEX}}"
+             style="max-width: 200px;white-space: normal;min-width: auto;margin-left: 10px;">
+            <div class="dropdown-anchor"></div>
+            <div class="dropdown-popconfirm-title">
+                <i class="layui-icon layui-icon-help"></i>
+                确定删除“{{d.name}}”?
+            </div>
+            <div class="dropdown-popconfirm-btn">
+                <a class="layui-btn" btn-cancel>取消</a>
+                <a class="layui-btn layui-btn-normal" lay-event="del">确定</a>
+            </div>
+        </div>
+        </#if>
+    </script>
+
+    <#if so.hasPermission('yolo:group:update')>
+    <script type="text/html" id="editDialog">
+        <form id="editForm" lay-filter="editForm" class="layui-form model-form">
+            <input name="id" type="hidden"/>
+            <div class="layui-form-item">
+                <label class="layui-form-label layui-form-required">名称:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="name" autocomplete="off" lay-verify="required" maxlength="64" placeholder="请输入名称" class="layui-input">
+                </div>
+            </div>
+            <div class="layui-form-item">
+                <label class="layui-form-label">排序值:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="sortNumber" autocomplete="off"  maxlength="10" placeholder="请输入排序值" class="layui-input">
+                </div>
+            </div>
+            <div class="layui-form-item">
+                <label class="layui-form-label">启用:</label>
+                <div class="layui-input-block">
+                    <input type="checkbox" name="status" checked value="1" lay-skin="switch" lay-text="是|否">
+                </div>
+            </div>
+            <div class="layui-form-item text-right">
+                <button class="layui-btn" lay-filter="editSubmit" lay-submit>保存</button>
+                <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">取消</button>
+            </div>
+        </form>
+    </script>
+    </#if>
+    <script type="text/html" id="readDialog">
+        <form id="readForm" lay-filter="readForm" class="layui-form model-form">
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">名称:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="name" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">排序值:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="sortNumber" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+            <div class="layui-form-item">
+                <label class="layui-form-label">启用:</label>
+                <div class="layui-input-block">
+                    <input type="checkbox" name="status" disabled lay-skin="switch" lay-text="是|否">
+                </div>
+            </div>
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">创建时间:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="createTime" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">创建人:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="createPerson" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">更新时间:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="updateTime" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+            <div class="layui-form-item layui-form-text">
+                <label class="layui-form-label">更新人:</label>
+                <div class="layui-input-block">
+                    <input type="text" name="updatePerson" disabled class="layui-input layui-btn-disabled">
+                </div>
+            </div>
+        </form>
+    </script>
+</@tag.page>
+<script type="text/javascript">
+    function editFormInit(data) {
+    }
+    function readFormInit(data) {
+    }
+</script>

+ 35 - 9
src/main/resources/templates/yolo/image.html

@@ -8,6 +8,15 @@
                 <form class="layui-form toolbar">
                     <div class="layui-form-item">
                         <div class="layui-inline">
+                            <label class="layui-form-label" style="width:auto;">分组:</label>
+                            <div class="layui-input-inline" style="width:150px;">
+                                <select name="yoloGroup">
+                                    <option value="">全部</option>
+                                    <#list groupList as item>
+                                    <option value="${item.id}">${item.name}</option>
+                                </#list>
+                                </select>
+                            </div>
                             <label class="layui-form-label" style="width:auto;">状态:</label>
                             <div class="layui-input-inline" style="width:90px;">
                                 <select name="marked">
@@ -16,10 +25,10 @@
                                     <option value="1">已标记</option>
                                 </select>
                             </div>
-                            <label class="layui-form-label" style="width:auto;">时间:</label>
+                            <!--<label class="layui-form-label" style="width:auto;">时间:</label>
                             <div class="layui-input-inline">
                                 <input name="timeRange" dateType="datetime" autocomplete="off" class="layui-input mrxu_date" range="~" placeholder="选择时间范围"/>
-                            </div>
+                            </div>-->
                             <label class="layui-form-label" style="width:auto;">名称:</label>
                             <div class="layui-input-inline">
                                 <input name="name" autocomplete="off" class="layui-input" placeholder="名称模糊匹配"/>
@@ -34,7 +43,7 @@
                                 <#if so.hasPermission('yolo:mark:update')>
                                 <span onclick="uploadLabels()" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon layui-icon-upload-drag"></i>标记</span>&nbsp;
                                 </#if>
-                                <span onclick="syncWithDisk(this)" class="layui-btn icon-btn"><i class="layui-icon layui-icon-refresh-3"></i>与磁盘同步</span>&nbsp;
+                                <!--<span onclick="syncWithDisk(this)" class="layui-btn icon-btn"><i class="layui-icon layui-icon-refresh-3"></i>与磁盘同步</span>&nbsp;-->
                             </#if>
                             <#if so.hasPermission('yolo:image:remove')>
                                 <span onclick="tableDataDel(this)" key="id" table-filter="mrxu_list_table" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</span>&nbsp;
@@ -48,6 +57,7 @@
                         <#if so.hasPermission('yolo:image:remove')>
                         <th lay-data="{type:'checkbox'}"></th>
                         </#if>
+                        <th lay-data="{field:'groupName',width:120}">分组</th>
                         <th lay-data="{templet:'#list_image',width:70}">图片</th>
                         <th lay-data="{field:'name'}">名称</th>
                         <th lay-data="{field:'status',templet:'#list_marked',width:100}">状态</th>
@@ -100,15 +110,25 @@
     <script type="text/html" id="uploadImagesDialog">
         <form id="uploadImagesForm" lay-filter="uploadImagesForm" class="layui-form model-form"
               style="padding-right: 20px;">
-            <input name="id" type="hidden"/>
             <div class="layui-row">
+                <div class="layui-form-item">
+                    <label class="layui-form-label layui-form-required">选择分组:</label>
+                    <div class="layui-input-block">
+                        <select name="yoloGroup" id="yoloGroup" lay-verify="required">
+                            <option value=""></option>
+                            <#list groupList as item>
+                            <option value="${item.id}">${item.name}</option>
+                            </#list>
+                        </select>
+                    </div>
+                </div>
                 <div class="layui-form-item">
                     <label class="layui-form-label layui-form-required">选择图片:</label>
                     <div class="layui-input-block">
-                        <button type="button" class="layui-btn layui-btn-primary" id="imageFileUpload" style="height: 150px;width: 340px;">
+                        <button type="button" class="layui-btn layui-btn-primary" id="imageFileUpload" style="height: 150px;width: 370px;">
                             <i class="layui-icon" style="font-size: 100px;">&#xe67c;</i>点击上传图片
                         </button>
-                        <input type="file" id="fileInput" multiple style="display: none;" accept=".jpg,.jpeg,.png,.bmp,.tif,.tiff"/>
+                        <input type="file" lay-verify="required" id="fileInput" multiple style="display: none;" accept=".jpg,.jpeg,.png,.bmp,.tif,.tiff"/>
                     </div>
                 </div>
             </div>
@@ -208,8 +228,6 @@
         window.open('/yolo/mark/index.html?'+queryParam, '_blank');
     }
 
-
-
     // 上传图片
     function uploadImages() {
         layui.admin.open({
@@ -219,8 +237,9 @@
             title: '批量上传图片',
             content: $('#uploadImagesDialog').html(),
             success: function (layero, dIndex) {
+                layui.form.render();
                 layui.use(['jquery', 'layer'], function() {
-                    var $ = layui.jquery,
+                    let $ = layui.jquery,
                         layer = layui.layer;
                     $('#imageFileUpload').on('click', function() {
                         $('#fileInput').trigger('click');
@@ -247,6 +266,12 @@
                         for (let i = 0; i < files.length; i++) {
                             formData.append('files', files[i]);
                         }
+                        let formDataValue = layui.form.val("uploadImagesForm");
+                        if(!formDataValue.yoloGroup) {
+                            layer.msg("请选分组", {icon: 2, anim: 6,time: 1500});
+                            return;
+                        }
+                        formData.append('yoloGroup',formDataValue.yoloGroup);
                         layer.close(dIndex);
                         wait();
                         // 使用 jQuery.ajax 或原生 fetch 发起请求
@@ -275,6 +300,7 @@
                 });
             }
         });
+
     }
 
     // 上传标记txt文件

+ 23 - 4
src/main/resources/templates/yolo/train.html

@@ -12,6 +12,15 @@
                             <div class="layui-input-inline">
                                 <input name="name" autocomplete="off" class="layui-input" placeholder="名称模糊匹配"/>
                             </div>
+                            <label class="layui-form-label" style="width:auto;">分组:</label>
+                            <div class="layui-input-inline" style="width:150px;">
+                                <select name="yoloGroup">
+                                    <option value="">全部</option>
+                                    <#list groupList as item>
+                                    <option value="${item.id}">${item.name}</option>
+                                    </#list>
+                                </select>
+                            </div>
                             <label class="layui-form-label" style="width:auto;">创建时间:</label>
                             <div class="layui-input-inline">
                                 <input name="timeRange" dateType="datetime" autocomplete="off" class="layui-input mrxu_date" range="~" placeholder="选择时间范围"/>
@@ -22,7 +31,7 @@
                                 <i class="layui-icon">&#xe615;</i>搜索
                             </button>
                             <#if so.hasPermission('yolo:train:update')>
-                                <span onclick="tableDataAdd(this)" table-filter="mrxu_list_table" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon">&#xe654;</i>添加训练参数</span>&nbsp;
+                                <span onclick="tableDataAdd(this)" table-filter="mrxu_list_table" dialog_area="650px" dialog_after="editFormInit" class="layui-btn layui-btn-warm icon-btn"><i class="layui-icon">&#xe654;</i>添加训练参数</span>&nbsp;
                             </#if>
                             <#if so.hasPermission('yolo:train:remove')>
                                 <span onclick="tableDataDel(this)" key="id" table-filter="mrxu_list_table" class="layui-btn layui-btn-danger icon-btn"><i class="layui-icon">&#xe640;</i>删除</span>&nbsp;
@@ -35,6 +44,7 @@
                     <tr>
                         <th lay-data="{field:'id',width:60,fixed:'left'}">ID</th>
                         <th lay-data="{field:'name',width:170}">名称</th>
+                        <th lay-data="{field:'groupName',width:150}">分组</th>
                         <th lay-data="{templet:'#list_train_status',width:110}">训练状态</th>
                         <th lay-data="{templet:'#list_train_start_time',width:170}">最近训练时间</th>
                         <th lay-data="{field:'trainTime',width:90}">训练耗时</th>
@@ -83,11 +93,11 @@
 
     <script type="text/html" id="mrxu_list_table_oper">
         <#if so.hasPermission('yolo:train:read')>
-        <span lay-event="read" dialog_area="['620px','500px']" dialog_after="readFormInit" class="layui-btn layui-btn-xs"><i class="layui-icon layui-icon-search"></i>详情</span>
+        <span lay-event="read" dialog_area="['650px','500px']" dialog_after="readFormInit" class="layui-btn layui-btn-xs"><i class="layui-icon layui-icon-search"></i>详情</span>
         <span lay-event="download" class="layui-btn layui-btn-xs"><i class="layui-icon layui-icon-download-circle"></i>下载模型</span>
         </#if>
         <#if so.hasPermission('yolo:train:update')>
-        <span lay-event="edit" dialog_area="620px" dialog_after="editFormInit" class="layui-btn layui-btn-warm layui-btn-xs"><i class="layui-icon layui-icon-edit"></i>编辑</span>
+        <span lay-event="edit" dialog_area="650px" dialog_after="editFormInit" class="layui-btn layui-btn-warm layui-btn-xs"><i class="layui-icon layui-icon-edit"></i>编辑</span>
         {{#  if(d.trainStatus=="TRAINING"){ }}
         <span class="layui-btn layui-btn-disabled layui-btn-xs"><i class="layui-icon layui-icon-play"></i>开始训练</span>
         {{#  } else { }}
@@ -125,7 +135,7 @@
             </div>
             <div class="layui-form-item">
                 <label class="layui-form-label layui-form-required">底模型:</label>
-                <div class="layui-input-block">
+                <div class="layui-input-inline">
                     <select name="model">
                         <option value="yolov12n.pt">yolov12n.pt</option>
                         <option value="yolov12s.pt">yolov12s.pt</option>
@@ -134,6 +144,15 @@
                         <option value="yolov12x.pt">yolov12x.pt</option>
                     </select>
                 </div>
+                <label class="layui-form-label layui-form-required">分组:</label>
+                <div class="layui-input-inline">
+                    <select name="yoloGroup" lay-verify="required">
+                        <option value="">全部</option>
+                        <#list groupList as item>
+                        <option value="${item.id}">${item.name}</option>
+                        </#list>
+                    </select>
+                </div>
             </div>
             <div class="layui-form-item">
                 <label class="layui-form-label" style="width:82px;float:left;">训练轮数:</label>