提交代码
This commit is contained in:
1 parent
2a38245ae2
commit
2d63693111
38 files changed
+5481
-1364
No files matched your search
@@ -104,6 +104,13 @@
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 学生考核模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-xskh</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+11
@@ -164,4 +164,15 @@ public class XgDormHygieneCheckController extends BaseController {
|
||||
return xgDormHygieneCheckService.queryPageListByStudentId(studentId, bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录)
|
||||
*/
|
||||
@SaCheckPermission("dormHygieneCheck:dormHygieneCheck:add")
|
||||
@Log(title = "宿舍卫生检查", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/saveCompleteData")
|
||||
public R<Void> saveCompleteData(@Validated(AddGroup.class) @RequestBody XgDormHygieneCheckCompleteBo bo) {
|
||||
return toAjax(xgDormHygieneCheckService.saveCompleteData(bo));
|
||||
}
|
||||
|
||||
}
|
||||
+10
@@ -140,4 +140,14 @@ public class XgDormHygieneCheck extends TenantEntity {
|
||||
*/
|
||||
private String checkRemarks;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
private String disciplineTypeName;
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.dromara.xg.domain;
|
||||
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪关联对象 xg_hygiene_check_violation
|
||||
*
|
||||
* @author 吴自龙
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("xg_hygiene_check_violation")
|
||||
public class XgHygieneCheckViolation extends TenantEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查ID,关联xg_dorm_hygiene_check.id
|
||||
*/
|
||||
private Long hygieneCheckId;
|
||||
|
||||
/**
|
||||
* 违纪记录ID,关联xskh_discipline_record.id
|
||||
*/
|
||||
private Long disciplineRecordId;
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
private String disciplineTypeName;
|
||||
|
||||
/**
|
||||
* 违纪来源(1宿舍卫生检查 2其他)
|
||||
*/
|
||||
private String violationSource;
|
||||
|
||||
/**
|
||||
* 状态(1生效 2已撤销)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
}
|
||||
+9
@@ -147,5 +147,14 @@ public class XgDormHygieneCheckBo extends BaseEntity {
|
||||
*/
|
||||
private String checkRemarks;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
private String disciplineTypeName;
|
||||
|
||||
}
|
||||
+16
@@ -149,6 +149,16 @@ public class XgDormHygieneCheckCompleteBo {
|
||||
*/
|
||||
private String checkRemarks;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
private String disciplineTypeName;
|
||||
|
||||
/**
|
||||
* 学生评分数据列表
|
||||
*/
|
||||
@@ -156,6 +166,12 @@ public class XgDormHygieneCheckCompleteBo {
|
||||
@NotEmpty(message = "学生评分数据不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private List<XgDormHygieneStudentScoreBo> studentScores;
|
||||
|
||||
/**
|
||||
* 违纪记录数据列表
|
||||
*/
|
||||
@Valid
|
||||
private List<XgViolationDataBo> violationData;
|
||||
|
||||
// 基础字段(用于VO类映射)
|
||||
private Long createBy;
|
||||
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.dromara.xg.domain.bo;
|
||||
|
||||
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪关联业务对象 xg_hygiene_check_violation
|
||||
*
|
||||
* @author 吴自龙
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = XgHygieneCheckViolation.class, reverseConvertGenerate = false)
|
||||
public class XgHygieneCheckViolationBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查ID,关联xg_dorm_hygiene_check.id
|
||||
*/
|
||||
private Long hygieneCheckId;
|
||||
|
||||
/**
|
||||
* 违纪记录ID,关联xskh_discipline_record.id
|
||||
*/
|
||||
private Long disciplineRecordId;
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
@NotNull(message = "学生ID不能为空")
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
@NotBlank(message = "学号不能为空")
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
@NotBlank(message = "学生姓名不能为空")
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
@NotNull(message = "违纪类型ID不能为空")
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
@NotBlank(message = "违纪类型名称不能为空")
|
||||
private String disciplineTypeName;
|
||||
|
||||
/**
|
||||
* 违纪来源(1宿舍卫生检查 2其他)
|
||||
*/
|
||||
private String violationSource;
|
||||
|
||||
/**
|
||||
* 状态(1生效 2已撤销)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 当前页数
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 排序列
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String orderByColumn;
|
||||
|
||||
/**
|
||||
* 排序的方向desc或者asc
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String isAsc;
|
||||
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package org.dromara.xg.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 违纪数据业务对象(用于前端传输)
|
||||
*
|
||||
* @author 吴自龙
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
public class XgViolationDataBo {
|
||||
|
||||
/**
|
||||
* 学生ID,关联xg_student_msg.id
|
||||
*/
|
||||
@NotNull(message = "学生ID不能为空")
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
@NotBlank(message = "学号不能为空")
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
@NotBlank(message = "学生姓名不能为空")
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 班级名称
|
||||
*/
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
private Long majorId;
|
||||
|
||||
/**
|
||||
* 专业名称
|
||||
*/
|
||||
private String majorName;
|
||||
|
||||
/**
|
||||
* 学院ID
|
||||
*/
|
||||
private Long collegeId;
|
||||
|
||||
/**
|
||||
* 学院名称
|
||||
*/
|
||||
private String collegeName;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
@NotNull(message = "违纪类型ID不能为空")
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
@NotBlank(message = "违纪类型名称不能为空")
|
||||
private String disciplineTypeName;
|
||||
|
||||
/**
|
||||
* 违纪日期
|
||||
*/
|
||||
private String disciplineDate;
|
||||
|
||||
/**
|
||||
* 违纪原因
|
||||
*/
|
||||
@NotBlank(message = "违纪原因不能为空")
|
||||
private String disciplineReason;
|
||||
|
||||
/**
|
||||
* 违纪内容
|
||||
*/
|
||||
private String disciplineContent;
|
||||
|
||||
/**
|
||||
* 扣分分值
|
||||
*/
|
||||
private BigDecimal scoreDeduction;
|
||||
|
||||
/**
|
||||
* 违纪来源(1宿舍卫生检查 2其他)
|
||||
*/
|
||||
private String violationSource;
|
||||
|
||||
/**
|
||||
* 状态(1生效 2已撤销)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 附件OSS ID列表,逗号分隔
|
||||
*/
|
||||
private String attachmentOssIds;
|
||||
}
|
||||
+11
-2
@@ -14,8 +14,6 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查视图对象 xg_dorm_hygiene_check
|
||||
*
|
||||
@@ -173,5 +171,16 @@ public class XgDormHygieneCheckVo implements Serializable {
|
||||
@ExcelProperty(value = "检查备注")
|
||||
private String checkRemarks;
|
||||
|
||||
/**
|
||||
* 违纪类型ID,关联xskh_discipline_type.id
|
||||
*/
|
||||
@ExcelProperty(value = "违纪类型ID")
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
@ExcelProperty(value = "违纪类型名称")
|
||||
private String disciplineTypeName;
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.dromara.xg.domain.vo;
|
||||
|
||||
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪关联视图对象 xg_hygiene_check_violation
|
||||
*
|
||||
* @author 吴自龙
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = XgHygieneCheckViolation.class)
|
||||
public class XgHygieneCheckViolationVo {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查ID
|
||||
*/
|
||||
private Long hygieneCheckId;
|
||||
|
||||
/**
|
||||
* 违纪记录ID
|
||||
*/
|
||||
private Long disciplineRecordId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 违纪类型ID
|
||||
*/
|
||||
private Long disciplineTypeId;
|
||||
|
||||
/**
|
||||
* 违纪类型名称
|
||||
*/
|
||||
private String disciplineTypeName;
|
||||
|
||||
/**
|
||||
* 违纪来源
|
||||
*/
|
||||
private String violationSource;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.xg.mapper;
|
||||
|
||||
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
||||
import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪记录Mapper接口
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-29
|
||||
*/
|
||||
public interface XgHygieneCheckViolationMapper
|
||||
extends BaseMapperPlus<XgHygieneCheckViolation, XgHygieneCheckViolationVo> {
|
||||
|
||||
}
|
||||
+8
@@ -111,4 +111,12 @@ public interface IXgDormHygieneCheckService {
|
||||
TableDataInfo<XgDormHygieneCheckVo> queryPageListByStudentId(Long studentId, XgDormHygieneCheckBo bo,
|
||||
PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录)
|
||||
*
|
||||
* @param bo 完整的宿舍卫生检查数据
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
Boolean saveCompleteData(XgDormHygieneCheckCompleteBo bo);
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package org.dromara.xg.service;
|
||||
|
||||
import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
|
||||
import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪记录Service接口
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-29
|
||||
*/
|
||||
public interface IXgHygieneCheckViolationService {
|
||||
|
||||
/**
|
||||
* 查询宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 宿舍卫生检查违纪记录
|
||||
*/
|
||||
XgHygieneCheckViolationVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询宿舍卫生检查违纪记录列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 宿舍卫生检查违纪记录分页列表
|
||||
*/
|
||||
TableDataInfo<XgHygieneCheckViolationVo> queryPageList(XgHygieneCheckViolationBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的宿舍卫生检查违纪记录列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 宿舍卫生检查违纪记录列表
|
||||
*/
|
||||
List<XgHygieneCheckViolationVo> queryList(XgHygieneCheckViolationBo bo);
|
||||
|
||||
/**
|
||||
* 新增宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bo 宿舍卫生检查违纪记录
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(XgHygieneCheckViolationBo bo);
|
||||
|
||||
/**
|
||||
* 修改宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bo 宿舍卫生检查违纪记录
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(XgHygieneCheckViolationBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除宿舍卫生检查违纪记录信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 批量新增宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bos 违纪记录列表
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertBatch(List<XgHygieneCheckViolationBo> bos);
|
||||
|
||||
/**
|
||||
* 根据卫生检查ID查询违纪记录列表
|
||||
*
|
||||
* @param hygieneCheckId 卫生检查ID
|
||||
* @return 违纪记录列表
|
||||
*/
|
||||
List<XgHygieneCheckViolationVo> queryByHygieneCheckId(Long hygieneCheckId);
|
||||
|
||||
}
|
||||
+373
@@ -14,6 +14,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.dromara.xg.domain.bo.XgDormHygieneCheckBo;
|
||||
import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo;
|
||||
import org.dromara.xg.domain.bo.XgDormHygieneStudentScoreBo;
|
||||
import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo;
|
||||
import org.dromara.xg.domain.bo.XgViolationDataBo;
|
||||
import org.dromara.xg.domain.vo.DormHygieneCheckExportVo;
|
||||
import org.dromara.xg.domain.vo.XgDormHygieneCheckVo;
|
||||
import org.dromara.xg.domain.XgDormHygieneCheck;
|
||||
@@ -22,11 +24,14 @@ import org.dromara.xg.mapper.XgDormHygieneCheckMapper;
|
||||
import org.dromara.xg.mapper.XgDormHygieneStudentScoreMapper;
|
||||
import org.dromara.xg.service.IXgDormHygieneCheckService;
|
||||
import org.dromara.xg.service.IXgStudentMsgService;
|
||||
import org.dromara.xg.service.IXgHygieneCheckViolationService;
|
||||
import org.dromara.system.service.ISysDictDataService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查Service业务层处理
|
||||
@@ -43,6 +48,8 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
||||
private final XgDormHygieneStudentScoreMapper studentScoreMapper;
|
||||
private final IXgStudentMsgService xgStudentMsgService;
|
||||
private final ISysDictDataService sysDictDataService;
|
||||
private final IXgHygieneCheckViolationService violationService;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* 查询宿舍卫生检查
|
||||
@@ -574,4 +581,370 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
||||
return "1".equals(isResponsiblePerson) ? "是" : "否";
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存完整的宿舍卫生检查数据(包含学生评分和违纪记录)
|
||||
*
|
||||
* @param bo 完整的宿舍卫生检查数据
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean saveCompleteData(XgDormHygieneCheckCompleteBo bo) {
|
||||
try {
|
||||
// 1. 转换并保存主表数据
|
||||
XgDormHygieneCheck add = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
log.info("保存宿舍卫生检查主表数据: {}", add);
|
||||
|
||||
if (!flag) {
|
||||
log.error("保存宿舍卫生检查主表数据失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
Long hygieneCheckId = add.getId();
|
||||
|
||||
// 2. 保存学生评分数据
|
||||
if (bo.getStudentScores() != null && !bo.getStudentScores().isEmpty()) {
|
||||
for (XgDormHygieneStudentScoreBo studentScoreBo : bo.getStudentScores()) {
|
||||
XgDormHygieneStudentScore studentScore = MapstructUtils.convert(studentScoreBo,
|
||||
XgDormHygieneStudentScore.class);
|
||||
studentScore.setHygieneCheckId(hygieneCheckId);
|
||||
|
||||
// 根据学生ID查询学生完整信息,补充学院、专业、班级信息
|
||||
if (studentScoreBo.getStudentId() != null) {
|
||||
try {
|
||||
var studentInfo = xgStudentMsgService.queryById(studentScoreBo.getStudentId());
|
||||
if (studentInfo != null) {
|
||||
// 补充学院信息
|
||||
if (studentScore.getCollegeId() == null
|
||||
&& StringUtils.isNotBlank(studentInfo.getCollegeId())) {
|
||||
try {
|
||||
studentScore.setCollegeId(Long.valueOf(studentInfo.getCollegeId()));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("学院ID转换失败,学生ID: {}, 学院ID: {}", studentScoreBo.getStudentId(),
|
||||
studentInfo.getCollegeId());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isBlank(studentScore.getCollegeName())) {
|
||||
studentScore.setCollegeName(studentInfo.getCollegeName());
|
||||
}
|
||||
|
||||
// 补充专业信息
|
||||
if (studentScore.getMajorId() == null
|
||||
&& StringUtils.isNotBlank(studentInfo.getMajorId())) {
|
||||
try {
|
||||
studentScore.setMajorId(Long.valueOf(studentInfo.getMajorId()));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("专业ID转换失败,学生ID: {}, 专业ID: {}", studentScoreBo.getStudentId(),
|
||||
studentInfo.getMajorId());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isBlank(studentScore.getMajorName())) {
|
||||
studentScore.setMajorName(studentInfo.getMajorName());
|
||||
}
|
||||
|
||||
// 补充班级信息
|
||||
if (studentScore.getClassId() == null
|
||||
&& StringUtils.isNotBlank(studentInfo.getClassId())) {
|
||||
try {
|
||||
studentScore.setClassId(Long.valueOf(studentInfo.getClassId()));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("班级ID转换失败,学生ID: {}, 班级ID: {}", studentScoreBo.getStudentId(),
|
||||
studentInfo.getClassId());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isBlank(studentScore.getClassName())) {
|
||||
studentScore.setClassName(studentInfo.getClassName());
|
||||
}
|
||||
|
||||
log.info("补充学生信息 - 学生ID: {}, 学院: {}, 专业: {}, 班级: {}",
|
||||
studentScoreBo.getStudentId(),
|
||||
studentInfo.getCollegeName(),
|
||||
studentInfo.getMajorName(),
|
||||
studentInfo.getClassName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询学生信息失败,学生ID: {}, 错误: {}", studentScoreBo.getStudentId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("保存学生评分数据: {}", studentScore);
|
||||
studentScoreMapper.insert(studentScore);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 保存违纪记录数据(兼容架构:优先保存到统一表,失败时保存关联关系)
|
||||
if (bo.getViolationData() != null && !bo.getViolationData().isEmpty()) {
|
||||
for (XgViolationDataBo violationBo : bo.getViolationData()) {
|
||||
try {
|
||||
log.info("处理违纪记录数据: {}", violationBo);
|
||||
|
||||
// 3.1 尝试创建违纪记录到统一的xskh_discipline_record表
|
||||
Long disciplineRecordId = createDisciplineRecord(violationBo);
|
||||
|
||||
// 3.2 仅当违纪记录创建成功时才创建关联记录
|
||||
if (disciplineRecordId != null) {
|
||||
XgHygieneCheckViolationBo relationBo = new XgHygieneCheckViolationBo();
|
||||
relationBo.setHygieneCheckId(hygieneCheckId);
|
||||
relationBo.setDisciplineRecordId(disciplineRecordId);
|
||||
relationBo.setStudentId(violationBo.getStudentId());
|
||||
relationBo.setStudentNo(violationBo.getStudentNo());
|
||||
relationBo.setStudentName(violationBo.getStudentName());
|
||||
relationBo.setDisciplineTypeId(violationBo.getDisciplineTypeId());
|
||||
relationBo.setDisciplineTypeName(violationBo.getDisciplineTypeName());
|
||||
relationBo.setViolationSource("1"); // 1-宿舍卫生检查
|
||||
relationBo.setStatus("1"); // 1-生效
|
||||
|
||||
boolean relationResult = violationService.insertByBo(relationBo);
|
||||
if (!relationResult) {
|
||||
log.warn("创建卫生检查违纪关联记录失败,学生ID: {}", violationBo.getStudentId());
|
||||
} else {
|
||||
log.info("创建卫生检查违纪关联记录成功,学生ID: {},违纪记录ID: {}",
|
||||
violationBo.getStudentId(), disciplineRecordId);
|
||||
}
|
||||
} else {
|
||||
log.warn("违纪记录创建失败,跳过关联记录创建,学生ID: {}", violationBo.getStudentId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理违纪数据失败,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage(), e);
|
||||
// 不影响主流程,继续处理下一个
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("完整保存宿舍卫生检查数据成功,检查ID: {}", hygieneCheckId);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("保存宿舍卫生检查数据失败", e);
|
||||
throw new RuntimeException("保存宿舍卫生检查数据失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建学生违纪记录
|
||||
*
|
||||
* @param violationBo 违纪数据
|
||||
* @return 违纪记录ID
|
||||
*/
|
||||
private Long createDisciplineRecord(XgViolationDataBo violationBo) {
|
||||
try {
|
||||
// 通过接口类型动态获取IXskhDisciplineRecordService,避免硬依赖
|
||||
Class<?> serviceInterface = Class.forName("org.dromara.xskh.service.IXskhDisciplineRecordService");
|
||||
Object disciplineRecordService = applicationContext.getBean(serviceInterface);
|
||||
|
||||
if (disciplineRecordService != null) {
|
||||
// 创建XskhDisciplineRecordBo对象
|
||||
Object disciplineRecordBo = createDisciplineRecordBo(violationBo);
|
||||
if (disciplineRecordBo != null) {
|
||||
// 反射调用insertByBo方法 - 使用具体的Bo类型而不是Object
|
||||
Class<?> disciplineRecordBoClass = Class
|
||||
.forName("org.dromara.xskh.domain.bo.XskhDisciplineRecordBo");
|
||||
var insertMethod = disciplineRecordService.getClass().getMethod("insertByBo",
|
||||
disciplineRecordBoClass);
|
||||
Boolean result = (Boolean) insertMethod.invoke(disciplineRecordService, disciplineRecordBo);
|
||||
|
||||
if (result) {
|
||||
// 获取插入后的ID
|
||||
var field = disciplineRecordBo.getClass().getDeclaredField("id");
|
||||
field.setAccessible(true);
|
||||
Long id = (Long) field.get(disciplineRecordBo);
|
||||
log.info("创建学生违纪记录成功,学生ID: {}, 违纪记录ID: {}", violationBo.getStudentId(), id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.warn("学生考核管理模块(xskh)未启用,跳过违纪记录创建");
|
||||
} catch (Exception e) {
|
||||
log.error("创建学生违纪记录失败: {}", e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建学生违纪记录Bo对象
|
||||
*
|
||||
* @param violationBo 违纪数据
|
||||
* @return 学生违纪记录Bo对象
|
||||
*/
|
||||
private Object createDisciplineRecordBo(XgViolationDataBo violationBo) {
|
||||
try {
|
||||
// 通过反射创建XskhDisciplineRecordBo对象
|
||||
Class<?> disciplineRecordBoClass = Class.forName("org.dromara.xskh.domain.bo.XskhDisciplineRecordBo");
|
||||
Object disciplineRecordBo = disciplineRecordBoClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
// 设置基本字段
|
||||
setFieldValue(disciplineRecordBo, "studentId", violationBo.getStudentId());
|
||||
setFieldValue(disciplineRecordBo, "studentNo", violationBo.getStudentNo());
|
||||
setFieldValue(disciplineRecordBo, "studentName", violationBo.getStudentName());
|
||||
|
||||
// 修复:确保collegeId、majorId、classId不为null
|
||||
// 如果ID为空,尝试通过学生ID查询完整信息
|
||||
Long collegeId = violationBo.getCollegeId();
|
||||
Long majorId = violationBo.getMajorId();
|
||||
Long classId = violationBo.getClassId();
|
||||
String collegeName = violationBo.getCollegeName();
|
||||
String majorName = violationBo.getMajorName();
|
||||
String className = violationBo.getClassName();
|
||||
|
||||
// 如果ID字段为空,尝试从学生信息中获取
|
||||
if ((collegeId == null || collegeId == 0) && violationBo.getStudentId() != null) {
|
||||
try {
|
||||
// 查询学生详细信息以获取缺失的ID字段
|
||||
Map<String, Object> studentDetail = getStudentDetailInfo(violationBo.getStudentId());
|
||||
if (studentDetail != null) {
|
||||
// 修复:安全地获取Long类型的ID字段
|
||||
Object collegeIdObj = studentDetail.get("collegeId");
|
||||
Object majorIdObj = studentDetail.get("majorId");
|
||||
Object classIdObj = studentDetail.get("classId");
|
||||
|
||||
collegeId = (collegeIdObj instanceof Long) ? (Long) collegeIdObj
|
||||
: (collegeIdObj != null ? Long.valueOf(collegeIdObj.toString()) : null);
|
||||
majorId = (majorIdObj instanceof Long) ? (Long) majorIdObj
|
||||
: (majorIdObj != null ? Long.valueOf(majorIdObj.toString()) : null);
|
||||
classId = (classIdObj instanceof Long) ? (Long) classIdObj
|
||||
: (classIdObj != null ? Long.valueOf(classIdObj.toString()) : null);
|
||||
|
||||
// 如果名称为空,也从查询结果中获取
|
||||
if (StringUtils.isBlank(collegeName)) {
|
||||
collegeName = (String) studentDetail.getOrDefault("collegeName", "未知学院");
|
||||
}
|
||||
if (StringUtils.isBlank(majorName)) {
|
||||
majorName = (String) studentDetail.getOrDefault("majorName", "未知专业");
|
||||
}
|
||||
if (StringUtils.isBlank(className)) {
|
||||
className = (String) studentDetail.getOrDefault("className", "未知班级");
|
||||
}
|
||||
|
||||
log.info("从学生信息中补充ID字段,学生ID: {}, 学院ID: {}, 专业ID: {}, 班级ID: {}",
|
||||
violationBo.getStudentId(), collegeId, majorId, classId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询学生详细信息失败,使用默认值,学生ID: {}, 错误: {}", violationBo.getStudentId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
setFieldValue(disciplineRecordBo, "collegeId", collegeId != null ? collegeId : 0L);
|
||||
setFieldValue(disciplineRecordBo, "collegeName",
|
||||
StringUtils.isNotBlank(collegeName) ? collegeName : "未知学院");
|
||||
setFieldValue(disciplineRecordBo, "majorId", majorId != null ? majorId : 0L);
|
||||
setFieldValue(disciplineRecordBo, "majorName",
|
||||
StringUtils.isNotBlank(majorName) ? majorName : "未知专业");
|
||||
setFieldValue(disciplineRecordBo, "classId", classId != null ? classId : 0L);
|
||||
setFieldValue(disciplineRecordBo, "className",
|
||||
StringUtils.isNotBlank(className) ? className : "未知班级");
|
||||
|
||||
setFieldValue(disciplineRecordBo, "disciplineTypeId", violationBo.getDisciplineTypeId());
|
||||
setFieldValue(disciplineRecordBo, "disciplineTypeName", violationBo.getDisciplineTypeName());
|
||||
|
||||
// 设置违纪日期
|
||||
if (StringUtils.isNotBlank(violationBo.getDisciplineDate())) {
|
||||
setFieldValue(disciplineRecordBo, "disciplineDate",
|
||||
java.time.LocalDate.parse(violationBo.getDisciplineDate()));
|
||||
} else {
|
||||
setFieldValue(disciplineRecordBo, "disciplineDate", java.time.LocalDate.now());
|
||||
}
|
||||
|
||||
// 设置违纪原因和内容
|
||||
setFieldValue(disciplineRecordBo, "disciplineReason", violationBo.getDisciplineReason());
|
||||
setFieldValue(disciplineRecordBo, "disciplineContent", violationBo.getDisciplineContent());
|
||||
|
||||
// 设置扣分分值
|
||||
if (violationBo.getScoreDeduction() != null) {
|
||||
setFieldValue(disciplineRecordBo, "scoreDeduction", violationBo.getScoreDeduction());
|
||||
}
|
||||
|
||||
// 设置状态和违纪来源
|
||||
setFieldValue(disciplineRecordBo, "status",
|
||||
StringUtils.isNotBlank(violationBo.getStatus()) ? violationBo.getStatus() : "1");
|
||||
setFieldValue(disciplineRecordBo, "violationSource", "1"); // 1-宿舍卫生检查
|
||||
|
||||
// 设置附件信息(如果有)
|
||||
if (StringUtils.isNotBlank(violationBo.getAttachmentOssIds())) {
|
||||
setFieldValue(disciplineRecordBo, "attachmentOssIds", violationBo.getAttachmentOssIds());
|
||||
}
|
||||
|
||||
log.info("创建学生违纪记录Bo对象成功,学生ID: {}, 违纪类型: {}",
|
||||
violationBo.getStudentId(), violationBo.getDisciplineTypeName());
|
||||
|
||||
return disciplineRecordBo;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建学生违纪记录Bo对象失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射设置对象字段值
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param fieldName 字段名
|
||||
* @param value 字段值
|
||||
*/
|
||||
private void setFieldValue(Object target, String fieldName, Object value) {
|
||||
try {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var field = target.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
} catch (Exception e) {
|
||||
log.warn("设置字段值失败,字段名: {}, 值: {}, 错误: {}", fieldName, value, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生详细信息(包含学院、专业、班级ID)
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @return 学生详细信息Map
|
||||
*/
|
||||
private Map<String, Object> getStudentDetailInfo(Long studentId) {
|
||||
try {
|
||||
var studentInfo = xgStudentMsgService.queryById(studentId);
|
||||
if (studentInfo != null) {
|
||||
Map<String, Object> detailMap = new HashMap<>();
|
||||
|
||||
// 修复:安全地将String类型的ID转换为Long类型
|
||||
detailMap.put("collegeId", parseStringToLong(studentInfo.getCollegeId()));
|
||||
detailMap.put("collegeName", studentInfo.getCollegeName());
|
||||
detailMap.put("majorId", parseStringToLong(studentInfo.getMajorId()));
|
||||
detailMap.put("majorName", studentInfo.getMajorName());
|
||||
detailMap.put("classId", parseStringToLong(studentInfo.getClassId()));
|
||||
detailMap.put("className", studentInfo.getClassName());
|
||||
|
||||
log.debug("获取学生详细信息成功,学生ID: {}, 学院ID: {}, 专业ID: {}, 班级ID: {}",
|
||||
studentId, detailMap.get("collegeId"), detailMap.get("majorId"), detailMap.get("classId"));
|
||||
|
||||
return detailMap;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询学生详细信息失败,学生ID: {}", studentId, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地将String类型转换为Long类型
|
||||
*
|
||||
* @param str 字符串值
|
||||
* @return Long值,转换失败时返回null
|
||||
*/
|
||||
private Long parseStringToLong(String str) {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(str.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("字符串转Long失败,原值: {}", str);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package org.dromara.xg.service.impl;
|
||||
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo;
|
||||
import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
|
||||
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
||||
import org.dromara.xg.mapper.XgHygieneCheckViolationMapper;
|
||||
import org.dromara.xg.service.IXgHygieneCheckViolationService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 宿舍卫生检查违纪记录Service业务层处理
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-29
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class XgHygieneCheckViolationServiceImpl implements IXgHygieneCheckViolationService {
|
||||
|
||||
private final XgHygieneCheckViolationMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 宿舍卫生检查违纪记录
|
||||
*/
|
||||
@Override
|
||||
public XgHygieneCheckViolationVo queryById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询宿舍卫生检查违纪记录列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 宿舍卫生检查违纪记录分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgHygieneCheckViolationVo> queryPageList(XgHygieneCheckViolationBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<XgHygieneCheckViolation> lqw = buildQueryWrapper(bo);
|
||||
Page<XgHygieneCheckViolationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的宿舍卫生检查违纪记录列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 宿舍卫生检查违纪记录列表
|
||||
*/
|
||||
@Override
|
||||
public List<XgHygieneCheckViolationVo> queryList(XgHygieneCheckViolationBo bo) {
|
||||
LambdaQueryWrapper<XgHygieneCheckViolation> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<XgHygieneCheckViolation> buildQueryWrapper(XgHygieneCheckViolationBo bo) {
|
||||
LambdaQueryWrapper<XgHygieneCheckViolation> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getHygieneCheckId() != null, XgHygieneCheckViolation::getHygieneCheckId, bo.getHygieneCheckId());
|
||||
lqw.eq(bo.getStudentId() != null, XgHygieneCheckViolation::getStudentId, bo.getStudentId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), XgHygieneCheckViolation::getStudentNo, bo.getStudentNo());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgHygieneCheckViolation::getStudentName,
|
||||
bo.getStudentName());
|
||||
lqw.eq(bo.getDisciplineTypeId() != null, XgHygieneCheckViolation::getDisciplineTypeId,
|
||||
bo.getDisciplineTypeId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getDisciplineTypeName()), XgHygieneCheckViolation::getDisciplineTypeName,
|
||||
bo.getDisciplineTypeName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getViolationSource()), XgHygieneCheckViolation::getViolationSource,
|
||||
bo.getViolationSource());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XgHygieneCheckViolation::getStatus, bo.getStatus());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bo 宿舍卫生检查违纪记录
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertByBo(XgHygieneCheckViolationBo bo) {
|
||||
XgHygieneCheckViolation add = MapstructUtils.convert(bo, XgHygieneCheckViolation.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bo 宿舍卫生检查违纪记录
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(XgHygieneCheckViolationBo bo) {
|
||||
XgHygieneCheckViolation update = MapstructUtils.convert(bo, XgHygieneCheckViolation.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*
|
||||
* @param entity 实体类数据
|
||||
*/
|
||||
private void validEntityBeforeSave(XgHygieneCheckViolation entity) {
|
||||
// TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并批量删除宿舍卫生检查违纪记录信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增宿舍卫生检查违纪记录
|
||||
*
|
||||
* @param bos 违纪记录列表
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertBatch(List<XgHygieneCheckViolationBo> bos) {
|
||||
if (bos == null || bos.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<XgHygieneCheckViolation> entities = MapstructUtils.convert(bos, XgHygieneCheckViolation.class);
|
||||
for (XgHygieneCheckViolation entity : entities) {
|
||||
validEntityBeforeSave(entity);
|
||||
}
|
||||
|
||||
return baseMapper.insertBatch(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据卫生检查ID查询违纪记录列表
|
||||
*
|
||||
* @param hygieneCheckId 卫生检查ID
|
||||
* @return 违纪记录列表
|
||||
*/
|
||||
@Override
|
||||
public List<XgHygieneCheckViolationVo> queryByHygieneCheckId(Long hygieneCheckId) {
|
||||
LambdaQueryWrapper<XgHygieneCheckViolation> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(XgHygieneCheckViolation::getHygieneCheckId, hygieneCheckId);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
}
|
||||
+22
-4
@@ -63,8 +63,7 @@ public class XskhDisciplineTypeController extends BaseController {
|
||||
*/
|
||||
@SaCheckPermission("disciplineType:disciplineType:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<XskhDisciplineTypeVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
public R<XskhDisciplineTypeVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
|
||||
return R.ok(xskhDisciplineTypeService.queryById(id));
|
||||
}
|
||||
|
||||
@@ -98,8 +97,27 @@ public class XskhDisciplineTypeController extends BaseController {
|
||||
@SaCheckPermission("disciplineType:disciplineType:remove")
|
||||
@Log(title = "学生违纪处分类型", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||
return toAjax(xskhDisciplineTypeService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域类型查询学生违纪处分类型列表
|
||||
*
|
||||
* @param areaType 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
* @param status 状态(0-正常 1-停用)
|
||||
* @return 学生违纪处分类型列表
|
||||
*/
|
||||
@SaCheckPermission("disciplineType:disciplineType:list")
|
||||
@GetMapping("/listByAreaType")
|
||||
public R<List<XskhDisciplineTypeVo>> listByAreaType(@RequestParam String areaType,
|
||||
@RequestParam(required = false) String status) {
|
||||
XskhDisciplineTypeBo bo = new XskhDisciplineTypeBo();
|
||||
bo.setAreaType(areaType);
|
||||
if (status != null) {
|
||||
bo.setStatus(status);
|
||||
}
|
||||
List<XskhDisciplineTypeVo> list = xskhDisciplineTypeService.queryList(bo);
|
||||
return R.ok(list);
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -42,6 +42,21 @@ public class XskhDisciplineType extends TenantEntity {
|
||||
*/
|
||||
private String typeCategory;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
private String areaType;
|
||||
|
||||
/**
|
||||
* 最低扣分
|
||||
*/
|
||||
private java.math.BigDecimal minScore;
|
||||
|
||||
/**
|
||||
* 最高扣分
|
||||
*/
|
||||
private java.math.BigDecimal maxScore;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
@@ -63,5 +78,4 @@ public class XskhDisciplineType extends TenantEntity {
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
private Long deleted;
|
||||
|
||||
|
||||
}
|
||||
+23
-1
@@ -8,6 +8,7 @@ import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 学生违纪处分类型业务对象 xskh_discipline_type
|
||||
@@ -44,6 +45,28 @@ public class XskhDisciplineTypeBo extends BaseEntity {
|
||||
@NotBlank(message = "类型分类(1-违纪类型 2-处分类型)不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String typeCategory;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
@NotBlank(message = "区域类型不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String areaType;
|
||||
|
||||
/**
|
||||
* 最低扣分
|
||||
*/
|
||||
@NotNull(message = "最低扣分不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
@DecimalMin(value = "0.00", message = "最低扣分不能小于0", groups = { AddGroup.class, EditGroup.class })
|
||||
@DecimalMax(value = "100.00", message = "最低扣分不能大于100", groups = { AddGroup.class, EditGroup.class })
|
||||
private java.math.BigDecimal minScore;
|
||||
|
||||
/**
|
||||
* 最高扣分
|
||||
*/
|
||||
@NotNull(message = "最高扣分不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
@DecimalMin(value = "0.00", message = "最高扣分不能小于0", groups = { AddGroup.class, EditGroup.class })
|
||||
@DecimalMax(value = "100.00", message = "最高扣分不能大于100", groups = { AddGroup.class, EditGroup.class })
|
||||
private java.math.BigDecimal maxScore;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
@@ -64,5 +87,4 @@ public class XskhDisciplineTypeBo extends BaseEntity {
|
||||
*/
|
||||
private Long deleted;
|
||||
|
||||
|
||||
}
|
||||
+19
-3
@@ -12,8 +12,6 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 学生违纪处分类型视图对象 xskh_discipline_type
|
||||
*
|
||||
@@ -53,6 +51,25 @@ public class XskhDisciplineTypeVo implements Serializable {
|
||||
@ExcelDictFormat(dictType = "student_discipline_category")
|
||||
private String typeCategory;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
@ExcelProperty(value = "区域类型", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "discipline_area_type")
|
||||
private String areaType;
|
||||
|
||||
/**
|
||||
* 最低扣分
|
||||
*/
|
||||
@ExcelProperty(value = "最低扣分")
|
||||
private java.math.BigDecimal minScore;
|
||||
|
||||
/**
|
||||
* 最高扣分
|
||||
*/
|
||||
@ExcelProperty(value = "最高扣分")
|
||||
private java.math.BigDecimal maxScore;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
@@ -78,5 +95,4 @@ public class XskhDisciplineTypeVo implements Serializable {
|
||||
@ExcelProperty(value = "删除标志(0-未删除 1-已删除)")
|
||||
private Long deleted;
|
||||
|
||||
|
||||
}
|
||||
+30
-2
@@ -73,10 +73,11 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService
|
||||
private LambdaQueryWrapper<XskhDisciplineType> buildQueryWrapper(XskhDisciplineTypeBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<XskhDisciplineType> lqw = Wrappers.lambdaQuery();
|
||||
lqw.orderByAsc(XskhDisciplineType::getId);
|
||||
lqw.orderByAsc(XskhDisciplineType::getSortOrder, XskhDisciplineType::getId);
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getTypeCode()), XskhDisciplineType::getTypeCode, bo.getTypeCode());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getTypeName()), XskhDisciplineType::getTypeName, bo.getTypeName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getTypeCategory()), XskhDisciplineType::getTypeCategory, bo.getTypeCategory());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getAreaType()), XskhDisciplineType::getAreaType, bo.getAreaType());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XskhDisciplineType::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getSortOrder() != null, XskhDisciplineType::getSortOrder, bo.getSortOrder());
|
||||
lqw.eq(bo.getDeleted() != null, XskhDisciplineType::getDeleted, bo.getDeleted());
|
||||
@@ -91,7 +92,16 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(XskhDisciplineTypeBo bo) {
|
||||
// 添加调试日志
|
||||
log.info("接收到的BO参数: typeCode={}, typeName={}, areaType={}, minScore={}, maxScore={}",
|
||||
bo.getTypeCode(), bo.getTypeName(), bo.getAreaType(), bo.getMinScore(), bo.getMaxScore());
|
||||
|
||||
XskhDisciplineType add = MapstructUtils.convert(bo, XskhDisciplineType.class);
|
||||
|
||||
// 添加映射后的调试日志
|
||||
log.info("映射后的Entity参数: typeCode={}, typeName={}, areaType={}, minScore={}, maxScore={}",
|
||||
add.getTypeCode(), add.getTypeName(), add.getAreaType(), add.getMinScore(), add.getMaxScore());
|
||||
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
@@ -117,7 +127,25 @@ public class XskhDisciplineTypeServiceImpl implements IXskhDisciplineTypeService
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(XskhDisciplineType entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
// 校验类型编码唯一性
|
||||
if (StringUtils.isNotBlank(entity.getTypeCode())) {
|
||||
LambdaQueryWrapper<XskhDisciplineType> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(XskhDisciplineType::getTypeCode, entity.getTypeCode());
|
||||
if (entity.getId() != null) {
|
||||
lqw.ne(XskhDisciplineType::getId, entity.getId());
|
||||
}
|
||||
boolean exists = baseMapper.exists(lqw);
|
||||
if (exists) {
|
||||
throw new RuntimeException("类型编码已存在:" + entity.getTypeCode());
|
||||
}
|
||||
}
|
||||
|
||||
// 校验扣分范围
|
||||
if (entity.getMinScore() != null && entity.getMaxScore() != null) {
|
||||
if (entity.getMinScore().compareTo(entity.getMaxScore()) > 0) {
|
||||
throw new RuntimeException("最低扣分不能大于最高扣分");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4381,6 +4381,8 @@ CREATE TABLE `xg_dorm_hygiene_check` (
|
||||
`attachment_ids` text COMMENT '附件OSS ID列表,逗号分隔',
|
||||
`check_summary` text COMMENT '检查总结',
|
||||
`improvement_suggestions` text COMMENT '整改建议',
|
||||
`discipline_type_id` bigint(20) DEFAULT NULL COMMENT '违纪类型ID,关联xskh_discipline_type.id',
|
||||
`discipline_type_name` varchar(100) DEFAULT NULL COMMENT '违纪类型名称',
|
||||
`check_remarks` varchar(500) DEFAULT NULL COMMENT '检查备注',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
@@ -4394,6 +4396,7 @@ CREATE TABLE `xg_dorm_hygiene_check` (
|
||||
KEY `idx_hygiene_grade` (`hygiene_grade`),
|
||||
KEY `idx_check_type` (`check_type`),
|
||||
KEY `idx_create_time` (`create_time`),
|
||||
KEY `idx_discipline_type_id` (`discipline_type_id`),
|
||||
KEY `idx_tenant_deleted` (`tenant_id`,`deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍卫生检查表';
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ CREATE TABLE `xskh_discipline_type` (
|
||||
`type_name` varchar(100) NOT NULL COMMENT '类型名称',
|
||||
`type_category` char(1) NOT NULL COMMENT '类型分类(1-违纪类型 2-处分类型)',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '类型说明',
|
||||
`area_type` char(1) DEFAULT '1' COMMENT '区域类型(1-宿舍区违纪 2-教学区违纪)' ,
|
||||
`min_score` decimal(5,2) DEFAULT 0.00 COMMENT '最低扣分',
|
||||
`max_score` decimal(5,2) DEFAULT 100.00 COMMENT '最高扣分',
|
||||
`status` char(1) DEFAULT '0' COMMENT '状态(0-正常 1-停用)',
|
||||
`sort_order` int(4) DEFAULT 0 COMMENT '排序号',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
@@ -27,6 +30,39 @@ CREATE TABLE `xskh_discipline_type` (
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='学生违纪处分类型表';
|
||||
|
||||
-- 1. 宿舍卫生检查违纪关联表(关联宿舍检查与违纪记录)
|
||||
DROP TABLE IF EXISTS `xg_hygiene_check_violation`;
|
||||
CREATE TABLE `xg_hygiene_check_violation` (
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`hygiene_check_id` bigint(20) NOT NULL COMMENT '宿舍卫生检查ID,关联xg_dorm_hygiene_check.id',
|
||||
`discipline_record_id` bigint(20) NOT NULL COMMENT '违纪记录ID,关联xskh_discipline_record.id',
|
||||
`student_id` bigint(20) NOT NULL COMMENT '学生ID,关联xg_student_msg.id',
|
||||
`student_no` varchar(20) NOT NULL COMMENT '学号',
|
||||
`student_name` varchar(50) NOT NULL COMMENT '学生姓名',
|
||||
`discipline_type_id` bigint(20) NOT NULL COMMENT '违纪类型ID,关联xskh_discipline_type.id',
|
||||
`discipline_type_name` varchar(100) NOT NULL COMMENT '违纪类型名称',
|
||||
`violation_source` char(1) DEFAULT '1' COMMENT '违纪来源(1宿舍卫生检查 2其他)',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(1生效 2已撤销)',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
|
||||
`deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0-未删除 1-已删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_hygiene_check_id` (`hygiene_check_id`),
|
||||
KEY `idx_discipline_record_id` (`discipline_record_id`),
|
||||
KEY `idx_student_id` (`student_id`),
|
||||
KEY `idx_student_no` (`student_no`),
|
||||
KEY `idx_discipline_type_id` (`discipline_type_id`),
|
||||
KEY `idx_violation_source` (`violation_source`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_tenant_deleted` (`tenant_id`,`deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宿舍卫生检查违纪关联表';
|
||||
|
||||
-- 2. 学生违纪处分记录表
|
||||
DROP TABLE IF EXISTS `xskh_discipline_record`;
|
||||
CREATE TABLE `xskh_discipline_record` (
|
||||
@@ -47,6 +83,7 @@ CREATE TABLE `xskh_discipline_record` (
|
||||
`discipline_content` varchar(500) DEFAULT NULL COMMENT '违纪处分内容',
|
||||
`score_deduction` decimal(5,2) DEFAULT 0.00 COMMENT '扣分分值',
|
||||
`status` char(1) DEFAULT '1' COMMENT '状态(1-生效 2-已撤销)',
|
||||
`violation_source` char(1) DEFAULT '2' COMMENT '违纪来源(1-宿舍卫生检查 2-教学区违纪)',
|
||||
`attachment_oss_ids` varchar(1000) DEFAULT NULL COMMENT '附件OSS文件ID,多个用逗号分隔(申请材料、证明文件等)',
|
||||
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
# 学生考核指标indicator_type_name字段问题修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
在新增学生考核指标时,出现数据库插入错误:
|
||||
|
||||
```
|
||||
Field 'indicator_type_name' doesn't have a default value
|
||||
```
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 1. 根本原因
|
||||
- 数据库表 `xskh_assessment_indicator` 中的 `indicator_type_name` 字段设置为 `NOT NULL`
|
||||
- 前端传递的数据中只有 `indicatorTypeId`,没有 `indicatorTypeName`
|
||||
- 后端Service层在插入数据时没有自动设置 `indicatorTypeName` 字段的值
|
||||
|
||||
### 2. 字段映射问题
|
||||
- 前端使用 `indicatorDescription` 字段,但后端数据库字段是 `description`
|
||||
- 为了保持前后端代码和数据库字段的一致性,统一使用 `description` 字段名
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 后端Service层修复
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorServiceImpl.java`
|
||||
|
||||
#### 1.1 添加依赖注入
|
||||
```java
|
||||
private final XskhAssessmentIndicatorTypeMapper indicatorTypeMapper;
|
||||
```
|
||||
|
||||
#### 1.2 修改insertByBo方法
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertByBo(XskhAssessmentIndicatorBo bo) {
|
||||
XskhAssessmentIndicator add = MapstructUtils.convert(bo, XskhAssessmentIndicator.class);
|
||||
|
||||
// 根据指标类型ID设置指标类型名称
|
||||
if (add.getIndicatorTypeId() != null) {
|
||||
XskhAssessmentIndicatorType indicatorType = indicatorTypeMapper.selectById(add.getIndicatorTypeId());
|
||||
if (indicatorType != null) {
|
||||
add.setIndicatorTypeName(indicatorType.getTypeName());
|
||||
}
|
||||
}
|
||||
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
// 保存关联关系
|
||||
saveIndicatorRelations(add.getId(), bo.getDisciplineTypeIds(), bo.getRewardTypeIds());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 修改updateByBo方法
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(XskhAssessmentIndicatorBo bo) {
|
||||
XskhAssessmentIndicator update = MapstructUtils.convert(bo, XskhAssessmentIndicator.class);
|
||||
|
||||
// 根据指标类型ID设置指标类型名称
|
||||
if (update.getIndicatorTypeId() != null) {
|
||||
XskhAssessmentIndicatorType indicatorType = indicatorTypeMapper.selectById(update.getIndicatorTypeId());
|
||||
if (indicatorType != null) {
|
||||
update.setIndicatorTypeName(indicatorType.getTypeName());
|
||||
}
|
||||
}
|
||||
|
||||
validEntityBeforeSave(update);
|
||||
boolean flag = baseMapper.updateById(update) > 0;
|
||||
if (flag) {
|
||||
// 更新关联关系
|
||||
saveIndicatorRelations(update.getId(), bo.getDisciplineTypeIds(), bo.getRewardTypeIds());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 字段映射修复
|
||||
|
||||
为了保持前后端代码和数据库字段的一致性,统一使用 `description` 字段名:
|
||||
|
||||
#### 2.1 前端类型定义修复
|
||||
**文件**: `web/src/api/xskh/assessmentIndicator/types.ts`
|
||||
|
||||
将所有接口中的 `indicatorDescription` 字段改为 `description`:
|
||||
- `XskhAssessmentIndicatorBo`
|
||||
- `XskhAssessmentIndicatorVo`
|
||||
- `XskhAssessmentIndicatorForm`
|
||||
|
||||
#### 2.2 前端表单修复
|
||||
**文件**: `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue`
|
||||
|
||||
- 表单字段名:`indicatorDescription` → `description`
|
||||
- 表单验证规则:`indicatorDescription` → `description`
|
||||
- 初始数据:`indicatorDescription` → `description`
|
||||
|
||||
### 3. Mapper XML修复
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorMapper.xml`
|
||||
|
||||
添加缺失的 `createDept` 字段映射:
|
||||
|
||||
```xml
|
||||
<result property="createDept" column="create_dept"/>
|
||||
```
|
||||
|
||||
### 4. 前端CSS修复
|
||||
|
||||
**文件**: `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue`
|
||||
|
||||
修复CSS语法错误:
|
||||
|
||||
```css
|
||||
/* 固定多选框高度,防止动态变化 */
|
||||
.fixed-height-select :deep(.el-select__tags) {
|
||||
max-height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fixed-height-select :deep(.el-select__tags-text) {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fixed-height-select :deep(.el-select__collapse-tags) {
|
||||
max-width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 创建测试脚本
|
||||
**文件**: `test/学生考核指标API测试脚本.py`
|
||||
|
||||
包含以下测试用例:
|
||||
- 登录认证
|
||||
- 新增学生考核指标
|
||||
- 查询指标列表
|
||||
- 查询指标详情
|
||||
- 修改指标
|
||||
|
||||
### 2. 测试数据
|
||||
```json
|
||||
{
|
||||
"indicatorCode": "zbbm01",
|
||||
"indicatorName": "学生指标01",
|
||||
"description": "学生指标01指标说明",
|
||||
"indicatorTypeId": "1958131304973221889",
|
||||
"maxScore": 2.1,
|
||||
"status": "0",
|
||||
"sortOrder": 0,
|
||||
"disciplineTypeIds": [
|
||||
"1958135628856049666",
|
||||
"1958135753561096193"
|
||||
],
|
||||
"rewardTypeIds": [
|
||||
"1958135350199074817",
|
||||
"1958135400207761409"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 1. 问题解决
|
||||
- ✅ 解决了 `indicator_type_name` 字段缺失默认值的问题
|
||||
- ✅ 统一了前后端字段命名,使用 `description` 字段
|
||||
- ✅ 修复了CSS语法错误
|
||||
- ✅ 完善了Mapper XML字段映射
|
||||
|
||||
### 2. 功能增强
|
||||
- ✅ 自动根据指标类型ID设置指标类型名称
|
||||
- ✅ 统一前后端字段命名,提高代码一致性
|
||||
- ✅ 完善了错误处理和日志记录
|
||||
|
||||
### 3. 代码质量
|
||||
- ✅ 遵循项目开发规范
|
||||
- ✅ 添加了完整的注释说明
|
||||
- ✅ 提供了测试脚本验证功能
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据库字段约束**: 确保数据库表设计合理,避免不必要的NOT NULL约束
|
||||
2. **字段命名一致性**: 前后端字段名应保持一致,避免不必要的映射转换
|
||||
3. **自动填充**: 对于关联字段,应该在Service层自动填充,而不是依赖前端传递
|
||||
4. **测试验证**: 每次修复后都应该进行完整的测试验证
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorServiceImpl.java`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorBo.java`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/XskhAssessmentIndicator.java`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorVo.java`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorMapper.xml`
|
||||
- `web/src/api/xskh/assessmentIndicator/types.ts`
|
||||
- `web/src/views/xskh/assessmentIndicator/AssessmentIndicatorForm.vue`
|
||||
- `test/学生考核指标API测试脚本.py`
|
||||
|
||||
---
|
||||
|
||||
**修复时间**: 2025-01-03
|
||||
**修复人员**: AI Assistant
|
||||
**验证状态**: 待验证
|
||||
@@ -1,162 +0,0 @@
|
||||
# 学生考核指标类型createDept字段问题修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
在 `XskhAssessmentIndicatorTypeController.java` 类的 `add` 方法执行后,数据库中查看数据时发现 `create_dept` 字段(创建人的部门ID)没有插入数据,显示为 NULL。
|
||||
|
||||
## 问题分析
|
||||
|
||||
通过代码分析,发现问题的根本原因是:
|
||||
|
||||
### 1. VO类缺少createDept字段
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorTypeVo.java`
|
||||
|
||||
**问题**: VO类中缺少 `createDept` 字段定义,导致字段映射不完整。
|
||||
|
||||
### 2. BO类缺少createDept字段
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorTypeBo.java`
|
||||
|
||||
**问题**: BO类中缺少 `createDept` 字段定义,影响数据传输。
|
||||
|
||||
### 3. Mapper XML缺少字段映射
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorTypeMapper.xml`
|
||||
|
||||
**问题**: resultMap中缺少 `createDept` 字段的映射配置,导致数据库字段无法正确映射到实体属性。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 修复VO类
|
||||
在 `XskhAssessmentIndicatorTypeVo.java` 中添加 `createDept` 字段:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private Long createDept;
|
||||
```
|
||||
|
||||
### 2. 修复BO类
|
||||
在 `XskhAssessmentIndicatorTypeBo.java` 中添加基础字段:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private Long createDept;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
### 3. 修复Mapper XML
|
||||
在 `XskhAssessmentIndicatorTypeMapper.xml` 的两个resultMap中添加字段映射:
|
||||
|
||||
```xml
|
||||
<result property="createDept" column="create_dept"/>
|
||||
```
|
||||
|
||||
### 4. 增强Service实现
|
||||
在 `XskhAssessmentIndicatorTypeServiceImpl.java` 中添加调试日志和手动设置逻辑:
|
||||
|
||||
```java
|
||||
// 调试日志:检查当前登录用户信息
|
||||
try {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
Long deptId = LoginHelper.getDeptId();
|
||||
log.info("🔍 调试信息 - 当前登录用户ID: {}, 部门ID: {}", userId, deptId);
|
||||
|
||||
// 手动设置createDept字段,确保有值
|
||||
if (add.getCreateDept() == null && deptId != null) {
|
||||
add.setCreateDept(deptId);
|
||||
log.info("✅ 手动设置createDept字段: {}", deptId);
|
||||
} else {
|
||||
log.info("⚠️ createDept字段状态: {}", add.getCreateDept());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("⚠️ 获取登录用户信息失败: {}", e.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
## 技术原理
|
||||
|
||||
### MyBatis-Plus字段填充机制
|
||||
系统使用 MyBatis-Plus 的 `@TableField(fill = FieldFill.INSERT)` 注解来自动填充字段:
|
||||
|
||||
```java
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long createDept;
|
||||
```
|
||||
|
||||
### 字段填充处理器
|
||||
`InjectionMetaObjectHandler` 负责在插入时自动填充字段:
|
||||
|
||||
```java
|
||||
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
|
||||
```
|
||||
|
||||
### 字段映射关系
|
||||
- 数据库字段: `create_dept`
|
||||
- 实体属性: `createDept`
|
||||
- 映射配置: `<result property="createDept" column="create_dept"/>`
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 创建测试脚本
|
||||
创建 `test/学生考核指标类型API测试脚本.py` 来验证修复效果。
|
||||
|
||||
### 2. 测试步骤
|
||||
1. 登录系统获取token
|
||||
2. 调用新增API创建测试数据
|
||||
3. 调用查询API检查createDept字段
|
||||
4. 验证字段是否正确填充
|
||||
|
||||
### 3. 预期结果
|
||||
- createDept字段应该包含当前登录用户的部门ID
|
||||
- 不再显示为NULL
|
||||
|
||||
## 预防措施
|
||||
|
||||
### 1. 规范检查
|
||||
- 所有VO类必须包含四个基础字段:`createBy`、`createTime`、`updateBy`、`updateTime`
|
||||
- 新增 `createDept` 字段作为第五个基础字段
|
||||
- Mapper XML必须包含完整的字段映射
|
||||
|
||||
### 2. 代码模板
|
||||
创建标准的代码模板,确保新开发的模块不会遗漏基础字段。
|
||||
|
||||
### 3. 自动化测试
|
||||
建立自动化测试机制,定期检查字段映射的完整性。
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 修改的文件
|
||||
1. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentIndicatorTypeVo.java`
|
||||
2. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/bo/XskhAssessmentIndicatorTypeBo.java`
|
||||
3. `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentIndicatorTypeMapper.xml`
|
||||
4. `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentIndicatorTypeServiceImpl.java`
|
||||
|
||||
### 新增的文件
|
||||
1. `test/学生考核指标类型API测试脚本.py`
|
||||
|
||||
## 总结
|
||||
|
||||
通过修复VO类、BO类、Mapper XML的字段映射问题,并增强Service实现的调试功能,成功解决了 `create_dept` 字段不填充的问题。这个问题的根本原因是字段映射不完整,导致MyBatis-Plus的自动填充机制无法正常工作。
|
||||
|
||||
修复后的系统能够正确记录创建人的部门信息,为后续的数据分析和权限控制提供了基础支持。
|
||||
@@ -1,218 +0,0 @@
|
||||
# 学生考核时间格式修复报告
|
||||
|
||||
## 📋 问题描述
|
||||
|
||||
### 问题现象
|
||||
在学生考核管理系统中,点击保存按钮调用接口 `http://localhost/dev-api/assessment/approval/approve` 时,返回的数据中 `bonusApprovalList` 和 `bonusList` 的时间字段显示为不标准的格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"bonusApprovalList": [
|
||||
{
|
||||
"createTime": "2025/8/21 上午10:52",
|
||||
"updateTime": "2025/8/21 上午10:52"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
经过代码分析,发现问题出现在以下两个VO类文件中:
|
||||
|
||||
1. **XskhAssessmentBonusApprovalVo.java** - 附加分审核VO类
|
||||
2. **XskhAssessmentSelfScoreVo.java** - 自评得分VO类
|
||||
|
||||
这两个文件中的 `createTime` 和 `updateTime` 字段被错误地定义为 `String` 类型,但使用了 `@JsonFormat` 注解,导致时间格式不正确。
|
||||
|
||||
## 🔧 修复方案
|
||||
|
||||
### 修复文件列表
|
||||
|
||||
#### 1. XskhAssessmentBonusApprovalVo.java
|
||||
**文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentBonusApprovalVo.java`
|
||||
|
||||
**修复内容**:
|
||||
```java
|
||||
// 修复前
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String updateTime;
|
||||
|
||||
// 修复后
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
#### 2. XskhAssessmentSelfScoreVo.java
|
||||
**文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java`
|
||||
|
||||
**修复内容**:
|
||||
```java
|
||||
// 修复前
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String updateTime;
|
||||
|
||||
// 修复后
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
### 修复原理
|
||||
|
||||
1. **类型一致性**: 将时间字段类型从 `String` 改为 `Date`,与实体类保持一致
|
||||
2. **JSON格式化**: 使用 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 注解确保时间以标准格式输出
|
||||
3. **遵循项目规范**: 符合项目后端开发规范中关于VO类基础字段的要求
|
||||
|
||||
## 📊 修复效果
|
||||
|
||||
### 修复前
|
||||
```json
|
||||
{
|
||||
"bonusApprovalList": [
|
||||
{
|
||||
"createTime": "2025/8/21 上午10:52",
|
||||
"updateTime": "2025/8/21 上午10:52"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 修复后
|
||||
```json
|
||||
{
|
||||
"bonusApprovalList": [
|
||||
{
|
||||
"createTime": "2025-08-21 10:52:40",
|
||||
"updateTime": "2025-08-21 10:52:40"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 验证方法
|
||||
|
||||
### 1. 代码验证
|
||||
- ✅ 检查VO类文件中的时间字段类型
|
||||
- ✅ 确认 `@JsonFormat` 注解正确使用
|
||||
- ✅ 验证字段类型与实体类一致
|
||||
|
||||
### 2. 功能验证
|
||||
创建了验证脚本 `test/学生考核时间格式修复验证.py`,用于:
|
||||
- 登录系统获取token
|
||||
- 调用考核审核接口
|
||||
- 检查返回数据中的时间格式
|
||||
- 验证时间字段是否为标准格式
|
||||
|
||||
### 3. 测试步骤
|
||||
```bash
|
||||
# 1. 启动后端服务
|
||||
cd backend
|
||||
mvn spring-boot:run
|
||||
|
||||
# 2. 运行验证脚本
|
||||
cd test
|
||||
python 学生考核时间格式修复验证.py
|
||||
```
|
||||
|
||||
## 📚 相关规范
|
||||
|
||||
### 项目后端开发规范
|
||||
根据 `.cursor/cursorrules-backend.md` 中的规范:
|
||||
|
||||
#### VO类基础字段规范(强制性)
|
||||
```java
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
#### 字段使用注解规范
|
||||
- 时间字段必须使用JSON格式化注解
|
||||
- 字段类型必须统一:createBy/updateBy 为 Long,createTime/updateTime 为 Date
|
||||
- 时间字段必须使用 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 注解
|
||||
|
||||
## 🔍 预防措施
|
||||
|
||||
### 1. 代码审查
|
||||
- 新增VO类时必须检查基础字段类型
|
||||
- 确保时间字段使用正确的类型和注解
|
||||
|
||||
### 2. 自动化检查
|
||||
- 在CI/CD流程中添加代码规范检查
|
||||
- 使用静态代码分析工具检查字段类型
|
||||
|
||||
### 3. 文档更新
|
||||
- 更新项目开发规范文档
|
||||
- 在团队中推广VO类开发标准
|
||||
|
||||
## 📝 总结
|
||||
|
||||
本次修复解决了学生考核管理系统中时间格式显示不正确的问题,主要修复了:
|
||||
|
||||
1. **2个VO类文件**中的时间字段类型错误
|
||||
2. **4个时间字段**的类型从String改为Date
|
||||
3. **确保JSON格式化注解**正确使用
|
||||
4. **修复Service实现类**中的类型转换错误
|
||||
|
||||
### 🔧 修复详情
|
||||
|
||||
#### 第一阶段:VO类修复
|
||||
- `XskhAssessmentBonusApprovalVo.java` - 修复createTime和updateTime字段类型
|
||||
- `XskhAssessmentSelfScoreVo.java` - 修复createTime和updateTime字段类型
|
||||
|
||||
#### 第二阶段:Service实现类修复(优化方案)
|
||||
- `XskhAssessmentApplicationApprovalServiceImpl.java` - 优化时间字段处理逻辑
|
||||
- **根本解决方案**:修改SQL查询使用ResultMap而不是Map类型
|
||||
- **移除复杂的手动类型转换**:让MyBatis自动处理类型映射
|
||||
- **简化代码**:从50+行手动转换代码简化为1行直接赋值
|
||||
|
||||
### 🚨 编译错误解决
|
||||
|
||||
在修复VO类后,编译时出现了类型转换错误:
|
||||
```
|
||||
不兼容的类型: java.lang.String无法转换为java.util.Date
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
1. **修改SQL查询**:使用ResultMap替代resultType="java.util.Map"
|
||||
2. **添加ResultMap定义**:`XskhAssessmentSelfScoreWithIndicatorResult`
|
||||
3. **修改Mapper接口**:返回类型从`List<Object>`改为`List<XskhAssessmentSelfScoreVo>`
|
||||
4. **简化Service代码**:移除复杂的手动类型转换,直接使用MyBatis自动映射
|
||||
|
||||
修复后,系统返回的时间格式将符合标准格式 `yyyy-MM-dd HH:mm:ss`,提升了数据的一致性和用户体验。
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: AI Assistant
|
||||
**修复时间**: 2025-01-03
|
||||
**验证状态**: ✅ 已修复
|
||||
**影响范围**: 学生考核管理系统时间显示
|
||||
@@ -1,246 +0,0 @@
|
||||
# 学生考核申请ID精度丢失修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 错误现象
|
||||
在学生考核申请界面保存时,ID等字段丢失精度,导致存到数据库都为 `1958180338534527000`,后几位都是 `000`。
|
||||
|
||||
### 问题分析
|
||||
1. **根本原因**: JavaScript 大整数精度丢失问题
|
||||
- JavaScript 的 `Number.MAX_SAFE_INTEGER` 为 `9007199254740991`
|
||||
- 雪花ID通常超过这个范围,导致精度丢失
|
||||
- 使用 `parseInt()` 转换大整数字符串时,超出安全范围的数字会被截断
|
||||
|
||||
2. **问题位置**: `safeConvertId` 函数
|
||||
- 原函数使用 `parseInt()` 将字符串ID转换为数字
|
||||
- 对于大整数(如雪花ID),转换后精度丢失
|
||||
|
||||
3. **影响范围**: 所有ID字段
|
||||
- `id`: 主键ID
|
||||
- `studentId`: 学生ID
|
||||
- `collegeId`: 学院ID
|
||||
- `majorId`: 专业ID
|
||||
- `classId`: 班级ID
|
||||
- `scheduleId`: 考核计划ID
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 修复 `safeConvertId` 函数
|
||||
|
||||
**文件**: `web/src/views/xskh/assessmentApplication/AssessmentApplicationForm.vue`
|
||||
|
||||
**修复前**:
|
||||
```typescript
|
||||
const safeConvertId = (id: string | number | undefined): number | undefined => {
|
||||
if (id === undefined || id === null) return undefined;
|
||||
if (typeof id === 'number') return id;
|
||||
if (typeof id === 'string') {
|
||||
// 对于大整数,使用parseInt而不是Number,并检查是否超出安全范围
|
||||
const num = parseInt(id, 10);
|
||||
if (isNaN(num)) return undefined;
|
||||
// 检查是否超出JavaScript安全整数范围
|
||||
if (num > Number.MAX_SAFE_INTEGER) {
|
||||
console.warn('ID超出JavaScript安全整数范围,可能导致精度丢失:', id);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
```
|
||||
|
||||
**修复后**:
|
||||
```typescript
|
||||
const safeConvertId = (id: string | number | undefined): string | undefined => {
|
||||
if (id === undefined || id === null) return undefined;
|
||||
// 始终将ID转换为字符串,避免JavaScript大整数精度丢失
|
||||
return String(id);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 修复要点
|
||||
|
||||
#### 2.1 类型安全
|
||||
- **返回类型**: 从 `number | undefined` 改为 `string | undefined`
|
||||
- **处理逻辑**: 始终使用 `String()` 转换,保持原始精度
|
||||
- **兼容性**: 后端API支持 `string | number` 类型,无需修改
|
||||
|
||||
#### 2.2 精度保持
|
||||
- **字符串转换**: 使用 `String()` 而不是 `parseInt()`
|
||||
- **精度完整**: 保持雪花ID的完整精度
|
||||
- **无截断**: 避免大整数被截断为 `000`
|
||||
|
||||
#### 2.3 项目一致性
|
||||
- **标准做法**: 与项目中其他模块的处理方式保持一致
|
||||
- **最佳实践**: 参考项目中已有的成功案例
|
||||
|
||||
## 技术原理
|
||||
|
||||
### JavaScript 大整数精度问题
|
||||
|
||||
#### 1. 安全整数范围
|
||||
```javascript
|
||||
Number.MAX_SAFE_INTEGER = 9007199254740991 // 2^53 - 1
|
||||
Number.MIN_SAFE_INTEGER = -9007199254740991 // -(2^53 - 1)
|
||||
```
|
||||
|
||||
#### 2. 雪花ID特点
|
||||
- **长度**: 通常为19位数字
|
||||
- **范围**: 远超JavaScript安全整数范围
|
||||
- **精度**: 需要保持完整精度
|
||||
|
||||
#### 3. 转换对比
|
||||
```javascript
|
||||
// 错误的转换方式(精度丢失)
|
||||
const bigId = "1958180338534527123";
|
||||
const wrongResult = parseInt(bigId); // 1958180338534527000 (精度丢失)
|
||||
|
||||
// 正确的转换方式(保持精度)
|
||||
const correctResult = String(bigId); // "1958180338534527123" (完整精度)
|
||||
```
|
||||
|
||||
### 后端支持
|
||||
|
||||
#### 1. API接口支持
|
||||
```typescript
|
||||
// 所有ID参数都支持 string | number 类型
|
||||
export function getAssessmentApplication(id: string | number) {
|
||||
return request({
|
||||
url: '/assessment/application/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 类型定义支持
|
||||
```typescript
|
||||
export interface XskhAssessmentApplicationApprovalBo {
|
||||
id?: string | number;
|
||||
studentId?: string | number;
|
||||
collegeId?: string | number;
|
||||
majorId?: string | number;
|
||||
classId?: string | number;
|
||||
scheduleId?: string | number;
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 后端处理
|
||||
- **Spring Boot**: 自动处理字符串到Long的转换
|
||||
- **MyBatis-Plus**: 支持字符串类型的ID参数
|
||||
- **数据库**: bigint类型可以正确处理字符串输入
|
||||
|
||||
## 验证测试
|
||||
|
||||
### 1. 功能测试
|
||||
|
||||
#### 1.1 新增申请测试
|
||||
- **测试步骤**:
|
||||
1. 打开学生考核申请界面
|
||||
2. 选择考核计划
|
||||
3. 填写申请信息
|
||||
4. 点击保存
|
||||
- **预期结果**: ID字段保持完整精度,数据库记录正确
|
||||
|
||||
#### 1.2 编辑申请测试
|
||||
- **测试步骤**:
|
||||
1. 打开已有申请记录
|
||||
2. 修改申请信息
|
||||
3. 点击保存
|
||||
- **预期结果**: ID字段保持完整精度,更新成功
|
||||
|
||||
#### 1.3 提交申请测试
|
||||
- **测试步骤**:
|
||||
1. 打开申请记录
|
||||
2. 点击提交
|
||||
- **预期结果**: ID字段保持完整精度,提交成功
|
||||
|
||||
### 2. 数据验证
|
||||
|
||||
#### 2.1 前端数据验证
|
||||
```javascript
|
||||
// 验证ID精度
|
||||
const testId = "1958180338534527123";
|
||||
const convertedId = safeConvertId(testId);
|
||||
console.log('转换前:', testId);
|
||||
console.log('转换后:', convertedId);
|
||||
console.log('精度保持:', testId === convertedId); // 应该为 true
|
||||
```
|
||||
|
||||
#### 2.2 后端数据验证
|
||||
```sql
|
||||
-- 查询保存的记录,验证ID精度
|
||||
SELECT id, student_id, college_id, major_id, class_id, schedule_id
|
||||
FROM xskh_assessment_application_approval
|
||||
WHERE id = '1958180338534527123';
|
||||
```
|
||||
|
||||
### 3. 边界测试
|
||||
|
||||
#### 3.1 大整数测试
|
||||
- **测试数据**: 19位雪花ID
|
||||
- **预期结果**: 完整精度保持
|
||||
|
||||
#### 3.2 小整数测试
|
||||
- **测试数据**: 普通整数ID
|
||||
- **预期结果**: 正常转换
|
||||
|
||||
#### 3.3 空值测试
|
||||
- **测试数据**: undefined, null
|
||||
- **预期结果**: 返回 undefined
|
||||
|
||||
## 影响范围
|
||||
|
||||
### 1. 直接影响
|
||||
- ✅ **学生考核申请模块**: 修复完成
|
||||
- ✅ **ID精度保持**: 所有ID字段完整精度
|
||||
- ✅ **数据一致性**: 前后端数据一致
|
||||
|
||||
### 2. 间接影响
|
||||
- ✅ **API接口**: 无需修改,已支持字符串类型
|
||||
- ✅ **数据库**: 无需修改,bigint类型支持字符串输入
|
||||
- ✅ **其他模块**: 无影响
|
||||
|
||||
### 3. 兼容性
|
||||
- ✅ **向后兼容**: 不影响现有功能
|
||||
- ✅ **向前兼容**: 支持新的ID格式
|
||||
- ✅ **跨平台**: 所有平台一致
|
||||
|
||||
## 最佳实践总结
|
||||
|
||||
### 1. 大整数处理原则
|
||||
```typescript
|
||||
// ✅ 正确做法:保持字符串类型
|
||||
const safeConvertId = (id: string | number | undefined): string | undefined => {
|
||||
if (id === undefined || id === null) return undefined;
|
||||
return String(id);
|
||||
};
|
||||
|
||||
// ❌ 错误做法:转换为数字类型
|
||||
const wrongConvertId = (id: string | number | undefined): number | undefined => {
|
||||
return parseInt(String(id), 10); // 可能导致精度丢失
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 项目统一规范
|
||||
- **ID字段**: 始终使用 `string | number` 类型
|
||||
- **转换函数**: 保持字符串格式,避免数字转换
|
||||
- **API接口**: 支持字符串类型参数
|
||||
- **类型定义**: 明确定义支持的类型
|
||||
|
||||
### 3. 测试验证
|
||||
- **功能测试**: 验证保存、编辑、提交功能
|
||||
- **数据验证**: 检查数据库记录精度
|
||||
- **边界测试**: 测试各种ID格式
|
||||
- **兼容性测试**: 确保向后兼容
|
||||
|
||||
## 总结
|
||||
|
||||
通过本次修复,成功解决了学生考核申请界面ID精度丢失的问题:
|
||||
|
||||
1. **问题根除**: 修复了JavaScript大整数精度丢失的根本原因
|
||||
2. **精度保持**: 所有ID字段现在保持完整精度
|
||||
3. **数据一致**: 前后端数据完全一致
|
||||
4. **标准统一**: 与项目其他模块的处理方式保持一致
|
||||
5. **向后兼容**: 不影响现有功能和数据
|
||||
|
||||
修复方案简单有效,符合项目的最佳实践,为后续类似问题提供了标准解决方案。
|
||||
@@ -1,180 +0,0 @@
|
||||
# 学生考核申请LocalDateTime类型转换错误修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
**时间**: 2025-01-03
|
||||
**接口**: `GET /assessment/application/student/{studentId}/schedule/{scheduleId}`
|
||||
**错误信息**:
|
||||
```
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "class java.time.LocalDateTime cannot be cast to class java.lang.String (java.time.LocalDateTime and java.lang.String are in module java.base of loader 'bootstrap')",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 错误原因
|
||||
在 `XskhAssessmentApplicationApprovalServiceImpl.queryById()` 方法中,第83-84行存在类型转换错误:
|
||||
|
||||
```java
|
||||
// 错误的代码
|
||||
selfScoreVo.setCreateTime((String) scoreMap.get("createTime"));
|
||||
selfScoreVo.setUpdateTime((String) scoreMap.get("updateTime"));
|
||||
```
|
||||
|
||||
### 根本原因
|
||||
1. **数据库字段类型**: `create_time` 和 `update_time` 字段在数据库中定义为 `datetime` 类型
|
||||
2. **MyBatis查询返回**: 通过 `selectSelfScoresWithIndicatorInfo` 查询时,MyBatis可能返回 `LocalDateTime` 类型
|
||||
3. **强制类型转换**: 代码中直接强制转换为 `String` 类型,导致 `ClassCastException`
|
||||
|
||||
### 影响范围
|
||||
- 学生考核申请详情查询接口
|
||||
- 学生考核申请表单页面无法正常加载历史数据
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 修复Service实现类中的类型转换
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentApplicationApprovalServiceImpl.java`
|
||||
|
||||
**修改内容**:
|
||||
```java
|
||||
// 修复前
|
||||
selfScoreVo.setCreateTime((String) scoreMap.get("createTime"));
|
||||
selfScoreVo.setUpdateTime((String) scoreMap.get("updateTime"));
|
||||
|
||||
// 修复后
|
||||
Object createTimeObj = scoreMap.get("createTime");
|
||||
if (createTimeObj != null) {
|
||||
selfScoreVo.setCreateTime(createTimeObj.toString());
|
||||
}
|
||||
Object updateTimeObj = scoreMap.get("updateTime");
|
||||
if (updateTimeObj != null) {
|
||||
selfScoreVo.setUpdateTime(updateTimeObj.toString());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 添加JSON格式化注解
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java`
|
||||
|
||||
**修改内容**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String updateTime;
|
||||
```
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentBonusApprovalVo.java`
|
||||
|
||||
**修改内容**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private String updateTime;
|
||||
```
|
||||
|
||||
**文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentApplicationApprovalVo.java`
|
||||
|
||||
**修改内容**:
|
||||
```java
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date submitTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date teacherApprovalTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 修复前
|
||||
- 接口返回500错误
|
||||
- 前端无法正常显示考核申请详情
|
||||
- 用户无法查看历史申请记录
|
||||
|
||||
### 修复后
|
||||
- 接口正常返回数据
|
||||
- 前端可以正常显示考核申请详情
|
||||
- 时间字段格式化为标准格式 `yyyy-MM-dd HH:mm:ss`
|
||||
- 支持查看历史申请记录
|
||||
|
||||
## 技术要点
|
||||
|
||||
### 1. 类型安全处理
|
||||
- 使用 `Object` 类型接收查询结果
|
||||
- 通过 `toString()` 方法安全转换
|
||||
- 添加空值检查避免NPE
|
||||
|
||||
### 2. JSON序列化优化
|
||||
- 添加 `@JsonFormat` 注解统一时间格式
|
||||
- 确保前后端时间格式一致
|
||||
- 提升API响应质量
|
||||
|
||||
### 3. 数据库字段映射
|
||||
- 保持与BaseEntity基类的一致性
|
||||
- 遵循项目的时间字段处理规范
|
||||
- 避免类型转换异常
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 接口测试
|
||||
```bash
|
||||
# 测试学生考核申请查询接口
|
||||
GET /assessment/application/student/1/schedule/1958180338534526978
|
||||
```
|
||||
|
||||
### 2. 前端验证
|
||||
- 打开学生考核申请表单页面
|
||||
- 验证历史申请记录正常显示
|
||||
- 检查时间字段格式是否正确
|
||||
|
||||
### 3. 日志检查
|
||||
- 查看后端日志确认无类型转换错误
|
||||
- 验证SQL查询正常执行
|
||||
- 确认数据正确返回
|
||||
|
||||
## 预防措施
|
||||
|
||||
### 1. 代码规范
|
||||
- 避免直接强制类型转换
|
||||
- 使用安全的类型转换方法
|
||||
- 添加适当的空值检查
|
||||
|
||||
### 2. 测试覆盖
|
||||
- 添加单元测试覆盖时间字段处理
|
||||
- 集成测试验证API响应格式
|
||||
- 前端测试确保UI正常显示
|
||||
|
||||
### 3. 文档维护
|
||||
- 更新API文档说明时间字段格式
|
||||
- 记录VO类字段类型定义
|
||||
- 维护问题解决方案文档
|
||||
|
||||
## 总结
|
||||
|
||||
本次修复解决了学生考核申请模块中的LocalDateTime类型转换错误,通过安全的类型处理和JSON格式化注解,确保了接口的稳定性和数据格式的一致性。修复后的代码更加健壮,能够正确处理各种时间类型的数据,提升了用户体验。
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: AI Assistant
|
||||
**修复时间**: 2025-01-03
|
||||
**影响模块**: 学生考核管理模块
|
||||
**测试状态**: 待验证
|
||||
@@ -1,144 +0,0 @@
|
||||
# 学生考核申请更新主键冲突修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
### 现象
|
||||
在考核申请表单中,对已有数据进行修改后点击保存按钮,调用PUT接口更新数据时出现主键冲突错误:
|
||||
|
||||
```
|
||||
Duplicate entry '1958434137320771585' for key 'PRIMARY'
|
||||
```
|
||||
|
||||
### 错误详情
|
||||
- **接口**: `PUT /assessment/application`
|
||||
- **错误类型**: `java.sql.SQLIntegrityConstraintViolationException`
|
||||
- **错误位置**: `XskhAssessmentSelfScoreMapper.insert`
|
||||
- **根本原因**: 更新操作误用插入逻辑
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 根本原因
|
||||
在 `XskhAssessmentApplicationApprovalServiceImpl.updateSelfScores()` 和 `updateBonusApprovals()` 方法中,更新逻辑存在严重缺陷:
|
||||
|
||||
**原有错误逻辑**:
|
||||
1. 删除所有关联的子表记录
|
||||
2. 重新插入新的记录
|
||||
|
||||
**问题所在**:
|
||||
- 前端传递的数据包含已存在记录的ID
|
||||
- 后端删除后重新插入时,由于ID已存在导致主键冲突
|
||||
- 这种"删除重建"的方式会丢失原有的创建时间、创建人等审计信息
|
||||
|
||||
### 影响范围
|
||||
- 学生考核自评得分表 (`xskh_assessment_self_score`)
|
||||
- 附加分申请表 (`xskh_assessment_bonus_approval`)
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 修复策略
|
||||
采用"智能更新"策略,根据记录ID是否存在决定执行更新或插入操作:
|
||||
|
||||
```java
|
||||
// 如果ID存在,则更新;否则新增
|
||||
if (selfScore.getId() != null) {
|
||||
// 更新现有记录
|
||||
selfScoreMapper.updateById(selfScore);
|
||||
} else {
|
||||
// 新增记录
|
||||
selfScoreMapper.insert(selfScore);
|
||||
}
|
||||
```
|
||||
|
||||
### 具体修改
|
||||
|
||||
#### 1. 修复自评得分更新逻辑
|
||||
**文件**: `XskhAssessmentApplicationApprovalServiceImpl.java`
|
||||
**方法**: `updateSelfScores()`
|
||||
|
||||
```java
|
||||
private void updateSelfScores(Long applicationId, List<XskhAssessmentSelfScoreBo> selfScoreList) {
|
||||
if (selfScoreList != null && !selfScoreList.isEmpty()) {
|
||||
for (XskhAssessmentSelfScoreBo selfScoreBo : selfScoreList) {
|
||||
XskhAssessmentSelfScore selfScore = MapstructUtils.convert(selfScoreBo, XskhAssessmentSelfScore.class);
|
||||
selfScore.setApplicationId(applicationId);
|
||||
|
||||
// 如果ID存在,则更新;否则新增
|
||||
if (selfScore.getId() != null) {
|
||||
// 更新现有记录
|
||||
selfScoreMapper.updateById(selfScore);
|
||||
} else {
|
||||
// 新增记录
|
||||
selfScoreMapper.insert(selfScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 修复附加分申请更新逻辑
|
||||
**文件**: `XskhAssessmentApplicationApprovalServiceImpl.java`
|
||||
**方法**: `updateBonusApprovals()`
|
||||
|
||||
```java
|
||||
private void updateBonusApprovals(Long applicationId, List<XskhAssessmentBonusApprovalBo> bonusApprovalList) {
|
||||
if (bonusApprovalList != null && !bonusApprovalList.isEmpty()) {
|
||||
for (XskhAssessmentBonusApprovalBo bonusApprovalBo : bonusApprovalList) {
|
||||
XskhAssessmentBonusApproval bonusApproval = MapstructUtils.convert(bonusApprovalBo,
|
||||
XskhAssessmentBonusApproval.class);
|
||||
bonusApproval.setApplicationId(applicationId);
|
||||
|
||||
// 如果ID存在,则更新;否则新增
|
||||
if (bonusApproval.getId() != null) {
|
||||
// 更新现有记录
|
||||
bonusApprovalMapper.updateById(bonusApproval);
|
||||
} else {
|
||||
// 新增记录,设置默认状态
|
||||
bonusApproval.setStatus("1"); // 默认待审核状态
|
||||
bonusApprovalMapper.insert(bonusApproval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 优势
|
||||
1. **保留审计信息**: 不再删除重建,保留原有的创建时间、创建人等字段
|
||||
2. **避免主键冲突**: 根据ID存在性智能选择更新或插入操作
|
||||
3. **数据完整性**: 确保数据的连续性和完整性
|
||||
4. **性能优化**: 避免不必要的删除和插入操作
|
||||
|
||||
### 测试验证
|
||||
- ✅ 新增考核申请:正常插入新记录
|
||||
- ✅ 修改考核申请:正常更新现有记录
|
||||
- ✅ 混合操作:同时包含新增和修改的记录
|
||||
- ✅ 数据完整性:审计字段正确保留
|
||||
|
||||
## 经验总结
|
||||
|
||||
### 开发规范
|
||||
1. **更新操作原则**: 避免"删除重建"模式,优先使用"智能更新"
|
||||
2. **ID处理策略**: 根据ID存在性决定操作类型
|
||||
3. **审计字段保护**: 更新时注意保护创建时间、创建人等审计信息
|
||||
4. **事务完整性**: 确保关联数据的更新在同一个事务中
|
||||
|
||||
### 代码审查要点
|
||||
- 检查更新逻辑是否采用"删除重建"模式
|
||||
- 验证是否正确处理已存在记录的ID
|
||||
- 确认审计字段的保留策略
|
||||
- 测试混合操作场景(新增+修改)
|
||||
|
||||
## 相关文件
|
||||
|
||||
- **修复文件**: `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentApplicationApprovalServiceImpl.java`
|
||||
- **涉及表**:
|
||||
- `xskh_assessment_self_score` (学生考核自评得分表)
|
||||
- `xskh_assessment_bonus_approval` (附加分申请表)
|
||||
- **相关接口**: `PUT /assessment/application`
|
||||
|
||||
---
|
||||
|
||||
**修复时间**: 2025-08-21
|
||||
**修复人员**: AI Assistant
|
||||
**状态**: 已完成
|
||||
@@ -1,153 +0,0 @@
|
||||
# 学生考核自评得分查询字段错误修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
**错误时间**: 2025-08-21 11:53:24
|
||||
**错误接口**: `/assessment/approval/1958361581653856257`
|
||||
**错误类型**: SQL语法错误 - 未知字段
|
||||
|
||||
### 错误详情
|
||||
```
|
||||
org.springframework.jdbc.BadSqlGrammarException:
|
||||
Unknown column 'i.indicator_description' in 'field list'
|
||||
```
|
||||
|
||||
### 错误SQL
|
||||
```sql
|
||||
SELECT s.id, s.application_id, s.indicator_id, s.indicator_name,
|
||||
i.indicator_code, i.indicator_description, s.indicator_type,
|
||||
s.max_score, s.self_score, s.self_remark, s.teacher_score,
|
||||
s.teacher_remark, s.create_by, s.create_time, s.update_by, s.update_time
|
||||
FROM xskh_assessment_self_score s
|
||||
LEFT JOIN xskh_assessment_indicator i ON s.indicator_id = i.id AND i.tenant_id = '000000'
|
||||
WHERE s.application_id = ? AND s.deleted = 0 AND s.tenant_id = '000000'
|
||||
ORDER BY s.create_time ASC
|
||||
```
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 根本原因
|
||||
1. **字段名不匹配**: SQL查询中使用了 `i.indicator_description`,但数据库表中实际字段名为 `description`
|
||||
2. **数据库表结构**: `xskh_assessment_indicator` 表中的字段名是 `description`,不是 `indicator_description`
|
||||
|
||||
### 数据库表结构确认
|
||||
```sql
|
||||
-- xskh_assessment_indicator 表结构
|
||||
CREATE TABLE `xskh_assessment_indicator` (
|
||||
`id` bigint(20) NOT NULL COMMENT '主键ID',
|
||||
`indicator_code` varchar(50) NOT NULL COMMENT '指标编码',
|
||||
`indicator_name` varchar(100) NOT NULL COMMENT '指标名称',
|
||||
`indicator_type_id` bigint(20) NOT NULL COMMENT '指标类型ID',
|
||||
`indicator_type_name` varchar(100) NOT NULL COMMENT '指标类型名称',
|
||||
`description` varchar(500) DEFAULT NULL COMMENT '指标说明', -- 正确字段名
|
||||
`max_score` decimal(5,2) NOT NULL COMMENT '最高分值',
|
||||
-- ... 其他字段
|
||||
);
|
||||
```
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 修复文件
|
||||
- **文件路径**: `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentSelfScoreMapper.xml`
|
||||
- **修复方法**: 将SQL查询中的字段名从 `i.indicator_description` 改为 `i.description`
|
||||
|
||||
### 字段命名统一优化
|
||||
为了保持前后端字段命名的一致性,同时进行了以下优化:
|
||||
|
||||
1. **VO类字段统一**: 将 `indicatorDescription` 改为 `description`
|
||||
2. **Mapper映射统一**: 更新ResultMap中的字段映射
|
||||
3. **相关文件同步**: 修改了所有相关的Mapper文件和VO类
|
||||
|
||||
### 具体修复内容
|
||||
|
||||
#### 1. 修复ResultMap映射
|
||||
```xml
|
||||
<!-- 修复前 -->
|
||||
<result property="indicatorDescription" column="indicator_description"/>
|
||||
|
||||
<!-- 修复后 -->
|
||||
<result property="description" column="description"/>
|
||||
```
|
||||
|
||||
#### 2. 修复SQL查询语句
|
||||
```xml
|
||||
<!-- 修复前 -->
|
||||
SELECT
|
||||
s.id, s.application_id, s.indicator_id, s.indicator_name,
|
||||
i.indicator_code, i.indicator_description, s.indicator_type,
|
||||
-- ... 其他字段
|
||||
|
||||
<!-- 修复后 -->
|
||||
SELECT
|
||||
s.id, s.application_id, s.indicator_id, s.indicator_name,
|
||||
i.indicator_code, i.description, s.indicator_type,
|
||||
-- ... 其他字段
|
||||
```
|
||||
|
||||
### 修复后的完整SQL
|
||||
```sql
|
||||
SELECT
|
||||
s.id,
|
||||
s.application_id,
|
||||
s.indicator_id,
|
||||
s.indicator_name,
|
||||
i.indicator_code,
|
||||
i.description, -- 修复:使用正确的字段名
|
||||
s.indicator_type,
|
||||
s.max_score,
|
||||
s.self_score,
|
||||
s.self_remark,
|
||||
s.teacher_score,
|
||||
s.teacher_remark,
|
||||
s.create_by,
|
||||
s.create_time,
|
||||
s.update_by,
|
||||
s.update_time
|
||||
FROM xskh_assessment_self_score s
|
||||
LEFT JOIN xskh_assessment_indicator i ON s.indicator_id = i.id AND i.tenant_id = '000000'
|
||||
WHERE s.application_id = #{applicationId}
|
||||
AND s.deleted = 0
|
||||
AND s.tenant_id = '000000'
|
||||
ORDER BY s.create_time ASC
|
||||
```
|
||||
|
||||
## 验证结果
|
||||
|
||||
### 修复验证
|
||||
1. ✅ **字段映射正确**: ResultMap中的 `description` 字段正确映射到数据库的 `description` 字段
|
||||
2. ✅ **SQL语法正确**: 查询语句使用正确的字段名,符合数据库表结构
|
||||
3. ✅ **VO类兼容**: `XskhAssessmentSelfScoreVo` 类中已正确定义 `description` 字段
|
||||
4. ✅ **字段命名统一**: 前后端字段名保持一致,避免混淆
|
||||
|
||||
### 测试建议
|
||||
1. **接口测试**: 重新调用 `/assessment/approval/{id}` 接口,确认不再出现字段错误
|
||||
2. **数据验证**: 确认返回的指标说明数据正确显示
|
||||
3. **功能测试**: 测试学生考核审核功能,确认自评得分查询正常
|
||||
|
||||
## 经验总结
|
||||
|
||||
### 开发规范提醒
|
||||
1. **字段命名一致性**: SQL查询中的字段名必须与数据库表结构完全一致
|
||||
2. **数据库设计规范**: 遵循项目统一的字段命名规范,避免混淆
|
||||
3. **代码审查**: 在开发过程中注意检查SQL语句的字段名正确性
|
||||
|
||||
### 预防措施
|
||||
1. **数据库文档**: 及时更新数据库设计文档,确保字段名准确
|
||||
2. **代码规范**: 建立SQL编写规范,要求字段名与数据库表结构保持一致
|
||||
3. **测试覆盖**: 增加SQL语法检查的自动化测试
|
||||
|
||||
## 相关文件
|
||||
|
||||
- **数据库设计**: `docs/设计文档/database/学生考核管理/学生考核管理数据库模型.sql`
|
||||
- **Mapper文件**:
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentSelfScoreMapper.xml`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/resources/mapper/xskh/XskhAssessmentPlanMapper.xml`
|
||||
- **VO类**:
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentSelfScoreVo.java`
|
||||
- `backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/domain/vo/XskhAssessmentPlanIndicatorVo.java`
|
||||
|
||||
---
|
||||
|
||||
**修复人员**: Cursor AI
|
||||
**修复时间**: 2025-08-21
|
||||
**修复状态**: ✅ 已完成
|
||||
@@ -1,5 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { TableDataInfo } from '@/api/types';
|
||||
import {
|
||||
DormHygieneCheckVO,
|
||||
DormHygieneCheckForm,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listDormHygieneCheck = (query?: DormHygieneCheckQuery): AxiosPromise<DormHygieneCheckVO[]> => {
|
||||
export const listDormHygieneCheck = (query?: DormHygieneCheckQuery): AxiosPromise<TableDataInfo<DormHygieneCheckVO[]>> => {
|
||||
return request({
|
||||
url: '/dormHygieneCheck/dormHygieneCheck/list',
|
||||
method: 'get',
|
||||
|
||||
@@ -19,6 +19,21 @@ export interface DisciplineTypeVO {
|
||||
*/
|
||||
typeCategory: string;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
areaType: string;
|
||||
|
||||
/**
|
||||
* 最低扣分
|
||||
*/
|
||||
minScore: number;
|
||||
|
||||
/**
|
||||
* 最高扣分
|
||||
*/
|
||||
maxScore: number;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
@@ -61,6 +76,21 @@ export interface DisciplineTypeForm extends BaseEntity {
|
||||
*/
|
||||
typeCategory?: string;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
areaType?: string;
|
||||
|
||||
/**
|
||||
* 最低扣分
|
||||
*/
|
||||
minScore?: number;
|
||||
|
||||
/**
|
||||
* 最高扣分
|
||||
*/
|
||||
maxScore?: number;
|
||||
|
||||
/**
|
||||
* 类型说明
|
||||
*/
|
||||
@@ -98,6 +128,11 @@ export interface DisciplineTypeQuery extends PageQuery {
|
||||
*/
|
||||
typeCategory?: string;
|
||||
|
||||
/**
|
||||
* 区域类型(1-宿舍区违纪 2-教学区违纪)
|
||||
*/
|
||||
areaType?: string;
|
||||
|
||||
/**
|
||||
* 状态(0-正常 1-停用)
|
||||
*/
|
||||
|
||||
+825
@@ -0,0 +1,825 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1400px" append-to-body @close="handleClose">
|
||||
<div class="edit-check-container">
|
||||
<!-- 宿舍整体情况 -->
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>宿舍整体情况</span>
|
||||
<div class="header-info">
|
||||
<el-tag type="info" size="small">{{ formData?.roomName }}</el-tag>
|
||||
<el-divider direction="vertical" />
|
||||
<el-tag type="success" size="small">{{ formatDate(formData?.checkDate) }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form ref="hygieneFormRef" :model="hygieneForm" :rules="hygieneRules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="检查标题" prop="checkTitle">
|
||||
<el-input v-model="hygieneForm.checkTitle" placeholder="请输入检查标题" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="检查日期" prop="checkDate">
|
||||
<el-date-picker
|
||||
v-model="hygieneForm.checkDate"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择检查日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="检查员" prop="checkerName">
|
||||
<el-input v-model="hygieneForm.checkerName" placeholder="检查员姓名" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="检查类型" prop="checkType">
|
||||
<el-select v-model="hygieneForm.checkType" placeholder="请选择检查类型" style="width: 100%">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_check_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="宿舍扣分" prop="totalScore">
|
||||
<el-input-number
|
||||
v-model="hygieneForm.totalScore"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
placeholder="请输入扣分"
|
||||
style="width: 100%"
|
||||
@change="handleTotalScoreChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="卫生等级" prop="hygieneGrade">
|
||||
<el-select v-model="hygieneForm.hygieneGrade" placeholder="请选择卫生等级" style="width: 100%" @change="handleHygieneGradeChange">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_grade" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="检查级别" prop="checkLevel">
|
||||
<el-select v-model="hygieneForm.checkLevel" placeholder="请选择检查级别" style="width: 100%">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_check_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="检查总结">
|
||||
<el-input v-model="hygieneForm.checkSummary" type="textarea" :rows="2" placeholder="请输入检查总结" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="整改建议">
|
||||
<el-input v-model="hygieneForm.improvementSuggestions" type="textarea" :rows="2" placeholder="请输入整改建议" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="检查附件">
|
||||
<div class="attachment-upload-container">
|
||||
<el-upload
|
||||
v-model:file-list="attachmentList"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:data="uploadData"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:on-remove="handleRemoveAttachment"
|
||||
multiple
|
||||
:limit="10"
|
||||
list-type="picture-card"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 宿舍学生情况 -->
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>宿舍学生情况</span>
|
||||
<div class="header-actions">
|
||||
<el-button size="small" @click="loadStudentScores" :loading="studentsLoading"> 刷新学生数据 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="studentScores.length > 0" class="students-table-wrapper">
|
||||
<el-table :data="studentScores" stripe>
|
||||
<el-table-column label="个人扣分" width="120" fixed="left">
|
||||
<template #default="scope">
|
||||
<el-input-number
|
||||
v-model="scope.row.studentScore"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
@change="handleStudentScoreChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="出勤状态" width="120" fixed="left">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.attendanceStatus" size="small" style="width: 100%" @change="handleAttendanceStatusChange(scope.row)">
|
||||
<el-option v-for="dict in xg_student_attendance_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="学生评级" width="120" fixed="left">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.studentGrade" size="small" style="width: 100%" @change="handleStudentGradeChange(scope.row)">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_grade" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="bedName" label="床位" width="80" />
|
||||
<el-table-column prop="studentName" label="学生姓名" width="100" />
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
<el-table-column prop="gender" label="性别" width="60">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.gender === '0'" type="primary" size="small">男</el-tag>
|
||||
<el-tag v-else class="female-tag" size="small">女</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="collegeName" label="学院名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="majorName" label="专业名称" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="className" label="班级名称" min-width="120" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-empty v-else-if="!studentsLoading" description="该房间暂无学生信息" />
|
||||
|
||||
<div v-if="studentsLoading" class="loading-container">
|
||||
<el-skeleton :rows="3" animated />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 宿舍违纪情况 -->
|
||||
<el-card shadow="never" class="section-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>宿舍违纪情况</span>
|
||||
<div class="header-actions">
|
||||
<el-button size="small" type="warning" @click="addViolationRecord" :disabled="studentScores.length === 0"> 新增违纪记录 </el-button>
|
||||
<el-button size="small" @click="loadViolationRecords" :loading="violationsLoading"> 刷新违纪数据 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="violationRecords.length > 0" class="violations-table-wrapper">
|
||||
<el-table :data="violationRecords" stripe>
|
||||
<el-table-column prop="studentName" label="学生姓名" width="100" />
|
||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||
<el-table-column prop="disciplineTypeName" label="违纪类型" width="150" />
|
||||
<el-table-column prop="scoreDeduction" label="扣分" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag type="warning" size="small">{{ scope.row.scoreDeduction }}分</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="disciplineDate" label="违纪日期" width="100" />
|
||||
<el-table-column prop="disciplineReason" label="违纪原因" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_discipline_status" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="editViolationRecord(scope.row)"> 编辑 </el-button>
|
||||
<el-button size="small" type="danger" @click="removeViolationRecord(scope.row)" :disabled="scope.row.status === '2'">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<el-empty v-else-if="!violationsLoading" description="该房间暂无违纪记录" />
|
||||
|
||||
<div v-if="violationsLoading" class="loading-container">
|
||||
<el-skeleton :rows="3" animated />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="buttonLoading"> 保存修改 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 违纪记录录入/编辑弹窗 -->
|
||||
<ViolationRecordDialog
|
||||
v-model="violationDialogVisible"
|
||||
:student-info="currentStudent"
|
||||
:violation-data="currentViolation"
|
||||
:title="violationDialogTitle"
|
||||
@confirm="handleViolationConfirm"
|
||||
@close="handleViolationClose"
|
||||
/>
|
||||
|
||||
<!-- 学生选择弹窗 -->
|
||||
<el-dialog v-model="studentSelectVisible" title="选择学生" width="600px" append-to-body>
|
||||
<el-table :data="studentScores" @selection-change="handleStudentSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="studentName" label="学生姓名" />
|
||||
<el-table-column prop="studentNo" label="学号" />
|
||||
<el-table-column prop="className" label="班级" />
|
||||
<el-table-column prop="bedName" label="床位" />
|
||||
</el-table>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="studentSelectVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmStudentSelection" :disabled="selectedStudents.length === 0">
|
||||
确定选择 ({{ selectedStudents.length }})
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, toRefs, getCurrentInstance, ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 组件实例
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 组件属性
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
formData: any;
|
||||
title: string;
|
||||
}
|
||||
|
||||
// 组件事件
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'submit', data: any): void;
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const hygieneFormRef = ref();
|
||||
const buttonLoading = ref(false);
|
||||
const studentsLoading = ref(false);
|
||||
const violationsLoading = ref(false);
|
||||
const attachmentList = ref<any[]>([]);
|
||||
|
||||
// 宿舍卫生检查表单
|
||||
const hygieneForm = reactive({
|
||||
id: undefined,
|
||||
checkTitle: '',
|
||||
checkDate: '',
|
||||
checkerId: undefined,
|
||||
checkerName: '',
|
||||
checkType: '1',
|
||||
checkLevel: '1',
|
||||
roomId: undefined,
|
||||
roomCode: '',
|
||||
roomName: '',
|
||||
buildingId: undefined,
|
||||
buildingName: '',
|
||||
campusId: undefined,
|
||||
campusName: '',
|
||||
totalScore: 0,
|
||||
hygieneGrade: 'A',
|
||||
checkSummary: '',
|
||||
improvementSuggestions: '',
|
||||
checkRemarks: '',
|
||||
attachmentIds: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const hygieneRules = reactive({
|
||||
checkTitle: [{ required: true, message: '检查标题不能为空', trigger: 'blur' }],
|
||||
checkDate: [{ required: true, message: '检查日期不能为空', trigger: 'change' }],
|
||||
checkerName: [{ required: true, message: '检查员不能为空', trigger: 'blur' }],
|
||||
totalScore: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: (error?: string | Error) => void) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
const score = parseFloat(value.toString());
|
||||
if (isNaN(score) || score < 0 || score > 100) {
|
||||
callback(new Error('宿舍总分必须在0-100分之间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
hygieneGrade: [{ required: true, message: '请选择卫生等级', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 学生评分数据
|
||||
const studentScores = ref<any[]>([]);
|
||||
|
||||
// 违纪记录数据
|
||||
const violationRecords = ref<any[]>([]);
|
||||
|
||||
// 违纪记录弹窗相关
|
||||
const violationDialogVisible = ref(false);
|
||||
const currentStudent = ref<any>(null);
|
||||
const currentViolation = ref<any>(null);
|
||||
|
||||
// 学生选择弹窗相关
|
||||
const studentSelectVisible = ref(false);
|
||||
const selectedStudents = ref<any[]>([]);
|
||||
|
||||
// 字典数据
|
||||
const { xg_dorm_hygiene_grade, xg_student_attendance_status, xg_dorm_hygiene_check_type, xg_dorm_hygiene_check_level, student_discipline_status } =
|
||||
toRefs<any>(
|
||||
proxy?.useDict(
|
||||
'xg_dorm_hygiene_grade',
|
||||
'xg_student_attendance_status',
|
||||
'xg_dorm_hygiene_check_type',
|
||||
'xg_dorm_hygiene_check_level',
|
||||
'student_discipline_status'
|
||||
)
|
||||
);
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => props.title);
|
||||
|
||||
const violationDialogTitle = computed(() => {
|
||||
return currentStudent.value ? `${currentStudent.value.studentName} - 违纪记录录入` : '违纪记录录入';
|
||||
});
|
||||
|
||||
// 上传配置
|
||||
const uploadAction = computed(() => {
|
||||
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
|
||||
});
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
return {
|
||||
'Authorization': `Bearer ${getToken() || ''}`,
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
};
|
||||
});
|
||||
|
||||
const uploadData = computed(() => {
|
||||
return {
|
||||
bizType: 'dorm_hygiene_check_attachment'
|
||||
};
|
||||
});
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
return new Date(dateStr).toLocaleDateString();
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const initFormData = () => {
|
||||
if (props.formData) {
|
||||
Object.assign(hygieneForm, props.formData);
|
||||
|
||||
// 处理附件数据
|
||||
if (props.formData.attachmentIds) {
|
||||
handleAttachmentData();
|
||||
}
|
||||
|
||||
// 加载学生评分数据
|
||||
loadStudentScores();
|
||||
|
||||
// 加载违纪记录数据
|
||||
loadViolationRecords();
|
||||
}
|
||||
};
|
||||
|
||||
// 加载学生评分数据
|
||||
const loadStudentScores = async () => {
|
||||
if (!hygieneForm.id) return;
|
||||
|
||||
studentsLoading.value = true;
|
||||
try {
|
||||
const res = await request({
|
||||
url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${hygieneForm.id}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
studentScores.value = res.data;
|
||||
} else {
|
||||
studentScores.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生评分数据失败:', error);
|
||||
ElMessage.error('加载学生评分数据失败');
|
||||
} finally {
|
||||
studentsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载违纪记录数据
|
||||
const loadViolationRecords = async () => {
|
||||
if (!hygieneForm.id) return;
|
||||
|
||||
violationsLoading.value = true;
|
||||
try {
|
||||
const res = await request({
|
||||
url: `/xg/dormHygieneCheck/getViolations/${hygieneForm.id}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
violationRecords.value = res.data;
|
||||
} else {
|
||||
violationRecords.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载违纪记录数据失败:', error);
|
||||
ElMessage.error('加载违纪记录数据失败');
|
||||
} finally {
|
||||
violationsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理附件数据
|
||||
const handleAttachmentData = async () => {
|
||||
if (!hygieneForm.attachmentIds) {
|
||||
attachmentList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const ossIdsArray = hygieneForm.attachmentIds
|
||||
.split(',')
|
||||
.map((id: string) => id.trim())
|
||||
.filter((id: string) => id);
|
||||
if (ossIdsArray.length === 0) {
|
||||
attachmentList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
attachmentList.value = response.data.map((item: any, index: number) => ({
|
||||
uid: `existing_${index}_${Date.now()}`,
|
||||
name: item.originalName || `附件${index + 1}`,
|
||||
url: item.url,
|
||||
ossId: item.ossId,
|
||||
status: 'success'
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理附件数据失败:', error);
|
||||
attachmentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 处理宿舍总分变化
|
||||
const handleTotalScoreChange = () => {
|
||||
if (hygieneForm.totalScore !== null && hygieneForm.totalScore !== undefined) {
|
||||
const score = parseFloat(hygieneForm.totalScore.toString());
|
||||
if (!isNaN(score) && score >= 0 && score <= 100) {
|
||||
// 自动为所有学生设置相同的个人得分
|
||||
studentScores.value.forEach((student) => {
|
||||
student.studentScore = score;
|
||||
});
|
||||
|
||||
if (studentScores.value.length > 0) {
|
||||
ElMessage.success(`已自动为${studentScores.value.length}名学生设置个人得分:${score}分`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理卫生等级变化
|
||||
const handleHygieneGradeChange = () => {
|
||||
if (hygieneForm.hygieneGrade && studentScores.value.length > 0) {
|
||||
// 自动为所有学生设置相同的评级
|
||||
studentScores.value.forEach((student) => {
|
||||
student.studentGrade = hygieneForm.hygieneGrade;
|
||||
});
|
||||
|
||||
const gradeDict = xg_dorm_hygiene_grade.value.find((dict: any) => dict.value === hygieneForm.hygieneGrade);
|
||||
const gradeLabel = gradeDict ? gradeDict.label : hygieneForm.hygieneGrade;
|
||||
|
||||
ElMessage.success(`已自动为${studentScores.value.length}名学生设置评级:${gradeLabel}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理学生个人得分变化
|
||||
const handleStudentScoreChange = (student: any) => {
|
||||
if (student.studentScore !== null && student.studentScore !== undefined && student.studentScore !== '') {
|
||||
const score = parseFloat(student.studentScore.toString());
|
||||
if (isNaN(score) || score < 0 || score > 100) {
|
||||
ElMessage.warning('个人得分必须在0-100分之间');
|
||||
student.studentScore = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理出勤状态变化
|
||||
const handleAttendanceStatusChange = (student: any) => {
|
||||
console.log('出勤状态变化:', student.studentName, student.attendanceStatus);
|
||||
};
|
||||
|
||||
// 处理学生评级变化
|
||||
const handleStudentGradeChange = (student: any) => {
|
||||
console.log('学生评级变化:', student.studentName, student.studentGrade);
|
||||
};
|
||||
|
||||
// 新增违纪记录
|
||||
const addViolationRecord = () => {
|
||||
if (studentScores.value.length === 1) {
|
||||
// 只有一个学生,直接选择
|
||||
currentStudent.value = studentScores.value[0];
|
||||
currentViolation.value = null;
|
||||
violationDialogVisible.value = true;
|
||||
} else {
|
||||
// 多个学生,显示选择弹窗
|
||||
studentSelectVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
// 编辑违纪记录
|
||||
const editViolationRecord = (violation: any) => {
|
||||
const student = studentScores.value.find((s) => s.studentId === violation.studentId);
|
||||
currentStudent.value = student;
|
||||
currentViolation.value = violation;
|
||||
violationDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除违纪记录
|
||||
const removeViolationRecord = async (violation: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除学生 ${violation.studentName} 的违纪记录吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
// 调用删除API
|
||||
const res = await request({
|
||||
url: `/xskh/disciplineRecord/${violation.id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('违纪记录删除成功');
|
||||
loadViolationRecords();
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除违纪记录失败:', error);
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 学生选择变化
|
||||
const handleStudentSelectionChange = (students: any[]) => {
|
||||
selectedStudents.value = students;
|
||||
};
|
||||
|
||||
// 确认学生选择
|
||||
const confirmStudentSelection = () => {
|
||||
if (selectedStudents.value.length === 1) {
|
||||
currentStudent.value = selectedStudents.value[0];
|
||||
currentViolation.value = null;
|
||||
violationDialogVisible.value = true;
|
||||
studentSelectVisible.value = false;
|
||||
} else {
|
||||
ElMessage.warning('请选择一个学生');
|
||||
}
|
||||
};
|
||||
|
||||
// 违纪记录确认
|
||||
const handleViolationConfirm = async (violationData: any) => {
|
||||
try {
|
||||
let res;
|
||||
if (currentViolation.value) {
|
||||
// 编辑模式
|
||||
res = await request({
|
||||
url: '/xskh/disciplineRecord',
|
||||
method: 'put',
|
||||
data: { ...violationData, id: currentViolation.value.id }
|
||||
});
|
||||
} else {
|
||||
// 新增模式
|
||||
res = await request({
|
||||
url: '/xskh/disciplineRecord',
|
||||
method: 'post',
|
||||
data: violationData
|
||||
});
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('违纪记录保存成功');
|
||||
violationDialogVisible.value = false;
|
||||
loadViolationRecords();
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存违纪记录失败:', error);
|
||||
ElMessage.error('保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 违纪记录弹窗关闭
|
||||
const handleViolationClose = () => {
|
||||
violationDialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 文件上传相关方法
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const isValidType = /\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx|txt|mp4|avi|mov)$/i.test(file.name);
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
|
||||
if (!isValidType) {
|
||||
ElMessage.error('上传文件格式不正确!');
|
||||
return false;
|
||||
}
|
||||
if (!isLt10M) {
|
||||
ElMessage.error('上传文件大小不能超过 10MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUploadSuccess = (response: any, file: any) => {
|
||||
if (response.code === 200) {
|
||||
file.url = response.data.url;
|
||||
file.ossId = response.data.ossId;
|
||||
ElMessage.success('文件上传成功');
|
||||
} else {
|
||||
ElMessage.error(response.msg || '文件上传失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadError = (error: any) => {
|
||||
console.error('文件上传失败:', error);
|
||||
ElMessage.error('文件上传失败');
|
||||
};
|
||||
|
||||
const handleRemoveAttachment = (file: any) => {
|
||||
ElMessage.success('文件删除成功');
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
try {
|
||||
await hygieneFormRef.value?.validate();
|
||||
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 处理附件数据
|
||||
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
||||
hygieneForm.attachmentIds = attachmentOssIds.join(',');
|
||||
|
||||
// 构建提交数据
|
||||
const submitData = {
|
||||
...hygieneForm,
|
||||
studentScores: studentScores.value
|
||||
};
|
||||
|
||||
emit('submit', submitData);
|
||||
} catch (error) {
|
||||
console.error('提交表单失败:', error);
|
||||
ElMessage.error('请检查表单填写是否完整');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
initFormData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听表单数据变化
|
||||
watch(
|
||||
() => props.formData,
|
||||
(newData) => {
|
||||
if (newData) {
|
||||
Object.assign(hygieneForm, newData);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.edit-check-container {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-card:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.students-table-wrapper {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.violations-table-wrapper {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.female-tag {
|
||||
background-color: rgba(255, 105, 180, 0.1) !important;
|
||||
color: #ff69b4 !important;
|
||||
border-color: rgba(255, 105, 180, 0.1) !important;
|
||||
}
|
||||
|
||||
.attachment-upload-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
+735
@@ -0,0 +1,735 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1400px" append-to-body @close="handleClose">
|
||||
<!-- 基础信息选择区域 -->
|
||||
<el-form ref="mainFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-card shadow="never" style="margin-bottom: 20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>检查基础信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 第一行:检查标题、检查日期、检查员 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检查标题" prop="checkTitle">
|
||||
<el-input v-model="form.checkTitle" placeholder="请输入检查标题" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检查日期" prop="checkDate">
|
||||
<el-date-picker
|
||||
v-model="form.checkDate"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择检查日期"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检查员" prop="checkerName">
|
||||
<el-select
|
||||
v-model="form.checkerName"
|
||||
placeholder="请选择检查员"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleCheckerSearch"
|
||||
:loading="checkerLoading"
|
||||
@change="handleCheckerChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="checker in checkerOptions"
|
||||
:key="checker.userId"
|
||||
:label="`${checker.nickName || checker.userName}`"
|
||||
:value="checker.nickName || checker.userName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:检查类型、检查级别 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检查类型" prop="checkType">
|
||||
<el-select v-model="form.checkType" placeholder="请选择检查类型" style="width: 100%">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_check_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="检查级别" prop="checkLevel">
|
||||
<el-select v-model="form.checkLevel" placeholder="请选择检查级别" style="width: 100%">
|
||||
<el-option v-for="dict in xg_dorm_hygiene_check_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第三行:校区、宿舍楼选择 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="校区名称" prop="campusId">
|
||||
<el-select
|
||||
v-model="form.campusId"
|
||||
placeholder="请选择校区"
|
||||
filterable
|
||||
:loading="dormLoading"
|
||||
@change="handleCampusChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option v-for="campus in campusOptions" :key="campus.id" :label="campus.campusName" :value="campus.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="宿舍楼名称" prop="buildingId">
|
||||
<el-select
|
||||
v-model="form.buildingId"
|
||||
placeholder="请选择宿舍楼"
|
||||
filterable
|
||||
:loading="dormLoading"
|
||||
@change="handleBuildingChange"
|
||||
style="width: 100%"
|
||||
:disabled="!form.campusId"
|
||||
>
|
||||
<el-option v-for="building in buildingOptions" :key="building.id" :label="getBuildingDisplayName(building)" :value="building.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-form>
|
||||
|
||||
<!-- 房间选择区域 -->
|
||||
<el-card shadow="never" v-if="form.buildingId && floorRooms.length > 0">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>宿舍房间列表</span>
|
||||
<div class="header-actions">
|
||||
<el-tag type="info" size="small">{{ getBuildingName() }}</el-tag>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="room-stats"> 总房间数: {{ totalRoomsCount }} | 已检查: {{ completedRooms.size }} </span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-text type="primary" size="small">双击房间开始检查</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 楼层和房间展示 -->
|
||||
<div class="floors-container">
|
||||
<div v-for="floorData in floorRooms" :key="floorData.floor" class="floor-section">
|
||||
<div class="floor-header">
|
||||
<h4>{{ floorData.floor }}楼</h4>
|
||||
<div class="floor-actions">
|
||||
<span class="floor-stats"> 已检查: {{ getFloorCompletedCount(floorData.floor) }} / {{ floorData.rooms.length }} </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rooms-grid">
|
||||
<div
|
||||
v-for="room in floorData.rooms"
|
||||
:key="room.id"
|
||||
class="room-card"
|
||||
:class="{
|
||||
'completed': completedRooms.has(room.id),
|
||||
'pending': !completedRooms.has(room.id)
|
||||
}"
|
||||
@dblclick="handleRoomDoubleClick(room)"
|
||||
>
|
||||
<div class="room-card-content">
|
||||
<div class="room-name">{{ room.roomName }}</div>
|
||||
<div class="room-info">
|
||||
<span class="room-code">{{ room.roomCode || room.roomName }}</span>
|
||||
</div>
|
||||
<div class="room-status">
|
||||
<el-icon v-if="completedRooms.has(room.id)" class="completed-icon">
|
||||
<Check />
|
||||
</el-icon>
|
||||
<el-icon v-else class="pending-icon">
|
||||
<Calendar />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="room-hint">
|
||||
<span v-if="completedRooms.has(room.id)" class="completed-text">已检查</span>
|
||||
<span v-else class="pending-text">双击检查</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-if="floorRooms.length === 0 && !roomsLoading" description="该宿舍楼暂无房间信息" />
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="roomsLoading" class="loading-container">
|
||||
<el-skeleton :rows="3" animated />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 单个房间检查弹窗 -->
|
||||
<DormHygieneCheckSingleDialog
|
||||
:visible="singleCheckVisible"
|
||||
:room-info="currentRoom"
|
||||
:check-info="form"
|
||||
:title="singleCheckTitle"
|
||||
@update:visible="(value) => (singleCheckVisible = value)"
|
||||
@submit="handleSingleCheckSubmit"
|
||||
@close="handleSingleCheckClose"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="DormHygieneCheckMainDialog" lang="ts">
|
||||
import { ref, reactive, computed, watch, getCurrentInstance, ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Check, Calendar } from '@element-plus/icons-vue';
|
||||
import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 组件实例
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 组件属性
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
// 组件事件
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'submit', data: any): void;
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const mainFormRef = ref();
|
||||
const dormLoading = ref(false);
|
||||
const roomsLoading = ref(false);
|
||||
const checkerLoading = ref(false);
|
||||
|
||||
// 基础表单数据
|
||||
const form = reactive({
|
||||
checkTitle: '',
|
||||
checkDate: '',
|
||||
checkerId: undefined,
|
||||
checkerName: '',
|
||||
checkType: '1',
|
||||
checkLevel: '1',
|
||||
campusId: undefined,
|
||||
campusName: '',
|
||||
buildingId: undefined,
|
||||
buildingName: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
checkTitle: [{ required: true, message: '检查标题不能为空', trigger: 'blur' }],
|
||||
checkDate: [{ required: true, message: '检查日期不能为空', trigger: 'change' }],
|
||||
checkerName: [{ required: true, message: '请选择检查员', trigger: 'change' }],
|
||||
checkType: [{ required: true, message: '请选择检查类型', trigger: 'change' }],
|
||||
checkLevel: [{ required: true, message: '请选择检查级别', trigger: 'change' }],
|
||||
campusId: [{ required: true, message: '请选择校区', trigger: 'change' }],
|
||||
buildingId: [{ required: true, message: '请选择宿舍楼', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 选项数据
|
||||
const campusOptions = ref<any[]>([]);
|
||||
const buildingOptions = ref<any[]>([]);
|
||||
const checkerOptions = ref<any[]>([]);
|
||||
|
||||
// 房间相关数据
|
||||
const floorRooms = ref<any[]>([]);
|
||||
const completedRooms = ref<Set<string | number>>(new Set());
|
||||
const allRooms = ref<any[]>([]);
|
||||
|
||||
// 单个房间检查弹窗相关
|
||||
const singleCheckVisible = ref(false);
|
||||
const currentRoom = ref<any>(null);
|
||||
|
||||
// 字典数据
|
||||
const xg_dorm_hygiene_check_level = ref<any[]>([]);
|
||||
const xg_dorm_hygiene_check_type = ref<any[]>([]);
|
||||
|
||||
// 初始化字典数据
|
||||
const initDict = () => {
|
||||
if (proxy && (proxy as any).useDict) {
|
||||
const dictData = (proxy as any).useDict('xg_dorm_hygiene_check_level', 'xg_dorm_hygiene_check_type');
|
||||
xg_dorm_hygiene_check_level.value = dictData.xg_dorm_hygiene_check_level;
|
||||
xg_dorm_hygiene_check_type.value = dictData.xg_dorm_hygiene_check_type;
|
||||
}
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => props.title);
|
||||
|
||||
const totalRoomsCount = computed(() => allRooms.value.length);
|
||||
|
||||
const singleCheckTitle = computed(() => {
|
||||
return currentRoom.value ? `${currentRoom.value.roomName} - 卫生检查` : '房间卫生检查';
|
||||
});
|
||||
|
||||
// 生成默认检查标题
|
||||
const generateDefaultCheckTitle = () => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
return `${year}${month}${day}宿舍卫生检查`;
|
||||
};
|
||||
|
||||
// 初始化表单
|
||||
const initForm = () => {
|
||||
Object.assign(form, {
|
||||
checkTitle: generateDefaultCheckTitle(),
|
||||
checkDate: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||
checkType: '1',
|
||||
checkLevel: '1',
|
||||
checkerId: undefined,
|
||||
checkerName: '',
|
||||
campusId: undefined,
|
||||
campusName: '',
|
||||
buildingId: undefined,
|
||||
buildingName: ''
|
||||
});
|
||||
|
||||
completedRooms.value.clear();
|
||||
floorRooms.value = [];
|
||||
allRooms.value = [];
|
||||
|
||||
// 获取当前用户信息
|
||||
fetchCurrentUserInfo();
|
||||
};
|
||||
|
||||
// 获取当前用户信息
|
||||
const fetchCurrentUserInfo = async () => {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data?.user) {
|
||||
const user = response.data.user;
|
||||
form.checkerId = user.userId;
|
||||
form.checkerName = user.nickName || user.userName;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取用户信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载校区选项
|
||||
const loadCampusOptions = async () => {
|
||||
try {
|
||||
dormLoading.value = true;
|
||||
const res = await request({
|
||||
url: '/xqxx/dormCampus/list',
|
||||
method: 'get'
|
||||
});
|
||||
campusOptions.value = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('加载校区选项失败:', error);
|
||||
ElMessage.error('加载校区选项失败');
|
||||
} finally {
|
||||
dormLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 校区选择变化处理
|
||||
const handleCampusChange = async (campusId: string | number) => {
|
||||
// 清空下级选项
|
||||
form.buildingId = undefined;
|
||||
form.buildingName = '';
|
||||
buildingOptions.value = [];
|
||||
floorRooms.value = [];
|
||||
allRooms.value = [];
|
||||
completedRooms.value.clear();
|
||||
|
||||
if (campusId) {
|
||||
const selectedCampus = campusOptions.value.find((campus) => campus.id === campusId);
|
||||
if (selectedCampus) {
|
||||
form.campusId = campusId;
|
||||
form.campusName = selectedCampus.campusName;
|
||||
}
|
||||
await loadBuildingOptions(campusId);
|
||||
} else {
|
||||
form.campusId = undefined;
|
||||
form.campusName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 加载宿舍楼选项
|
||||
const loadBuildingOptions = async (campusId: string | number) => {
|
||||
try {
|
||||
dormLoading.value = true;
|
||||
const res = await request({
|
||||
url: '/sslxx/dormBuilding/list',
|
||||
method: 'get',
|
||||
params: { campusId }
|
||||
});
|
||||
buildingOptions.value = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('加载宿舍楼选项失败:', error);
|
||||
ElMessage.error('加载宿舍楼选项失败');
|
||||
} finally {
|
||||
dormLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 宿舍楼选择变化处理
|
||||
const handleBuildingChange = async (buildingId: string | number) => {
|
||||
floorRooms.value = [];
|
||||
allRooms.value = [];
|
||||
completedRooms.value.clear();
|
||||
|
||||
if (buildingId) {
|
||||
const selectedBuilding = buildingOptions.value.find((building) => building.id === buildingId);
|
||||
if (selectedBuilding) {
|
||||
form.buildingId = buildingId;
|
||||
form.buildingName = selectedBuilding.buildingName;
|
||||
}
|
||||
await loadRoomsByFloor(buildingId);
|
||||
} else {
|
||||
form.buildingId = undefined;
|
||||
form.buildingName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 按楼层加载房间
|
||||
const loadRoomsByFloor = async (buildingId: string | number) => {
|
||||
try {
|
||||
roomsLoading.value = true;
|
||||
const res = await request({
|
||||
url: '/xgss/dormRoom/list',
|
||||
method: 'get',
|
||||
params: { buildingId }
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.rows) {
|
||||
allRooms.value = res.rows;
|
||||
|
||||
// 按楼层分组
|
||||
const floorMap = new Map();
|
||||
res.rows.forEach((room: any) => {
|
||||
// 使用房间数据中的floorNum字段作为楼层
|
||||
const floor = room.floorNum || '1';
|
||||
if (!floorMap.has(floor)) {
|
||||
floorMap.set(floor, []);
|
||||
}
|
||||
floorMap.get(floor).push(room);
|
||||
});
|
||||
|
||||
// 转换为数组并排序
|
||||
floorRooms.value = Array.from(floorMap.entries())
|
||||
.map(([floor, rooms]) => ({ floor, rooms }))
|
||||
.sort((a, b) => parseInt(a.floor) - parseInt(b.floor));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载房间失败:', error);
|
||||
ElMessage.error('加载房间失败');
|
||||
} finally {
|
||||
roomsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载检查员选项
|
||||
const loadCheckerOptions = async (searchKeyword?: string) => {
|
||||
try {
|
||||
checkerLoading.value = true;
|
||||
const params: any = {
|
||||
pageNum: 1,
|
||||
pageSize: 50,
|
||||
status: '0'
|
||||
};
|
||||
|
||||
if (searchKeyword?.trim()) {
|
||||
params.nickName = searchKeyword.trim();
|
||||
}
|
||||
|
||||
const res = await request({
|
||||
url: '/system/user/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
checkerOptions.value = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('加载检查员选项失败:', error);
|
||||
} finally {
|
||||
checkerLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 检查员搜索处理
|
||||
const handleCheckerSearch = (query: string) => {
|
||||
if (query) {
|
||||
loadCheckerOptions(query);
|
||||
} else {
|
||||
loadCheckerOptions();
|
||||
}
|
||||
};
|
||||
|
||||
// 检查员选择变化处理
|
||||
const handleCheckerChange = (checkerName: string) => {
|
||||
const selectedChecker = checkerOptions.value.find((checker) => (checker.nickName || checker.userName) === checkerName);
|
||||
|
||||
if (selectedChecker) {
|
||||
form.checkerId = selectedChecker.userId;
|
||||
form.checkerName = selectedChecker.nickName || selectedChecker.userName;
|
||||
}
|
||||
};
|
||||
|
||||
// 房间双击处理
|
||||
const handleRoomDoubleClick = (room: any) => {
|
||||
// 验证基础信息是否完整
|
||||
if (!form.checkTitle || !form.checkDate || !form.checkerName || !form.checkType || !form.checkLevel) {
|
||||
ElMessage.warning('请先完善检查基础信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果房间已经检查过,询问是否重新检查
|
||||
if (completedRooms.value.has(room.id)) {
|
||||
ElMessageBox.confirm(`${room.roomName} 已经检查过,是否重新检查?`, '提示', {
|
||||
confirmButtonText: '重新检查',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
openSingleCheckDialog(room);
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
} else {
|
||||
openSingleCheckDialog(room);
|
||||
}
|
||||
};
|
||||
|
||||
// 打开单个房间检查弹窗
|
||||
const openSingleCheckDialog = (room: any) => {
|
||||
currentRoom.value = room;
|
||||
singleCheckVisible.value = true;
|
||||
};
|
||||
|
||||
// 单个房间检查提交处理
|
||||
const handleSingleCheckSubmit = (data: any) => {
|
||||
// 标记房间为已完成
|
||||
completedRooms.value.add(currentRoom.value.id);
|
||||
|
||||
ElMessage.success(`${currentRoom.value.roomName} 检查完成`);
|
||||
singleCheckVisible.value = false;
|
||||
};
|
||||
|
||||
// 单个房间检查关闭处理
|
||||
const handleSingleCheckClose = () => {
|
||||
singleCheckVisible.value = false;
|
||||
};
|
||||
|
||||
// 获取楼层已完成数量
|
||||
const getFloorCompletedCount = (floor: string) => {
|
||||
const floorData = floorRooms.value.find((f) => f.floor === floor);
|
||||
if (!floorData) return 0;
|
||||
return floorData.rooms.filter((room: any) => completedRooms.value.has(room.id)).length;
|
||||
};
|
||||
|
||||
// 获取宿舍楼名称
|
||||
const getBuildingName = () => {
|
||||
const building = buildingOptions.value.find((b) => b.id === form.buildingId);
|
||||
return building ? getBuildingDisplayName(building) : '';
|
||||
};
|
||||
|
||||
// 宿舍楼显示名称
|
||||
const getBuildingDisplayName = (building: any) => {
|
||||
let displayName = building.buildingName;
|
||||
if (building.buildingType) {
|
||||
if (building.buildingType === '1') {
|
||||
displayName = `(男)${displayName}`;
|
||||
} else if (building.buildingType === '2') {
|
||||
displayName = `(女)${displayName}`;
|
||||
}
|
||||
}
|
||||
return displayName;
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
initForm();
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
initForm();
|
||||
initDict();
|
||||
loadCampusOptions();
|
||||
loadCheckerOptions();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.room-stats {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.floors-container {
|
||||
/* 移除高度限制,让内容自然撑开 */
|
||||
}
|
||||
|
||||
.floor-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.floor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.floor-header h4 {
|
||||
margin: 0;
|
||||
color: #409eff;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.floor-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.floor-stats {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.rooms-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.room-card {
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.room-card:hover {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.room-card.pending {
|
||||
border-color: #e4e7ed;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.room-card.completed {
|
||||
border-color: #67c23a;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
|
||||
.room-card-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.room-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.room-status {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.pending-icon {
|
||||
color: #909399;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.completed-icon {
|
||||
color: #67c23a;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.room-hint {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.completed-text {
|
||||
color: #67c23a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pending-text {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
+1418
File diff suppressed because it is too large.
Load diff
+995
@@ -0,0 +1,995 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" append-to-body @close="handleClose">
|
||||
<el-form ref="violationFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<!-- 学生信息显示 -->
|
||||
<el-form-item label="学生信息">
|
||||
<div class="student-info-display">
|
||||
<span class="student-name">{{ studentInfo?.studentName }}</span>
|
||||
<span class="student-detail">({{ studentInfo?.studentNo }})</span>
|
||||
<span class="student-detail">{{ studentInfo?.className }}</span>
|
||||
<span class="student-detail">{{ studentInfo?.bedName }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 违纪类型选择 -->
|
||||
<el-form-item label="违纪类型" prop="disciplineTypeId" class="discipline-form-item">
|
||||
<el-select v-model="form.disciplineTypeId" placeholder="请选择违纪类型" style="width: 100%" @change="handleDisciplineTypeChange" filterable>
|
||||
<el-option v-for="item in allViolationTypes" :key="item.id" :label="item.typeName" :value="item.id" :disabled="item.status !== '0'">
|
||||
<div class="violation-type-option">
|
||||
<span class="violation-type-name" :title="item.typeName">{{ getShortTypeName(item.typeName) }}</span>
|
||||
<span class="score-range">{{ item.minScore }}-{{ item.maxScore }}分</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 违纪类型说明展示 -->
|
||||
<div v-if="form.disciplineTypeId && selectedDisciplineTypeRemark" class="discipline-type-remark">
|
||||
<el-icon class="remark-icon"><InfoFilled /></el-icon>
|
||||
<span class="remark-text">类型说明:{{ selectedDisciplineTypeRemark }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 违纪日期和扣分分值 - 同一行显示 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="违纪日期" prop="disciplineDate">
|
||||
<el-date-picker
|
||||
v-model="form.disciplineDate"
|
||||
type="date"
|
||||
placeholder="选择违纪日期"
|
||||
style="width: 100%"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="扣分分值" prop="scoreDeduction">
|
||||
<el-input-number v-model="form.scoreDeduction" :precision="2" placeholder="请输入扣分" style="width: 100%" />
|
||||
<!-- 违纪类型参考范围提示 -->
|
||||
<div v-if="form.disciplineTypeId" class="score-range-reference" style="font-size: 12px; color: #999; margin-top: 4px">
|
||||
参考范围:{{ currentTypeMinScore }} - {{ currentTypeMaxScore }}分
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 扣分范围提示 -->
|
||||
<!-- <el-row v-if="currentTypeMinScore && currentTypeMaxScore">
|
||||
<el-col :span="24">
|
||||
<div class="form-tip score-tip">
|
||||
<el-text size="small" type="info"> 当前违纪类型扣分范围:{{ currentTypeMinScore }} - {{ currentTypeMaxScore }}分 </el-text>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row> -->
|
||||
|
||||
<!-- 违纪原因 -->
|
||||
<el-form-item label="违纪原因" prop="disciplineReason">
|
||||
<el-input v-model="form.disciplineReason" type="textarea" :rows="3" placeholder="请输入违纪原因" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 违纪内容 -->
|
||||
<el-form-item label="处理要求" prop="disciplineContent">
|
||||
<el-input v-model="form.disciplineContent" type="textarea" :rows="2" placeholder="请输入处理要求或整改措施" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 附件上传 -->
|
||||
<el-form-item label="相关附件">
|
||||
<div class="violation-upload-container">
|
||||
<el-upload
|
||||
:file-list="attachmentList"
|
||||
class="upload-demo"
|
||||
drag
|
||||
list-type="picture-card"
|
||||
:on-preview="handleFilePreview"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:data="uploadData"
|
||||
:auto-upload="true"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:on-remove="handleRemoveAttachmentConfirm"
|
||||
:on-change="handleFileChange"
|
||||
multiple
|
||||
:limit="5"
|
||||
:show-file-list="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传5个文件。
|
||||
<span class="el-upload__tip" style="color: #ff0000">(文件会立即上传并关联到当前违纪记录)</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 调整拖拽上传图标与文字在同一行、居中对齐 -->
|
||||
<div class="el-upload-dragger1">
|
||||
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义文件卡片缩略图(含文件名) -->
|
||||
<template #file="{ file }">
|
||||
<div class="upload-file-card">
|
||||
<!-- 缩略图区域 -->
|
||||
<div class="upload-file-thumb">
|
||||
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
|
||||
<div v-else class="upload-file-placeholder">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
||||
</div>
|
||||
<!-- loading 遮罩只在上传中显示 -->
|
||||
<div v-if="file.status === 'uploading'" class="upload-file-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span class="loading-text">上传中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件名称 -->
|
||||
<div class="upload-file-name" :title="file.name">
|
||||
{{ getShortFileName(file.name) }}
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮区域 - 固定在文件名下方 -->
|
||||
<div class="upload-file-card-actions" v-if="file.status && file.status !== 'ready'">
|
||||
<el-button-group size="small">
|
||||
<el-button type="success" @click.stop="handleDownloadAttachment(file)" title="下载" :disabled="file.status !== 'success'">
|
||||
<el-icon><Download /></el-icon>
|
||||
</el-button>
|
||||
<el-button type="danger" @click.stop="handleRemoveAttachmentConfirm(file)" title="删除">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm" :loading="buttonLoading"> 确 定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
|
||||
// import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 组件属性
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
studentInfo: any;
|
||||
violationData?: any;
|
||||
title: string;
|
||||
}
|
||||
|
||||
// 组件事件
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'confirm', data: any): void;
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
studentInfo: null,
|
||||
violationData: null,
|
||||
title: '违纪记录录入'
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const violationFormRef = ref();
|
||||
const buttonLoading = ref(false);
|
||||
const attachmentList = ref<any[]>([]);
|
||||
|
||||
// 所有违纪类型列表
|
||||
const allViolationTypes = ref<any[]>([]);
|
||||
|
||||
// 当前选择的违纪类型扣分范围
|
||||
const currentTypeMinScore = ref(0);
|
||||
const currentTypeMaxScore = ref(100);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
studentId: '',
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
collegeId: '',
|
||||
collegeName: '',
|
||||
majorId: '',
|
||||
majorName: '',
|
||||
classId: '',
|
||||
className: '',
|
||||
disciplineTypeId: '',
|
||||
disciplineTypeName: '',
|
||||
disciplineDate: '',
|
||||
disciplineReason: '',
|
||||
disciplineContent: '',
|
||||
scoreDeduction: 0,
|
||||
attachmentOssIds: '',
|
||||
violationSource: '1'
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
disciplineTypeId: [{ required: true, message: '请选择违纪类型', trigger: 'change' }],
|
||||
disciplineDate: [{ required: true, message: '请选择违纪日期', trigger: 'change' }],
|
||||
scoreDeduction: [{ required: true, message: '请输入扣分分值', trigger: 'blur' }],
|
||||
disciplineReason: [{ required: true, message: '请输入违纪原因', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => props.title);
|
||||
|
||||
// 上传配置
|
||||
const uploadAction = computed(() => {
|
||||
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
|
||||
});
|
||||
|
||||
const uploadHeaders = computed(() => {
|
||||
return {
|
||||
'Authorization': `Bearer ${getToken() || ''}`,
|
||||
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
|
||||
};
|
||||
});
|
||||
|
||||
const uploadData = computed(() => {
|
||||
return {
|
||||
bizType: 'dorm_violation_attachment'
|
||||
};
|
||||
});
|
||||
|
||||
// 获取所有违纪类型
|
||||
const getAllViolationTypes = async () => {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/disciplineType/disciplineType/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
status: '0' // 启用状态
|
||||
}
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
// 修复:使用 response.rows 而不是 response.data
|
||||
const violationTypes = response.rows || [];
|
||||
allViolationTypes.value = violationTypes;
|
||||
console.log('获取违纪类型成功,数量:', allViolationTypes.value.length);
|
||||
|
||||
if (violationTypes.length === 0) {
|
||||
ElMessage.warning('暂无可用的违纪类型数据');
|
||||
}
|
||||
} else {
|
||||
allViolationTypes.value = [];
|
||||
ElMessage.error(response.msg || '获取违纪类型失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取违纪类型失败:', error);
|
||||
ElMessage.error('获取违纪类型失败,请检查网络连接或联系管理员');
|
||||
allViolationTypes.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 违纪类型变化处理
|
||||
const handleDisciplineTypeChange = (typeId: string | number) => {
|
||||
const selectedType = allViolationTypes.value.find((item) => item.id === typeId);
|
||||
if (selectedType) {
|
||||
form.disciplineTypeName = selectedType.typeName;
|
||||
|
||||
// 处理BigDecimal类型转换,仅用于显示参考范围
|
||||
let minScore = 0;
|
||||
let maxScore = 100;
|
||||
|
||||
if (selectedType.minScore !== null && selectedType.minScore !== undefined) {
|
||||
minScore = typeof selectedType.minScore === 'number' ? selectedType.minScore : parseFloat(selectedType.minScore.toString()) || 0;
|
||||
}
|
||||
|
||||
if (selectedType.maxScore !== null && selectedType.maxScore !== undefined) {
|
||||
maxScore = typeof selectedType.maxScore === 'number' ? selectedType.maxScore : parseFloat(selectedType.maxScore.toString()) || 100;
|
||||
}
|
||||
|
||||
// 设置参考范围(仅用于显示)
|
||||
currentTypeMinScore.value = minScore;
|
||||
currentTypeMaxScore.value = maxScore;
|
||||
|
||||
// 如果当前扣分为0,可以设置为推荐的最低分
|
||||
if (form.scoreDeduction === 0) {
|
||||
form.scoreDeduction = minScore;
|
||||
}
|
||||
} else {
|
||||
// 重置为默认值
|
||||
currentTypeMinScore.value = 0;
|
||||
currentTypeMaxScore.value = 100;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const initForm = () => {
|
||||
if (props.studentInfo) {
|
||||
Object.assign(form, {
|
||||
studentId: props.studentInfo.studentId,
|
||||
studentNo: props.studentInfo.studentNo,
|
||||
studentName: props.studentInfo.studentName,
|
||||
collegeId: props.studentInfo.collegeId || '',
|
||||
collegeName: props.studentInfo.collegeName || '',
|
||||
majorId: props.studentInfo.majorId || '',
|
||||
majorName: props.studentInfo.majorName || '',
|
||||
classId: props.studentInfo.classId || '',
|
||||
className: props.studentInfo.className || '',
|
||||
disciplineDate: new Date().toISOString().split('T')[0], // 默认今天
|
||||
violationSource: '1'
|
||||
});
|
||||
}
|
||||
|
||||
if (props.violationData) {
|
||||
console.log('违纪记录:编辑模式,violationData:', props.violationData);
|
||||
Object.assign(form, props.violationData);
|
||||
|
||||
// 如果是编辑模式,处理附件数据
|
||||
if (props.violationData.attachmentOssIds) {
|
||||
console.log('违纪记录:发现附件数据,准备回显:', props.violationData.attachmentOssIds);
|
||||
handleAttachmentData(props.violationData.attachmentOssIds);
|
||||
} else {
|
||||
console.log('违纪记录:编辑模式但无附件数据');
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} else {
|
||||
console.log('违纪记录:新增模式,重置附件列表');
|
||||
// 重置附件列表
|
||||
attachmentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 处理附件数据
|
||||
const handleAttachmentData = async (ossIds: string) => {
|
||||
try {
|
||||
if (!ossIds || !ossIds.trim()) {
|
||||
console.log('违纪记录:没有附件数据,清空附件列表');
|
||||
attachmentList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('违纪记录:开始处理附件数据:', ossIds);
|
||||
|
||||
const ossIdsArray = ossIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
if (ossIdsArray.length === 0) {
|
||||
attachmentList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
console.log('违纪记录:获取到的附件信息:', response.data);
|
||||
|
||||
const attachmentFiles = response.data.map((item: any, index: number) => ({
|
||||
uid: `existing_${index}_${Date.now()}`,
|
||||
name: item.originalName || `附件${index + 1}`,
|
||||
url: item.url,
|
||||
ossId: item.ossId,
|
||||
status: 'success',
|
||||
response: {
|
||||
code: 200,
|
||||
data: {
|
||||
url: item.url,
|
||||
ossId: item.ossId
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
attachmentList.value = attachmentFiles;
|
||||
console.log('违纪记录:设置附件列表成功:', attachmentList.value);
|
||||
} else {
|
||||
console.warn('违纪记录:获取附件信息失败:', response.msg);
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('违纪记录:处理附件数据失败:', error);
|
||||
attachmentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 文件上传相关方法
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const isValidType = /\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx|txt|mp4|avi|mov)$/i.test(file.name);
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
|
||||
if (!isValidType) {
|
||||
ElMessage.error('上传文件格式不正确!');
|
||||
return false;
|
||||
}
|
||||
if (!isLt10M) {
|
||||
ElMessage.error('上传文件大小不能超过 10MB!');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUploadSuccess = (response: any, file: any) => {
|
||||
if (response.code === 200) {
|
||||
file.url = response.data.url;
|
||||
file.ossId = response.data.ossId;
|
||||
ElMessage.success('文件上传成功');
|
||||
} else {
|
||||
ElMessage.error(response.msg || '文件上传失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadError = (error: any) => {
|
||||
console.error('文件上传失败:', error);
|
||||
ElMessage.error('文件上传失败');
|
||||
};
|
||||
|
||||
// 文件类型判断
|
||||
const isImageFile = (file: any) => {
|
||||
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
|
||||
return imageTypes.includes(file.raw?.type) || (file.url && /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name));
|
||||
};
|
||||
|
||||
// 获取文件OSS ID的辅助函数
|
||||
const getFileOssId = (file: any) => {
|
||||
return file.ossId || '';
|
||||
};
|
||||
|
||||
// 获取文件扩展名
|
||||
const getFileExt = (fileName: string) => {
|
||||
const ext = fileName.split('.').pop()?.toUpperCase();
|
||||
return ext || 'FILE';
|
||||
};
|
||||
|
||||
// 获取缩写的文件名(保留后缀)
|
||||
const getShortFileName = (fileName: string) => {
|
||||
if (!fileName) return '';
|
||||
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex === -1) {
|
||||
return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
|
||||
}
|
||||
|
||||
const name = fileName.substring(0, lastDotIndex);
|
||||
const ext = fileName.substring(lastDotIndex);
|
||||
|
||||
if (name.length <= 8) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
return name.substring(0, 8) + '...' + ext;
|
||||
};
|
||||
|
||||
// 获取缩写的违纪类型名称
|
||||
const getShortTypeName = (typeName: string) => {
|
||||
if (!typeName) return '';
|
||||
|
||||
// 根据字符长度进行截取,中文字符按2个字符计算
|
||||
const maxLength = 12;
|
||||
let currentLength = 0;
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < typeName.length; i++) {
|
||||
const char = typeName[i];
|
||||
// 判断是否为中文字符
|
||||
const isChinese = /[\u4e00-\u9fa5]/.test(char);
|
||||
currentLength += isChinese ? 2 : 1;
|
||||
|
||||
if (currentLength > maxLength) {
|
||||
result += '...';
|
||||
break;
|
||||
}
|
||||
result += char;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// 文件预览
|
||||
const handleFilePreview = (file: any) => {
|
||||
if (isImageFile(file)) {
|
||||
window.open(file.url);
|
||||
} else {
|
||||
handleDownloadAttachment(file);
|
||||
}
|
||||
};
|
||||
|
||||
// 文件变更
|
||||
const handleFileChange = (file: any, fileList: any) => {
|
||||
console.log('文件变更:', file, fileList);
|
||||
attachmentList.value = fileList;
|
||||
};
|
||||
|
||||
// 删除附件确认
|
||||
const handleRemoveAttachmentConfirm = (file: any) => {
|
||||
if (confirm('确定要删除这个文件吗?')) {
|
||||
handleRemoveAttachment(file);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除附件
|
||||
const handleRemoveAttachment = (file: any) => {
|
||||
const index = attachmentList.value.findIndex((item: any) => item.uid === file.uid);
|
||||
if (index > -1) {
|
||||
attachmentList.value.splice(index, 1);
|
||||
ElMessage.success('文件删除成功');
|
||||
}
|
||||
};
|
||||
|
||||
// 下载附件
|
||||
const handleDownloadAttachment = async (file: any) => {
|
||||
let downloadUrl = '';
|
||||
let fileName = file.name || 'attachment';
|
||||
|
||||
if (file.response && file.response.data && file.response.data.filePath) {
|
||||
downloadUrl = file.response.data.filePath;
|
||||
fileName = file.response.data.fileName || fileName;
|
||||
} else if (file.ossId) {
|
||||
downloadUrl = file.ossId;
|
||||
} else if (file.raw) {
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(file.raw);
|
||||
link.download = fileName;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(link.href);
|
||||
console.log('下载本地文件:', fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!downloadUrl) {
|
||||
ElMessage.error('无法获取文件下载地址');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + downloadUrl,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
let blobData: Blob;
|
||||
if (response instanceof Blob) {
|
||||
blobData = response;
|
||||
} else if (response.data instanceof Blob) {
|
||||
blobData = response.data;
|
||||
} else {
|
||||
throw new Error('无效的响应数据格式');
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blobData);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
console.log('下载服务器文件成功:', fileName);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
ElMessage.error('下载文件失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 确认提交
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
await violationFormRef.value?.validate();
|
||||
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 处理附件数据
|
||||
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
||||
form.attachmentOssIds = attachmentOssIds.join(',');
|
||||
|
||||
emit('confirm', { ...form });
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
ElMessage.error('请检查表单填写是否完整');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
studentId: '',
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
collegeId: '',
|
||||
collegeName: '',
|
||||
majorId: '',
|
||||
majorName: '',
|
||||
classId: '',
|
||||
className: '',
|
||||
disciplineTypeId: '',
|
||||
disciplineTypeName: '',
|
||||
disciplineDate: '',
|
||||
disciplineReason: '',
|
||||
disciplineContent: '',
|
||||
scoreDeduction: 0,
|
||||
attachmentOssIds: '',
|
||||
violationSource: '1'
|
||||
});
|
||||
|
||||
attachmentList.value = [];
|
||||
currentTypeMinScore.value = 0;
|
||||
currentTypeMaxScore.value = 100;
|
||||
};
|
||||
|
||||
// 获取违纪类型说明
|
||||
const selectedDisciplineTypeRemark = computed(() => {
|
||||
if (form.disciplineTypeId) {
|
||||
const selectedType = allViolationTypes.value.find((item) => item.id === form.disciplineTypeId);
|
||||
return selectedType ? selectedType.remark : '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
initForm();
|
||||
getAllViolationTypes();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
getAllViolationTypes();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-tip {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.score-tip {
|
||||
margin-top: -10px;
|
||||
margin-bottom: 15px;
|
||||
margin-left: 120px; /* 对齐表单标签 */
|
||||
}
|
||||
|
||||
.student-info-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.student-detail {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.violation-type-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 100%; /* 使用全宽度 */
|
||||
}
|
||||
|
||||
.violation-type-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 16px; /* 增加与分数的间距 */
|
||||
max-width: calc(100% - 80px); /* 动态计算最大宽度,为分数预留空间 */
|
||||
}
|
||||
|
||||
.score-range {
|
||||
color: #8492a6;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
min-width: 70px; /* 增加分数范围的最小宽度 */
|
||||
text-align: right;
|
||||
margin-left: auto; /* 强制靠右对齐 */
|
||||
padding-left: 8px; /* 增加左侧间距 */
|
||||
}
|
||||
|
||||
.discipline-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.discipline-type-remark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0;
|
||||
margin-left: -120px;
|
||||
padding-left: 120px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
padding-right: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.remark-icon {
|
||||
margin-right: 6px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 违纪类型选择框样式优化 */
|
||||
:deep(.el-select-dropdown__item) {
|
||||
padding: 8px 12px !important;
|
||||
height: auto !important;
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item .violation-type-option) {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
min-width: 280px !important; /* 确保有足够宽度 */
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item .score-range) {
|
||||
margin-left: auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
text-align: right !important;
|
||||
padding-left: 12px !important;
|
||||
}
|
||||
|
||||
/* 附件上传容器样式 */
|
||||
.violation-upload-container {
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 附件上传区域内容样式 */
|
||||
.violation-upload-container .upload-demo {
|
||||
padding: 12px;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .el-upload {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .el-upload__tip {
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
/* 调整拖拽上传图标与文字在同一行、居中对齐 */
|
||||
.violation-upload-container .upload-demo .el-upload-dragger1 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .el-upload-dragger1 .el-icon--upload {
|
||||
margin: 0;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
/* 文件卡片占位封面样式 */
|
||||
.violation-upload-container .upload-demo .upload-file-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .upload-file-placeholder .el-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.violation-upload-container .upload-demo .upload-file-placeholder .file-ext {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 让附件卡片一行展示 3 个 */
|
||||
.violation-upload-container :deep(.el-upload-list--picture-card) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 9px;
|
||||
margin-top: 12px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 确保卡片宽度自适应 */
|
||||
.violation-upload-container :deep(.el-upload-list--picture-card) .el-upload-list__item {
|
||||
margin: 0;
|
||||
height: 180px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
border: 0px !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) .el-upload-dragger1 {
|
||||
border: 0px !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
/* 文件卡片样式 */
|
||||
.upload-file-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.upload-file-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.upload-file-thumb {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-thumb-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.upload-file-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.upload-file-name {
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
line-height: 1.2;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
word-break: break-all;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.upload-file-card-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
transition: all 0.3s;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.upload-file-card-actions .el-button-group {
|
||||
background: transparent;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.upload-file-card-actions .el-button {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
margin: 0 2px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.upload-file-card-actions .el-button:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.upload-file-card-actions .el-button.is-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
@@ -137,17 +137,35 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@update:page="(val) => (queryParams.pageNum = val)"
|
||||
@update:limit="(val) => (queryParams.pageSize = val)"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 宿舍卫生检查弹窗组件 -->
|
||||
<DormHygieneCheckDialog
|
||||
:visible="dialog.visible"
|
||||
:title="dialog.title"
|
||||
:form-data="form"
|
||||
@submit="handleDialogSubmit"
|
||||
@close="handleDialogClose"
|
||||
@update:visible="(value) => (dialog.visible = value)"
|
||||
<!-- 宿舍卫生检查主弹窗组件(新增使用) -->
|
||||
<DormHygieneCheckMainDialog
|
||||
:visible="mainDialog.visible"
|
||||
:title="mainDialog.title"
|
||||
@submit="handleMainDialogSubmit"
|
||||
@close="handleMainDialogClose"
|
||||
@update:visible="(value) => (mainDialog.visible = value)"
|
||||
/>
|
||||
|
||||
<!-- 单个宿舍房间检查弹窗组件(修改使用) -->
|
||||
<DormHygieneCheckSingleDialog
|
||||
:visible="singleDialog.visible"
|
||||
:title="singleDialog.title"
|
||||
:room-info="currentEditData?.roomInfo"
|
||||
:check-info="currentEditData"
|
||||
@submit="handleSingleDialogSubmit"
|
||||
@close="handleSingleDialogClose"
|
||||
@update:visible="(value) => (singleDialog.visible = value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -171,7 +189,8 @@ import {
|
||||
DormHygieneCheckForm,
|
||||
DormHygieneCheckCompleteForm
|
||||
} from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||
import DormHygieneCheckDialog from './DormHygieneCheckDialog.vue';
|
||||
import DormHygieneCheckMainDialog from './DormHygieneCheckMainDialog.vue';
|
||||
import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue';
|
||||
|
||||
// 类型定义
|
||||
interface DialogOption {
|
||||
@@ -200,11 +219,21 @@ const total = ref(0);
|
||||
|
||||
const queryFormRef = ref();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
// 主弹窗(新增)
|
||||
const mainDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 单个房间检查弹窗(修改)
|
||||
const singleDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
// 存储当前编辑的数据
|
||||
const currentEditData = ref<any>(null);
|
||||
|
||||
const initFormData: DormHygieneCheckForm = {
|
||||
id: undefined,
|
||||
deleted: undefined,
|
||||
@@ -287,22 +316,39 @@ const getList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 弹窗关闭事件处理 */
|
||||
const handleDialogClose = () => {
|
||||
dialog.visible = false;
|
||||
/** 主弹窗关闭事件处理 */
|
||||
const handleMainDialogClose = () => {
|
||||
mainDialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 弹窗提交事件处理 */
|
||||
const handleDialogSubmit = async (formData: DormHygieneCheckCompleteForm) => {
|
||||
/** 单个房间检查弹窗关闭事件处理 */
|
||||
const handleSingleDialogClose = () => {
|
||||
singleDialog.visible = false;
|
||||
currentEditData.value = null;
|
||||
};
|
||||
|
||||
/** 主弹窗提交事件处理 */
|
||||
const handleMainDialogSubmit = async (formData: any) => {
|
||||
try {
|
||||
if (formData.id) {
|
||||
await updateDormHygieneCheck(formData);
|
||||
} else {
|
||||
// 主弹窗提交的是新增数据
|
||||
await addDormHygieneCheck(formData);
|
||||
}
|
||||
ElMessage.success('操作成功');
|
||||
dialog.visible = false;
|
||||
mainDialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 单个房间检查弹窗提交事件处理 */
|
||||
const handleSingleDialogSubmit = async (formData: any) => {
|
||||
try {
|
||||
// 单个房间检查弹窗提交的是修改数据
|
||||
await updateDormHygieneCheck(formData);
|
||||
ElMessage.success('操作成功');
|
||||
singleDialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
@@ -344,14 +390,14 @@ const handleSelectionChange = (selection: DormHygieneCheckVO[]) => {
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加宿舍卫生检查';
|
||||
mainDialog.visible = true;
|
||||
mainDialog.title = '添加宿舍卫生检查';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: DormHygieneCheckVO) => {
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改宿舍卫生检查';
|
||||
singleDialog.visible = true;
|
||||
singleDialog.title = '修改宿舍卫生检查';
|
||||
|
||||
const loadingInstance = ElLoading.service({
|
||||
target: '.el-dialog__body',
|
||||
@@ -360,14 +406,13 @@ const handleUpdate = async (row?: DormHygieneCheckVO) => {
|
||||
});
|
||||
|
||||
try {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getDormHygieneCheck(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
currentEditData.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('加载编辑数据失败:', error);
|
||||
ElMessage.error('加载数据失败,请重试');
|
||||
dialog.visible = false;
|
||||
singleDialog.visible = false;
|
||||
} finally {
|
||||
loadingInstance.close();
|
||||
}
|
||||
|
||||
@@ -5,17 +5,25 @@
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="违纪来源" prop="violationSource">
|
||||
<el-select v-model="queryParams.violationSource" placeholder="请选择违纪来源" clearable>
|
||||
<el-option label="宿舍卫生检查" value="1" />
|
||||
<el-option label="其他来源" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in student_discipline_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
@@ -81,6 +89,13 @@
|
||||
<el-table-column label="专业名称" align="center" prop="majorName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="班级名称" align="center" prop="className" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="违纪处分类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="违纪来源" align="center" prop="violationSource" min-width="120">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.violationSource === '1'" type="warning" size="small">宿舍卫生检查</el-tag>
|
||||
<el-tag v-else-if="scope.row.violationSource === '2'" type="info" size="small">其他来源</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="违纪处分日期" align="center" prop="disciplineDate" min-width="130" />
|
||||
<el-table-column label="扣分分值" align="center" prop="scoreDeduction" min-width="100">
|
||||
<template #default="scope">
|
||||
@@ -202,6 +217,7 @@ const data = reactive<PageData<DisciplineRecordForm, DisciplineRecordQuery>>({
|
||||
disciplineReason: undefined,
|
||||
disciplineContent: undefined,
|
||||
scoreDeduction: undefined,
|
||||
violationSource: undefined, // 新增违纪来源字段
|
||||
status: undefined,
|
||||
approvalStatus: undefined,
|
||||
approvalBy: undefined,
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="区域类型" prop="areaType">
|
||||
<el-select v-model="queryParams.areaType" placeholder="请选择区域类型" clearable>
|
||||
<el-option v-for="dict in discipline_area_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
@@ -90,6 +99,19 @@
|
||||
<dict-tag :options="student_discipline_category" :value="scope.row.typeCategory" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="区域类型" align="center" prop="areaType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="discipline_area_type" :value="scope.row.areaType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="扣分范围" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag type="info" size="small" v-if="scope.row.minScore !== undefined && scope.row.maxScore !== undefined">
|
||||
{{ scope.row.minScore }} - {{ scope.row.maxScore }}分
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型说明" align="center" prop="remark" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
@@ -138,6 +160,34 @@
|
||||
<el-option v-for="dict in student_discipline_category" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="区域类型" prop="areaType">
|
||||
<el-radio-group v-model="form.areaType">
|
||||
<el-radio v-for="dict in discipline_area_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tip">
|
||||
<el-text size="small" type="info">宿舍区违纪适用于宿舍卫生检查,教学区违纪适用于其他场景</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="扣分范围">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="10">
|
||||
<el-form-item prop="minScore" label-width="0">
|
||||
<el-input v-model="form.minScore" placeholder="最低扣分" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" style="text-align: center; line-height: 32px">
|
||||
<span>至</span>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item prop="maxScore" label-width="0">
|
||||
<el-input v-model="form.maxScore" placeholder="最高扣分" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-tip">
|
||||
<el-text size="small" type="info">录入违纪记录时,扣分必须在此范围内</el-text>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型说明" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
@@ -164,6 +214,7 @@
|
||||
</template>
|
||||
|
||||
<script setup name="DisciplineType" lang="ts">
|
||||
import { ref, reactive, toRefs, getCurrentInstance, onMounted, ComponentInternalInstance } from 'vue';
|
||||
import {
|
||||
listDisciplineType,
|
||||
getDisciplineType,
|
||||
@@ -174,7 +225,9 @@ import {
|
||||
import { DisciplineTypeVO, DisciplineTypeQuery, DisciplineTypeForm } from '@/api/xskh/disciplineType/disciplineType/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { student_discipline_category, sys_msg_status } = toRefs<any>(proxy?.useDict('student_discipline_category', 'sys_msg_status'));
|
||||
const { student_discipline_category, sys_msg_status, discipline_area_type } = toRefs<any>(
|
||||
proxy?.useDict('student_discipline_category', 'sys_msg_status', 'discipline_area_type')
|
||||
);
|
||||
|
||||
const disciplineTypeList = ref<DisciplineTypeVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
@@ -185,8 +238,8 @@ const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const disciplineTypeFormRef = ref<ElFormInstance>();
|
||||
const queryFormRef = ref();
|
||||
const disciplineTypeFormRef = ref();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
@@ -198,6 +251,9 @@ const initFormData: DisciplineTypeForm = {
|
||||
typeCode: undefined,
|
||||
typeName: undefined,
|
||||
typeCategory: undefined,
|
||||
areaType: '1', // 默认宿舍区违纪
|
||||
minScore: 0, // 默认最低扣分
|
||||
maxScore: 100, // 默认最高扣分
|
||||
remark: undefined,
|
||||
status: undefined,
|
||||
sortOrder: undefined,
|
||||
@@ -211,6 +267,7 @@ const data = reactive<PageData<DisciplineTypeForm, DisciplineTypeQuery>>({
|
||||
typeCode: undefined,
|
||||
typeName: undefined,
|
||||
typeCategory: undefined,
|
||||
areaType: undefined,
|
||||
status: undefined,
|
||||
sortOrder: undefined,
|
||||
deleted: undefined,
|
||||
@@ -220,7 +277,49 @@ const data = reactive<PageData<DisciplineTypeForm, DisciplineTypeQuery>>({
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
typeCode: [{ required: true, message: '类型编码不能为空', trigger: 'blur' }],
|
||||
typeName: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }],
|
||||
typeCategory: [{ required: true, message: '类型分类(1-违纪类型 2-处分类型)不能为空', trigger: 'change' }]
|
||||
typeCategory: [{ required: true, message: '类型分类(1-违纪类型 2-处分类型)不能为空', trigger: 'change' }],
|
||||
areaType: [{ required: true, message: '请选择区域类型', trigger: 'change' }],
|
||||
minScore: [
|
||||
{ required: true, message: '请输入最低扣分', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/,
|
||||
message: '请输入有效的数字(支持最多2位小数)',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
const numValue = parseFloat(value);
|
||||
if (isNaN(numValue) || numValue < 0 || numValue > 100) {
|
||||
callback(new Error('最低扣分必须在0-100之间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
maxScore: [
|
||||
{ required: true, message: '请输入最高扣分', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/,
|
||||
message: '请输入有效的数字(支持最多2位小数)',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
const numValue = parseFloat(value);
|
||||
const minValue = parseFloat(form.value.minScore as any);
|
||||
if (isNaN(numValue) || numValue < 0 || numValue > 100) {
|
||||
callback(new Error('最高扣分必须在0-100之间'));
|
||||
} else if (!isNaN(minValue) && numValue < minValue) {
|
||||
callback(new Error('最高扣分不能小于最低扣分'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -335,9 +434,14 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表单提示样式 */
|
||||
.form-tip {
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user