Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project
This commit is contained in:
commit
1155a976b9
49 files changed
+3227
-424
No files matched your search
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package org.dromara.xg.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.xg.domain.bo.ClassTeacherDormQueryBo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherClassInfoVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormRollCallVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormViolationVo;
|
||||||
|
import org.dromara.xg.service.IXgClassTeacherDormViewService;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任宿舍情况查看(违纪 + 点名,只读)
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/classTeacherDorm/classTeacherDorm")
|
||||||
|
public class XgClassTeacherDormViewController extends BaseController {
|
||||||
|
|
||||||
|
private final IXgClassTeacherDormViewService classTeacherDormViewService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前班主任所带班级信息
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("classTeacherDorm:classTeacherDorm:query")
|
||||||
|
@GetMapping("/myClass")
|
||||||
|
public R<ClassTeacherClassInfoVo> myClass() {
|
||||||
|
return R.ok(classTeacherDormViewService.getMyClassInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本班宿舍违纪记录(卫生检查 + 宿舍违纪登记)
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("classTeacherDorm:classTeacherDorm:query")
|
||||||
|
@GetMapping("/violation/list")
|
||||||
|
public TableDataInfo<ClassTeacherDormViolationVo> violationList(ClassTeacherDormQueryBo bo, PageQuery pageQuery) {
|
||||||
|
return classTeacherDormViewService.queryViolationPage(bo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本班宿舍点名明细
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("classTeacherDorm:classTeacherDorm:query")
|
||||||
|
@GetMapping("/rollCall/list")
|
||||||
|
public TableDataInfo<ClassTeacherDormRollCallVo> rollCallList(ClassTeacherDormQueryBo bo, PageQuery pageQuery) {
|
||||||
|
return classTeacherDormViewService.queryRollCallPage(bo, pageQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package org.dromara.xg.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.dromara.common.tenant.core.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细对象 xg_dorm_hygiene_check_deduction
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("xg_dorm_hygiene_check_deduction")
|
||||||
|
public class XgDormHygieneCheckDeduction extends TenantEntity {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@TableLogic(value = "0", delval = "1")
|
||||||
|
private Long deleted;
|
||||||
|
|
||||||
|
private Long hygieneCheckId;
|
||||||
|
|
||||||
|
private Long disciplineTypeId;
|
||||||
|
|
||||||
|
private String disciplineTypeName;
|
||||||
|
|
||||||
|
private Double score;
|
||||||
|
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package org.dromara.xg.domain.bo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任宿舍情况查询条件
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassTeacherDormQueryBo {
|
||||||
|
|
||||||
|
/** 开始日期 yyyy-MM-dd,默认当天 */
|
||||||
|
private String beginDate;
|
||||||
|
|
||||||
|
/** 结束日期 yyyy-MM-dd,默认当天 */
|
||||||
|
private String endDate;
|
||||||
|
|
||||||
|
/** 学号 */
|
||||||
|
private String studentNo;
|
||||||
|
|
||||||
|
/** 学生姓名 */
|
||||||
|
private String studentName;
|
||||||
|
|
||||||
|
/** 点名状态(宿舍点名 Tab 筛选,字典 xg_dorm_roll_call_status) */
|
||||||
|
private String rollCallStatus;
|
||||||
|
}
|
||||||
+6
@@ -7,6 +7,7 @@ import io.github.linpeilie.annotations.AutoMapper;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import jakarta.validation.constraints.*;
|
import jakarta.validation.constraints.*;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckDeductionBo;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
@@ -159,6 +160,11 @@ public class XgDormHygieneCheckCompleteBo {
|
|||||||
*/
|
*/
|
||||||
private String disciplineTypeName;
|
private String disciplineTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分明细列表
|
||||||
|
*/
|
||||||
|
private List<XgDormHygieneCheckDeductionBo> deductionItems;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学生评分数据列表
|
* 学生评分数据列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package org.dromara.xg.domain.bo;
|
||||||
|
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.dromara.xg.domain.XgDormHygieneCheckDeduction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细业务对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AutoMapper(target = XgDormHygieneCheckDeduction.class, reverseConvertGenerate = false)
|
||||||
|
public class XgDormHygieneCheckDeductionBo {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long hygieneCheckId;
|
||||||
|
|
||||||
|
@NotNull(message = "扣分项类型不能为空")
|
||||||
|
private Long disciplineTypeId;
|
||||||
|
|
||||||
|
private String disciplineTypeName;
|
||||||
|
|
||||||
|
@NotNull(message = "扣分分值不能为空")
|
||||||
|
private Double score;
|
||||||
|
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
-1
@@ -95,7 +95,6 @@ public class XgViolationDataBo {
|
|||||||
/**
|
/**
|
||||||
* 违纪原因
|
* 违纪原因
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "违纪原因不能为空")
|
|
||||||
private String disciplineReason;
|
private String disciplineReason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package org.dromara.xg.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任班级信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassTeacherClassInfoVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long classId;
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
private String classCode;
|
||||||
|
|
||||||
|
private String collegeName;
|
||||||
|
|
||||||
|
private String majorName;
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package org.dromara.xg.domain.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任查看-宿舍点名明细
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassTeacherDormRollCallVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long studentId;
|
||||||
|
|
||||||
|
private String studentNo;
|
||||||
|
|
||||||
|
private String studentName;
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
private String batchNo;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date checkDate;
|
||||||
|
|
||||||
|
private String checkerName;
|
||||||
|
|
||||||
|
private String buildingName;
|
||||||
|
|
||||||
|
private String roomName;
|
||||||
|
|
||||||
|
private String bedCode;
|
||||||
|
|
||||||
|
/** 0在寝 1请假 2不在 3空床位 */
|
||||||
|
private String rollCallStatus;
|
||||||
|
|
||||||
|
private String leaveTypeName;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package org.dromara.xg.domain.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任查看-宿舍违纪记录
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ClassTeacherDormViolationVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** hygiene_check / dorm_violation */
|
||||||
|
private String dataSource;
|
||||||
|
|
||||||
|
/** 来源名称 */
|
||||||
|
private String dataSourceName;
|
||||||
|
|
||||||
|
private Long studentId;
|
||||||
|
|
||||||
|
private String studentNo;
|
||||||
|
|
||||||
|
private String studentName;
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
private String buildingName;
|
||||||
|
|
||||||
|
private String roomName;
|
||||||
|
|
||||||
|
private String violationTypeName;
|
||||||
|
|
||||||
|
private BigDecimal scoreDeduction;
|
||||||
|
|
||||||
|
private String violationLevel;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private Date violationDate;
|
||||||
|
|
||||||
|
private String violationReason;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
+6
@@ -90,6 +90,12 @@ public class DormHygieneCheckExportVo implements Serializable {
|
|||||||
@ExcelProperty(value = "宿舍总分")
|
@ExcelProperty(value = "宿舍总分")
|
||||||
private Double totalScore;
|
private Double totalScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分项明细
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "扣分项")
|
||||||
|
private String disciplineTypeName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卫生等级
|
* 卫生等级
|
||||||
*/
|
*/
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package org.dromara.xg.domain.vo;
|
||||||
|
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.dromara.xg.domain.XgDormHygieneCheckDeduction;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细视图对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AutoMapper(target = XgDormHygieneCheckDeduction.class)
|
||||||
|
public class XgDormHygieneCheckDeductionVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long hygieneCheckId;
|
||||||
|
|
||||||
|
private Long disciplineTypeId;
|
||||||
|
|
||||||
|
private String disciplineTypeName;
|
||||||
|
|
||||||
|
private Double score;
|
||||||
|
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/** 前端展示用:类型最低扣分 */
|
||||||
|
private Double minScore;
|
||||||
|
|
||||||
|
/** 前端展示用:类型最高扣分 */
|
||||||
|
private Double maxScore;
|
||||||
|
}
|
||||||
+7
@@ -13,6 +13,8 @@ import lombok.Data;
|
|||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 宿舍卫生检查视图对象 xg_dorm_hygiene_check
|
* 宿舍卫生检查视图对象 xg_dorm_hygiene_check
|
||||||
@@ -183,4 +185,9 @@ public class XgDormHygieneCheckVo implements Serializable {
|
|||||||
@ExcelProperty(value = "违纪类型名称")
|
@ExcelProperty(value = "违纪类型名称")
|
||||||
private String disciplineTypeName;
|
private String disciplineTypeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分明细列表
|
||||||
|
*/
|
||||||
|
private List<XgDormHygieneCheckDeductionVo> deductionItems;
|
||||||
|
|
||||||
}
|
}
|
||||||
+5
@@ -113,6 +113,11 @@ public class XgDormHygieneStudentScoreVo implements Serializable {
|
|||||||
@ExcelProperty(value = "床位编号")
|
@ExcelProperty(value = "床位编号")
|
||||||
private String bedCode;
|
private String bedCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别(0男 1女,来自xg_student_msg,查询时关联填充)
|
||||||
|
*/
|
||||||
|
private String gender;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学生个人得分(0-100分)
|
* 学生个人得分(0-100分)
|
||||||
*/
|
*/
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package org.dromara.xg.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.dromara.xg.domain.bo.ClassTeacherDormQueryBo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherClassInfoVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormRollCallVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormViolationVo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任宿舍情况查询 Mapper
|
||||||
|
*/
|
||||||
|
public interface XgClassTeacherDormViewMapper {
|
||||||
|
|
||||||
|
ClassTeacherClassInfoVo selectClassByTeacherId(@Param("teacherId") Long teacherId);
|
||||||
|
|
||||||
|
IPage<ClassTeacherDormViolationVo> selectViolationPage(
|
||||||
|
Page<ClassTeacherDormViolationVo> page,
|
||||||
|
@Param("classId") Long classId,
|
||||||
|
@Param("bo") ClassTeacherDormQueryBo bo);
|
||||||
|
|
||||||
|
IPage<ClassTeacherDormRollCallVo> selectRollCallPage(
|
||||||
|
Page<ClassTeacherDormRollCallVo> page,
|
||||||
|
@Param("classId") Long classId,
|
||||||
|
@Param("bo") ClassTeacherDormQueryBo bo);
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package org.dromara.xg.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
import org.dromara.xg.domain.XgDormHygieneCheckDeduction;
|
||||||
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细Mapper接口
|
||||||
|
*/
|
||||||
|
public interface XgDormHygieneCheckDeductionMapper extends BaseMapperPlus<XgDormHygieneCheckDeduction, XgDormHygieneCheckDeductionVo> {
|
||||||
|
|
||||||
|
List<XgDormHygieneCheckDeductionVo> selectVoListByHygieneCheckId(@Param("hygieneCheckId") Long hygieneCheckId);
|
||||||
|
}
|
||||||
+12
@@ -4,6 +4,10 @@ import org.dromara.xg.domain.XgDormHygieneStudentScore;
|
|||||||
import org.dromara.xg.domain.vo.XgDormHygieneStudentScoreVo;
|
import org.dromara.xg.domain.vo.XgDormHygieneStudentScoreVo;
|
||||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 宿舍卫生检查学生评分Mapper接口
|
* 宿舍卫生检查学生评分Mapper接口
|
||||||
*
|
*
|
||||||
@@ -12,4 +16,12 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
|||||||
*/
|
*/
|
||||||
public interface XgDormHygieneStudentScoreMapper extends BaseMapperPlus<XgDormHygieneStudentScore, XgDormHygieneStudentScoreVo> {
|
public interface XgDormHygieneStudentScoreMapper extends BaseMapperPlus<XgDormHygieneStudentScore, XgDormHygieneStudentScoreVo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据卫生检查ID查询学生评分列表(关联学生档案补充性别)
|
||||||
|
*
|
||||||
|
* @param hygieneCheckId 卫生检查ID
|
||||||
|
* @return 学生评分列表
|
||||||
|
*/
|
||||||
|
List<XgDormHygieneStudentScoreVo> selectVoListByHygieneCheckId(@Param("hygieneCheckId") Long hygieneCheckId);
|
||||||
|
|
||||||
}
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package org.dromara.xg.service;
|
||||||
|
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.xg.domain.bo.ClassTeacherDormQueryBo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherClassInfoVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormRollCallVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormViolationVo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任宿舍情况查看 Service
|
||||||
|
*/
|
||||||
|
public interface IXgClassTeacherDormViewService {
|
||||||
|
|
||||||
|
ClassTeacherClassInfoVo getMyClassInfo();
|
||||||
|
|
||||||
|
TableDataInfo<ClassTeacherDormViolationVo> queryViolationPage(ClassTeacherDormQueryBo bo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
TableDataInfo<ClassTeacherDormRollCallVo> queryRollCallPage(ClassTeacherDormQueryBo bo, PageQuery pageQuery);
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package org.dromara.xg.service;
|
||||||
|
|
||||||
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckDeductionBo;
|
||||||
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细Service接口
|
||||||
|
*/
|
||||||
|
public interface IXgDormHygieneCheckDeductionService {
|
||||||
|
|
||||||
|
List<XgDormHygieneCheckDeductionVo> listByHygieneCheckId(Long hygieneCheckId);
|
||||||
|
|
||||||
|
void replaceByHygieneCheckId(Long hygieneCheckId, List<XgDormHygieneCheckDeductionBo> items);
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package org.dromara.xg.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.xg.domain.bo.ClassTeacherDormQueryBo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherClassInfoVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormRollCallVo;
|
||||||
|
import org.dromara.xg.domain.vo.ClassTeacherDormViolationVo;
|
||||||
|
import org.dromara.xg.mapper.XgClassTeacherDormViewMapper;
|
||||||
|
import org.dromara.xg.service.IXgClassTeacherDormViewService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班主任宿舍情况查看 Service 实现
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class XgClassTeacherDormViewServiceImpl implements IXgClassTeacherDormViewService {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
|
private final XgClassTeacherDormViewMapper classTeacherDormViewMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassTeacherClassInfoVo getMyClassInfo() {
|
||||||
|
Long teacherId = LoginHelper.getUserId();
|
||||||
|
ClassTeacherClassInfoVo info = classTeacherDormViewMapper.selectClassByTeacherId(teacherId);
|
||||||
|
if (info == null) {
|
||||||
|
throw new ServiceException("当前账号未绑定班主任班级,无法查看");
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<ClassTeacherDormViolationVo> queryViolationPage(ClassTeacherDormQueryBo bo, PageQuery pageQuery) {
|
||||||
|
Long classId = resolveClassId();
|
||||||
|
normalizeDateRange(bo);
|
||||||
|
Page<ClassTeacherDormViolationVo> page = pageQuery.disableDefaultOrder().build();
|
||||||
|
IPage<ClassTeacherDormViolationVo> result = classTeacherDormViewMapper.selectViolationPage(page, classId, bo);
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<ClassTeacherDormRollCallVo> queryRollCallPage(ClassTeacherDormQueryBo bo, PageQuery pageQuery) {
|
||||||
|
Long classId = resolveClassId();
|
||||||
|
normalizeDateRange(bo);
|
||||||
|
Page<ClassTeacherDormRollCallVo> page = pageQuery.disableDefaultOrder().build();
|
||||||
|
IPage<ClassTeacherDormRollCallVo> result = classTeacherDormViewMapper.selectRollCallPage(page, classId, bo);
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long resolveClassId() {
|
||||||
|
ClassTeacherClassInfoVo info = getMyClassInfo();
|
||||||
|
return info.getClassId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeDateRange(ClassTeacherDormQueryBo bo) {
|
||||||
|
String today = LocalDate.now().format(DATE_FMT);
|
||||||
|
if (StringUtils.isBlank(bo.getEndDate())) {
|
||||||
|
bo.setEndDate(today);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(bo.getBeginDate())) {
|
||||||
|
bo.setBeginDate(today);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package org.dromara.xg.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.utils.MapstructUtils;
|
||||||
|
import org.dromara.xg.domain.XgDormHygieneCheckDeduction;
|
||||||
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckDeductionBo;
|
||||||
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo;
|
||||||
|
import org.dromara.xg.mapper.XgDormHygieneCheckDeductionMapper;
|
||||||
|
import org.dromara.xg.service.IXgDormHygieneCheckDeductionService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 宿舍卫生检查扣分明细Service业务层处理
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class XgDormHygieneCheckDeductionServiceImpl implements IXgDormHygieneCheckDeductionService {
|
||||||
|
|
||||||
|
private final XgDormHygieneCheckDeductionMapper baseMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<XgDormHygieneCheckDeductionVo> listByHygieneCheckId(Long hygieneCheckId) {
|
||||||
|
if (hygieneCheckId == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return baseMapper.selectVoListByHygieneCheckId(hygieneCheckId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void replaceByHygieneCheckId(Long hygieneCheckId, List<XgDormHygieneCheckDeductionBo> items) {
|
||||||
|
if (hygieneCheckId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<XgDormHygieneCheckDeduction> deleteWrapper = Wrappers.lambdaQuery();
|
||||||
|
deleteWrapper.eq(XgDormHygieneCheckDeduction::getHygieneCheckId, hygieneCheckId);
|
||||||
|
baseMapper.delete(deleteWrapper);
|
||||||
|
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int sortOrder = 1;
|
||||||
|
for (XgDormHygieneCheckDeductionBo item : items) {
|
||||||
|
XgDormHygieneCheckDeduction entity = MapstructUtils.convert(item, XgDormHygieneCheckDeduction.class);
|
||||||
|
entity.setId(null);
|
||||||
|
entity.setHygieneCheckId(hygieneCheckId);
|
||||||
|
entity.setSortOrder(item.getSortOrder() != null ? item.getSortOrder() : sortOrder++);
|
||||||
|
baseMapper.insert(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
-3
@@ -14,10 +14,12 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.dromara.xg.domain.bo.XgDormHygieneCheckBo;
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckBo;
|
||||||
import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo;
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckCompleteBo;
|
||||||
import org.dromara.xg.domain.bo.XgDormHygieneStudentScoreBo;
|
import org.dromara.xg.domain.bo.XgDormHygieneStudentScoreBo;
|
||||||
|
import org.dromara.xg.domain.bo.XgDormHygieneCheckDeductionBo;
|
||||||
import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo;
|
import org.dromara.xg.domain.bo.XgHygieneCheckViolationBo;
|
||||||
import org.dromara.xg.domain.bo.XgViolationDataBo;
|
import org.dromara.xg.domain.bo.XgViolationDataBo;
|
||||||
import org.dromara.xg.domain.vo.DormHygieneCheckExportVo;
|
import org.dromara.xg.domain.vo.DormHygieneCheckExportVo;
|
||||||
import org.dromara.xg.domain.vo.XgDormHygieneCheckVo;
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckVo;
|
||||||
|
import org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo;
|
||||||
import org.dromara.xg.domain.XgDormHygieneCheck;
|
import org.dromara.xg.domain.XgDormHygieneCheck;
|
||||||
import org.dromara.xg.domain.XgDormHygieneStudentScore;
|
import org.dromara.xg.domain.XgDormHygieneStudentScore;
|
||||||
import org.dromara.xg.mapper.XgDormHygieneCheckMapper;
|
import org.dromara.xg.mapper.XgDormHygieneCheckMapper;
|
||||||
@@ -26,6 +28,7 @@ import org.dromara.xg.service.IXgDormHygieneCheckService;
|
|||||||
import org.dromara.xg.service.IXgStudentMsgService;
|
import org.dromara.xg.service.IXgStudentMsgService;
|
||||||
import org.dromara.xg.service.IXgHygieneCheckViolationService;
|
import org.dromara.xg.service.IXgHygieneCheckViolationService;
|
||||||
import org.dromara.xg.service.IXgDormHygieneStudentScoreService;
|
import org.dromara.xg.service.IXgDormHygieneStudentScoreService;
|
||||||
|
import org.dromara.xg.service.IXgDormHygieneCheckDeductionService;
|
||||||
import org.dromara.xg.mapper.XgHygieneCheckViolationMapper;
|
import org.dromara.xg.mapper.XgHygieneCheckViolationMapper;
|
||||||
import org.dromara.system.service.ISysDictDataService;
|
import org.dromara.system.service.ISysDictDataService;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
@@ -41,6 +44,10 @@ import java.util.stream.Collectors;
|
|||||||
import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
|
import org.dromara.xg.domain.vo.XgHygieneCheckViolationVo;
|
||||||
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
import org.dromara.xg.domain.XgHygieneCheckViolation;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 宿舍卫生检查Service业务层处理
|
* 宿舍卫生检查Service业务层处理
|
||||||
@@ -60,6 +67,7 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
private final IXgHygieneCheckViolationService violationService;
|
private final IXgHygieneCheckViolationService violationService;
|
||||||
private final IXgDormHygieneStudentScoreService studentScoreService;
|
private final IXgDormHygieneStudentScoreService studentScoreService;
|
||||||
private final XgHygieneCheckViolationMapper violationMapper;
|
private final XgHygieneCheckViolationMapper violationMapper;
|
||||||
|
private final IXgDormHygieneCheckDeductionService deductionService;
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +78,21 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public XgDormHygieneCheckVo queryById(Long id) {
|
public XgDormHygieneCheckVo queryById(Long id) {
|
||||||
return baseMapper.selectVoById(id);
|
XgDormHygieneCheckVo vo = baseMapper.selectVoById(id);
|
||||||
|
if (vo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<XgDormHygieneCheckDeductionVo> deductionItems = deductionService.listByHygieneCheckId(id);
|
||||||
|
if (deductionItems.isEmpty() && vo.getDisciplineTypeId() != null) {
|
||||||
|
XgDormHygieneCheckDeductionVo legacyItem = new XgDormHygieneCheckDeductionVo();
|
||||||
|
legacyItem.setDisciplineTypeId(vo.getDisciplineTypeId());
|
||||||
|
legacyItem.setDisciplineTypeName(vo.getDisciplineTypeName());
|
||||||
|
legacyItem.setScore(vo.getTotalScore());
|
||||||
|
legacyItem.setSortOrder(1);
|
||||||
|
deductionItems = List.of(legacyItem);
|
||||||
|
}
|
||||||
|
vo.setDeductionItems(deductionItems);
|
||||||
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -644,8 +666,10 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Long saveCompleteData(XgDormHygieneCheckCompleteBo bo) {
|
public Long saveCompleteData(XgDormHygieneCheckCompleteBo bo) {
|
||||||
try {
|
try {
|
||||||
|
List<XgDormHygieneCheckDeductionBo> deductionItems = resolveDeductionItems(bo);
|
||||||
// 1. 转换并保存主表数据
|
// 1. 转换并保存主表数据
|
||||||
XgDormHygieneCheck add = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
|
XgDormHygieneCheck add = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
|
||||||
|
applyDeductionSummary(add, deductionItems);
|
||||||
validEntityBeforeSave(add);
|
validEntityBeforeSave(add);
|
||||||
boolean flag = baseMapper.insert(add) > 0;
|
boolean flag = baseMapper.insert(add) > 0;
|
||||||
log.info("保存宿舍卫生检查主表数据: {}", add);
|
log.info("保存宿舍卫生检查主表数据: {}", add);
|
||||||
@@ -657,6 +681,9 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
|
|
||||||
Long hygieneCheckId = add.getId();
|
Long hygieneCheckId = add.getId();
|
||||||
|
|
||||||
|
// 1.1 保存扣分明细
|
||||||
|
deductionService.replaceByHygieneCheckId(hygieneCheckId, deductionItems);
|
||||||
|
|
||||||
// 2. 保存学生评分数据
|
// 2. 保存学生评分数据
|
||||||
if (bo.getStudentScores() != null && !bo.getStudentScores().isEmpty()) {
|
if (bo.getStudentScores() != null && !bo.getStudentScores().isEmpty()) {
|
||||||
for (XgDormHygieneStudentScoreBo studentScoreBo : bo.getStudentScores()) {
|
for (XgDormHygieneStudentScoreBo studentScoreBo : bo.getStudentScores()) {
|
||||||
@@ -901,8 +928,13 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
setFieldValue(disciplineRecordBo, "disciplineDate", java.time.LocalDate.now());
|
setFieldValue(disciplineRecordBo, "disciplineDate", java.time.LocalDate.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置违纪原因和内容
|
// 设置违纪原因和内容(原因为空时使用违纪类型名称兜底,满足下游非空校验)
|
||||||
setFieldValue(disciplineRecordBo, "disciplineReason", violationBo.getDisciplineReason());
|
String disciplineReason = StringUtils.isNotBlank(violationBo.getDisciplineReason())
|
||||||
|
? violationBo.getDisciplineReason().trim()
|
||||||
|
: StringUtils.isNotBlank(violationBo.getDisciplineTypeName())
|
||||||
|
? violationBo.getDisciplineTypeName()
|
||||||
|
: "宿舍卫生检查违纪";
|
||||||
|
setFieldValue(disciplineRecordBo, "disciplineReason", disciplineReason);
|
||||||
setFieldValue(disciplineRecordBo, "disciplineContent", violationBo.getDisciplineContent());
|
setFieldValue(disciplineRecordBo, "disciplineContent", violationBo.getDisciplineContent());
|
||||||
|
|
||||||
// 设置扣分分值
|
// 设置扣分分值
|
||||||
@@ -1005,8 +1037,10 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean updateCompleteData(XgDormHygieneCheckCompleteBo bo) {
|
public Boolean updateCompleteData(XgDormHygieneCheckCompleteBo bo) {
|
||||||
try {
|
try {
|
||||||
|
List<XgDormHygieneCheckDeductionBo> deductionItems = resolveDeductionItems(bo);
|
||||||
// 1. 更新主表数据
|
// 1. 更新主表数据
|
||||||
XgDormHygieneCheck update = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
|
XgDormHygieneCheck update = MapstructUtils.convert(bo, XgDormHygieneCheck.class);
|
||||||
|
applyDeductionSummary(update, deductionItems);
|
||||||
validEntityBeforeSave(update);
|
validEntityBeforeSave(update);
|
||||||
boolean flag = baseMapper.updateById(update) > 0;
|
boolean flag = baseMapper.updateById(update) > 0;
|
||||||
log.info("更新宿舍卫生检查主表数据: {}", update);
|
log.info("更新宿舍卫生检查主表数据: {}", update);
|
||||||
@@ -1018,6 +1052,9 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
|
|
||||||
Long hygieneCheckId = update.getId();
|
Long hygieneCheckId = update.getId();
|
||||||
|
|
||||||
|
// 1.1 更新扣分明细
|
||||||
|
deductionService.replaceByHygieneCheckId(hygieneCheckId, deductionItems);
|
||||||
|
|
||||||
// 2. 更新学生评分数据 - 先删除旧数据,再插入新数据
|
// 2. 更新学生评分数据 - 先删除旧数据,再插入新数据
|
||||||
LambdaQueryWrapper<XgDormHygieneStudentScore> scoreWrapper = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<XgDormHygieneStudentScore> scoreWrapper = Wrappers.lambdaQuery();
|
||||||
scoreWrapper.eq(XgDormHygieneStudentScore::getHygieneCheckId, hygieneCheckId);
|
scoreWrapper.eq(XgDormHygieneStudentScore::getHygieneCheckId, hygieneCheckId);
|
||||||
@@ -1341,4 +1378,63 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<XgDormHygieneCheckDeductionBo> resolveDeductionItems(XgDormHygieneCheckCompleteBo bo) {
|
||||||
|
if (bo.getDeductionItems() != null && !bo.getDeductionItems().isEmpty()) {
|
||||||
|
List<XgDormHygieneCheckDeductionBo> items = new ArrayList<>();
|
||||||
|
Set<Long> typeIds = new HashSet<>();
|
||||||
|
int sortOrder = 1;
|
||||||
|
for (XgDormHygieneCheckDeductionBo item : bo.getDeductionItems()) {
|
||||||
|
if (item == null || item.getDisciplineTypeId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!typeIds.add(item.getDisciplineTypeId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
XgDormHygieneCheckDeductionBo normalized = new XgDormHygieneCheckDeductionBo();
|
||||||
|
normalized.setId(item.getId());
|
||||||
|
normalized.setHygieneCheckId(bo.getId());
|
||||||
|
normalized.setDisciplineTypeId(item.getDisciplineTypeId());
|
||||||
|
normalized.setDisciplineTypeName(item.getDisciplineTypeName());
|
||||||
|
normalized.setScore(item.getScore() != null ? item.getScore() : 0D);
|
||||||
|
normalized.setSortOrder(item.getSortOrder() != null ? item.getSortOrder() : sortOrder++);
|
||||||
|
normalized.setRemark(item.getRemark());
|
||||||
|
items.add(normalized);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
if (bo.getDisciplineTypeId() != null) {
|
||||||
|
XgDormHygieneCheckDeductionBo legacyItem = new XgDormHygieneCheckDeductionBo();
|
||||||
|
legacyItem.setDisciplineTypeId(bo.getDisciplineTypeId());
|
||||||
|
legacyItem.setDisciplineTypeName(bo.getDisciplineTypeName());
|
||||||
|
legacyItem.setScore(bo.getTotalScore() != null ? bo.getTotalScore() : 0D);
|
||||||
|
legacyItem.setSortOrder(1);
|
||||||
|
return List.of(legacyItem);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDeductionSummary(XgDormHygieneCheck entity, List<XgDormHygieneCheckDeductionBo> items) {
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
entity.setTotalScore(0D);
|
||||||
|
entity.setDisciplineTypeId(null);
|
||||||
|
entity.setDisciplineTypeName(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double total = items.stream()
|
||||||
|
.map(item -> item.getScore() != null ? item.getScore() : 0D)
|
||||||
|
.mapToDouble(Double::doubleValue)
|
||||||
|
.sum();
|
||||||
|
BigDecimal totalDecimal = BigDecimal.valueOf(total).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
if (totalDecimal.doubleValue() > 100D) {
|
||||||
|
throw new ServiceException("宿舍合计扣分不能超过100分");
|
||||||
|
}
|
||||||
|
entity.setTotalScore(totalDecimal.doubleValue());
|
||||||
|
entity.setDisciplineTypeId(items.get(0).getDisciplineTypeId());
|
||||||
|
String typeNames = items.stream()
|
||||||
|
.map(XgDormHygieneCheckDeductionBo::getDisciplineTypeName)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.collect(Collectors.joining("、"));
|
||||||
|
entity.setDisciplineTypeName(typeNames);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-5
@@ -164,11 +164,7 @@ public class XgDormHygieneStudentScoreServiceImpl implements IXgDormHygieneStude
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<XgDormHygieneStudentScoreVo> queryByHygieneCheckId(Long hygieneCheckId) {
|
public List<XgDormHygieneStudentScoreVo> queryByHygieneCheckId(Long hygieneCheckId) {
|
||||||
LambdaQueryWrapper<XgDormHygieneStudentScore> lqw = Wrappers.lambdaQuery();
|
return baseMapper.selectVoListByHygieneCheckId(hygieneCheckId);
|
||||||
lqw.eq(XgDormHygieneStudentScore::getHygieneCheckId, hygieneCheckId);
|
|
||||||
lqw.eq(XgDormHygieneStudentScore::getDeleted, 0); // 只查询未删除的记录
|
|
||||||
lqw.orderByAsc(XgDormHygieneStudentScore::getId);
|
|
||||||
return baseMapper.selectVoList(lqw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+35
@@ -23,6 +23,8 @@ import org.dromara.xg.mapper.XgDormRollCallDetailMapper;
|
|||||||
import org.dromara.xg.mapper.XgDormRoomMapper;
|
import org.dromara.xg.mapper.XgDormRoomMapper;
|
||||||
import org.dromara.xg.mapper.XgDormBedMapper;
|
import org.dromara.xg.mapper.XgDormBedMapper;
|
||||||
import org.dromara.xg.mapper.XgDormBuildingMapper;
|
import org.dromara.xg.mapper.XgDormBuildingMapper;
|
||||||
|
import org.dromara.xg.mapper.XgStudentMsgMapper;
|
||||||
|
import org.dromara.xg.domain.XgStudentMsg;
|
||||||
import org.dromara.xg.domain.XgDormRoom;
|
import org.dromara.xg.domain.XgDormRoom;
|
||||||
import org.dromara.xg.domain.XgDormBuilding;
|
import org.dromara.xg.domain.XgDormBuilding;
|
||||||
import org.dromara.xg.domain.XgDormBed;
|
import org.dromara.xg.domain.XgDormBed;
|
||||||
@@ -45,6 +47,7 @@ public class XgDormRollCallServiceImpl implements IXgDormRollCallService {
|
|||||||
private final XgDormRoomMapper roomMapper;
|
private final XgDormRoomMapper roomMapper;
|
||||||
private final XgDormBedMapper bedMapper;
|
private final XgDormBedMapper bedMapper;
|
||||||
private final XgDormBuildingMapper buildingMapper;
|
private final XgDormBuildingMapper buildingMapper;
|
||||||
|
private final XgStudentMsgMapper studentMsgMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XgDormRollCallVo queryById(Long id) {
|
public XgDormRollCallVo queryById(Long id) {
|
||||||
@@ -274,6 +277,7 @@ public class XgDormRollCallServiceImpl implements IXgDormRollCallService {
|
|||||||
private void saveDetails(Long rollCallId, List<XgDormRollCallDetailBo> details) {
|
private void saveDetails(Long rollCallId, List<XgDormRollCallDetailBo> details) {
|
||||||
if (details == null || details.isEmpty()) return;
|
if (details == null || details.isEmpty()) return;
|
||||||
for (XgDormRollCallDetailBo detailBo : details) {
|
for (XgDormRollCallDetailBo detailBo : details) {
|
||||||
|
fillDetailClassInfo(detailBo);
|
||||||
XgDormRollCallDetail detail = new XgDormRollCallDetail();
|
XgDormRollCallDetail detail = new XgDormRollCallDetail();
|
||||||
detail.setRollCallId(rollCallId);
|
detail.setRollCallId(rollCallId);
|
||||||
detail.setBedId(detailBo.getBedId());
|
detail.setBedId(detailBo.getBedId());
|
||||||
@@ -296,4 +300,35 @@ public class XgDormRollCallServiceImpl implements IXgDormRollCallService {
|
|||||||
detailMapper.insert(detail);
|
detailMapper.insert(detail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillDetailClassInfo(XgDormRollCallDetailBo detailBo) {
|
||||||
|
if (detailBo.getStudentId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detailBo.getClassId() != null && StringUtils.isNotBlank(detailBo.getClassName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XgStudentMsg student = studentMsgMapper.selectById(detailBo.getStudentId());
|
||||||
|
if (student == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detailBo.getClassId() == null && StringUtils.isNotBlank(student.getClassId())) {
|
||||||
|
detailBo.setClassId(Long.valueOf(student.getClassId()));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(detailBo.getClassName())) {
|
||||||
|
detailBo.setClassName(student.getClassName());
|
||||||
|
}
|
||||||
|
if (detailBo.getMajorId() == null && StringUtils.isNotBlank(student.getMajorId())) {
|
||||||
|
detailBo.setMajorId(Long.valueOf(student.getMajorId()));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(detailBo.getMajorName())) {
|
||||||
|
detailBo.setMajorName(student.getMajorName());
|
||||||
|
}
|
||||||
|
if (detailBo.getCollegeId() == null && StringUtils.isNotBlank(student.getCollegeId())) {
|
||||||
|
detailBo.setCollegeId(Long.valueOf(student.getCollegeId()));
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(detailBo.getCollegeName())) {
|
||||||
|
detailBo.setCollegeName(student.getCollegeName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="org.dromara.xg.mapper.XgClassTeacherDormViewMapper">
|
||||||
|
|
||||||
|
<select id="selectClassByTeacherId" resultType="org.dromara.xg.domain.vo.ClassTeacherClassInfoVo">
|
||||||
|
SELECT
|
||||||
|
cm.id AS classId,
|
||||||
|
cm.class_name AS className,
|
||||||
|
cm.class_code AS classCode,
|
||||||
|
cm.college_name AS collegeName,
|
||||||
|
cm.major_name AS majorName
|
||||||
|
FROM xg_class_msg cm
|
||||||
|
WHERE cm.deleted = 0
|
||||||
|
AND cm.class_status = '1'
|
||||||
|
AND (
|
||||||
|
cm.class_teacher_id = #{teacherId}
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM xg_class_teacher_counselor_rel rel
|
||||||
|
WHERE rel.class_id = cm.id
|
||||||
|
AND rel.teacher_id = #{teacherId}
|
||||||
|
AND rel.teacher_type = 'class_teacher'
|
||||||
|
AND rel.deleted = 0
|
||||||
|
AND (rel.end_date IS NULL OR rel.end_date > NOW())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY cm.create_time DESC
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectViolationPage" resultType="org.dromara.xg.domain.vo.ClassTeacherDormViolationVo">
|
||||||
|
SELECT * FROM (
|
||||||
|
SELECT
|
||||||
|
hcv.id AS id,
|
||||||
|
'hygiene_check' AS dataSource,
|
||||||
|
'卫生检查' AS dataSourceName,
|
||||||
|
hcv.student_id AS studentId,
|
||||||
|
hcv.student_no AS studentNo,
|
||||||
|
hcv.student_name AS studentName,
|
||||||
|
sm.class_name AS className,
|
||||||
|
hc.building_name AS buildingName,
|
||||||
|
hc.room_name AS roomName,
|
||||||
|
hcv.discipline_type_name AS violationTypeName,
|
||||||
|
dr.score_deduction AS scoreDeduction,
|
||||||
|
NULL AS violationLevel,
|
||||||
|
COALESCE(dr.discipline_date, DATE(hcv.create_time)) AS violationDate,
|
||||||
|
dr.discipline_reason COLLATE utf8mb4_general_ci AS violationReason,
|
||||||
|
hcv.status AS status,
|
||||||
|
hcv.create_time AS create_time
|
||||||
|
FROM xg_hygiene_check_violation hcv
|
||||||
|
INNER JOIN xskh_discipline_record dr ON hcv.discipline_record_id = dr.id AND dr.deleted = 0
|
||||||
|
INNER JOIN xg_dorm_hygiene_check hc ON hcv.hygiene_check_id = hc.id AND hc.deleted = 0
|
||||||
|
INNER JOIN xg_student_msg sm ON hcv.student_id = sm.id AND sm.deleted = 0
|
||||||
|
WHERE hcv.deleted = 0
|
||||||
|
AND sm.class_id = #{classId}
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
dv.id AS id,
|
||||||
|
'dorm_violation' AS dataSource,
|
||||||
|
'宿舍违纪登记' AS dataSourceName,
|
||||||
|
dv.student_id AS studentId,
|
||||||
|
dv.student_no AS studentNo,
|
||||||
|
dv.student_name AS studentName,
|
||||||
|
dv.class_name AS className,
|
||||||
|
dv.building_name AS buildingName,
|
||||||
|
dv.room_name AS roomName,
|
||||||
|
dv.violation_type AS violationTypeName,
|
||||||
|
NULL AS scoreDeduction,
|
||||||
|
dv.violation_level AS violationLevel,
|
||||||
|
COALESCE(dv.violation_time, dv.create_time) AS violationDate,
|
||||||
|
dv.violation_description AS violationReason,
|
||||||
|
'1' AS status,
|
||||||
|
dv.create_time AS create_time
|
||||||
|
FROM xg_dorm_violation dv
|
||||||
|
LEFT JOIN xg_student_msg sm2 ON dv.student_id = sm2.id AND sm2.deleted = 0
|
||||||
|
WHERE (dv.deleted = '0' OR dv.deleted = 0)
|
||||||
|
AND COALESCE(dv.class_id, sm2.class_id) = #{classId}
|
||||||
|
) t
|
||||||
|
WHERE 1 = 1
|
||||||
|
<if test="bo.beginDate != null and bo.beginDate != ''">
|
||||||
|
AND t.violationDate >= CONCAT(#{bo.beginDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND t.violationDate <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
<if test="bo.studentNo != null and bo.studentNo != ''">
|
||||||
|
AND t.studentNo LIKE CONCAT('%', #{bo.studentNo}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo.studentName != null and bo.studentName != ''">
|
||||||
|
AND t.studentName LIKE CONCAT('%', #{bo.studentName}, '%')
|
||||||
|
</if>
|
||||||
|
ORDER BY t.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectRollCallPage" resultType="org.dromara.xg.domain.vo.ClassTeacherDormRollCallVo">
|
||||||
|
SELECT
|
||||||
|
d.id AS id,
|
||||||
|
d.student_id AS studentId,
|
||||||
|
d.student_no AS studentNo,
|
||||||
|
d.student_name AS studentName,
|
||||||
|
d.class_name AS className,
|
||||||
|
r.batch_no AS batchNo,
|
||||||
|
r.check_date AS checkDate,
|
||||||
|
r.checker_name AS checkerName,
|
||||||
|
r.building_name AS buildingName,
|
||||||
|
r.room_name AS roomName,
|
||||||
|
d.bed_code AS bedCode,
|
||||||
|
d.roll_call_status AS rollCallStatus,
|
||||||
|
d.leave_type_name AS leaveTypeName,
|
||||||
|
r.remark AS remark
|
||||||
|
FROM xg_dorm_roll_call_detail d
|
||||||
|
INNER JOIN xg_dorm_roll_call r ON d.roll_call_id = r.id AND r.deleted = 0
|
||||||
|
INNER JOIN xg_student_msg sm ON d.student_id = sm.id AND sm.deleted = 0
|
||||||
|
WHERE d.deleted = 0
|
||||||
|
AND sm.class_id = #{classId}
|
||||||
|
AND d.roll_call_status != '3'
|
||||||
|
<if test="bo.beginDate != null and bo.beginDate != ''">
|
||||||
|
AND r.check_date >= CONCAT(#{bo.beginDate}, ' 00:00:00')
|
||||||
|
</if>
|
||||||
|
<if test="bo.endDate != null and bo.endDate != ''">
|
||||||
|
AND r.check_date <= CONCAT(#{bo.endDate}, ' 23:59:59')
|
||||||
|
</if>
|
||||||
|
<if test="bo.studentNo != null and bo.studentNo != ''">
|
||||||
|
AND d.student_no LIKE CONCAT('%', #{bo.studentNo}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo.studentName != null and bo.studentName != ''">
|
||||||
|
AND d.student_name LIKE CONCAT('%', #{bo.studentName}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo.rollCallStatus != null and bo.rollCallStatus != ''">
|
||||||
|
AND d.roll_call_status = #{bo.rollCallStatus}
|
||||||
|
</if>
|
||||||
|
ORDER BY r.check_date DESC, d.student_no ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="org.dromara.xg.mapper.XgDormHygieneCheckDeductionMapper">
|
||||||
|
|
||||||
|
<select id="selectVoListByHygieneCheckId" resultType="org.dromara.xg.domain.vo.XgDormHygieneCheckDeductionVo">
|
||||||
|
SELECT
|
||||||
|
d.id,
|
||||||
|
d.hygiene_check_id AS hygieneCheckId,
|
||||||
|
d.discipline_type_id AS disciplineTypeId,
|
||||||
|
d.discipline_type_name AS disciplineTypeName,
|
||||||
|
d.score,
|
||||||
|
d.sort_order AS sortOrder,
|
||||||
|
d.remark,
|
||||||
|
dt.min_score AS minScore,
|
||||||
|
dt.max_score AS maxScore
|
||||||
|
FROM xg_dorm_hygiene_check_deduction d
|
||||||
|
LEFT JOIN xskh_discipline_type dt ON dt.id = d.discipline_type_id AND dt.deleted = 0
|
||||||
|
WHERE d.hygiene_check_id = #{hygieneCheckId}
|
||||||
|
AND d.deleted = 0
|
||||||
|
ORDER BY d.sort_order ASC, d.id ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
+2
@@ -15,6 +15,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="buildingName" column="building_name" />
|
<result property="buildingName" column="building_name" />
|
||||||
<result property="roomName" column="room_name" />
|
<result property="roomName" column="room_name" />
|
||||||
<result property="totalScore" column="total_score" />
|
<result property="totalScore" column="total_score" />
|
||||||
|
<result property="disciplineTypeName" column="discipline_type_name" />
|
||||||
<result property="hygieneGradeText" column="hygiene_grade" />
|
<result property="hygieneGradeText" column="hygiene_grade" />
|
||||||
<result property="studentNo" column="student_no" />
|
<result property="studentNo" column="student_no" />
|
||||||
<result property="studentName" column="student_name" />
|
<result property="studentName" column="student_name" />
|
||||||
@@ -44,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
dhc.building_name,
|
dhc.building_name,
|
||||||
dhc.room_name,
|
dhc.room_name,
|
||||||
dhc.total_score,
|
dhc.total_score,
|
||||||
|
dhc.discipline_type_name,
|
||||||
dhc.hygiene_grade,
|
dhc.hygiene_grade,
|
||||||
dhc.check_summary,
|
dhc.check_summary,
|
||||||
dhc.improvement_suggestions,
|
dhc.improvement_suggestions,
|
||||||
|
|||||||
+31
@@ -4,4 +4,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="org.dromara.xg.mapper.XgDormHygieneStudentScoreMapper">
|
<mapper namespace="org.dromara.xg.mapper.XgDormHygieneStudentScoreMapper">
|
||||||
|
|
||||||
|
<select id="selectVoListByHygieneCheckId" resultType="org.dromara.xg.domain.vo.XgDormHygieneStudentScoreVo">
|
||||||
|
SELECT
|
||||||
|
ss.id,
|
||||||
|
ss.deleted,
|
||||||
|
ss.hygiene_check_id AS hygieneCheckId,
|
||||||
|
ss.student_id AS studentId,
|
||||||
|
ss.student_no AS studentNo,
|
||||||
|
ss.student_name AS studentName,
|
||||||
|
ss.class_id AS classId,
|
||||||
|
ss.class_name AS className,
|
||||||
|
ss.major_id AS majorId,
|
||||||
|
ss.major_name AS majorName,
|
||||||
|
ss.college_id AS collegeId,
|
||||||
|
ss.college_name AS collegeName,
|
||||||
|
ss.bed_id AS bedId,
|
||||||
|
ss.bed_code AS bedCode,
|
||||||
|
ss.student_score AS studentScore,
|
||||||
|
ss.attendance_status AS attendanceStatus,
|
||||||
|
ss.student_grade AS studentGrade,
|
||||||
|
ss.is_responsible_person AS isResponsiblePerson,
|
||||||
|
ss.score_remarks AS scoreRemarks,
|
||||||
|
ss.improvement_notes AS improvementNotes,
|
||||||
|
COALESCE(sm_by_id.gender, sm_by_no.gender) AS gender
|
||||||
|
FROM xg_dorm_hygiene_student_score ss
|
||||||
|
LEFT JOIN xg_student_msg sm_by_id ON sm_by_id.id = ss.student_id AND sm_by_id.deleted = 0
|
||||||
|
LEFT JOIN xg_student_msg sm_by_no ON sm_by_no.student_no = ss.student_no AND sm_by_no.deleted = 0
|
||||||
|
WHERE ss.hygiene_check_id = #{hygieneCheckId}
|
||||||
|
AND ss.deleted = 0
|
||||||
|
ORDER BY ss.id ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -180,6 +180,7 @@
|
|||||||
| [班级干部信息管理API](./班级干部信息管理API.md) | 班级干部信息管理(含用户角色关联) | ✅ 完成 | 2025-01-27 |
|
| [班级干部信息管理API](./班级干部信息管理API.md) | 班级干部信息管理(含用户角色关联) | ✅ 完成 | 2025-01-27 |
|
||||||
| [班干部管理API](./班干部管理API.md) | 班级干部职务管理 | ✅ 完成 | 2025-01-27 |
|
| [班干部管理API](./班干部管理API.md) | 班级干部职务管理 | ✅ 完成 | 2025-01-27 |
|
||||||
| [班级教师关联管理API](./班级教师关联管理API.md) | 班主任辅导员关联关系 | ✅ 完成 | 2025-01-27 |
|
| [班级教师关联管理API](./班级教师关联管理API.md) | 班主任辅导员关联关系 | ✅ 完成 | 2025-01-27 |
|
||||||
|
| [班主任班级宿舍情况查看API](./班主任班级宿舍情况查看API.md) | 班主任只读查看本班宿舍违纪与点名 | ✅ 完成 | 2026-06-23 |
|
||||||
| **[学生考核管理系统API](./学生考核管理系统API.md)** | **学生综合素质测评、违纪处分、奖励加分管理** | **✅ 完成** | **2025-01-27** |
|
| **[学生考核管理系统API](./学生考核管理系统API.md)** | **学生综合素质测评、违纪处分、奖励加分管理** | **✅ 完成** | **2025-01-27** |
|
||||||
| **[学生考核管理系统修改方案](./学生考核管理系统API修改方案.md)** | **基于标准化违纪处分等级的系统升级方案** | **📋 方案** | **2025-01-27** |
|
| **[学生考核管理系统修改方案](./学生考核管理系统API修改方案.md)** | **基于标准化违纪处分等级的系统升级方案** | **📋 方案** | **2025-01-27** |
|
||||||
| **[学生考核管理系统数据库修改影响分析](./学生考核管理系统数据库修改影响分析.md)** | **数据库升级脚本执行的详细影响分析** | **📊 分析** | **2025-01-27** |
|
| **[学生考核管理系统数据库修改影响分析](./学生考核管理系统数据库修改影响分析.md)** | **数据库升级脚本执行的详细影响分析** | **📊 分析** | **2025-01-27** |
|
||||||
|
|||||||
@@ -14,6 +14,27 @@
|
|||||||
- 存储每个学生的个人评分信息
|
- 存储每个学生的个人评分信息
|
||||||
- 关联主表,包含学生信息、个人得分、出勤状态、评级等
|
- 关联主表,包含学生信息、个人得分、出勤状态、评级等
|
||||||
|
|
||||||
|
### 扣分明细表:xg_dorm_hygiene_check_deduction(2026-06-13 新增)
|
||||||
|
- 存储宿舍整体扣分的多条明细
|
||||||
|
- 关联主表 `hygiene_check_id`,每条记录对应一个违纪类型及扣分值
|
||||||
|
- 主表 `total_score` 为明细合计;`discipline_type_name` 为多项名称用「、」拼接(兼容旧列表/导出)
|
||||||
|
|
||||||
|
**deductionItems 字段说明:**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| disciplineTypeId | Long | 违纪类型ID |
|
||||||
|
| disciplineTypeName | String | 违纪类型名称 |
|
||||||
|
| score | Number | 该项扣分,默认取类型 minScore,可在 min-max 范围内手动调整 |
|
||||||
|
| sortOrder | Integer | 排序序号 |
|
||||||
|
|
||||||
|
**业务规则:**
|
||||||
|
- 扣分项可选(允许无扣分项,totalScore=0)
|
||||||
|
- 每项默认分数为违纪类型的 `minScore`
|
||||||
|
- 单项分数必须在对应类型的 min-max 范围内
|
||||||
|
- 合计扣分上限 100 分
|
||||||
|
- 历史单条数据迁移:以主表 `total_score` 作为唯一明细的 score
|
||||||
|
|
||||||
## API接口规范
|
## API接口规范
|
||||||
|
|
||||||
### 1. 新增宿舍卫生检查(包含学生评分)
|
### 1. 新增宿舍卫生检查(包含学生评分)
|
||||||
@@ -39,6 +60,20 @@
|
|||||||
"campusId": 1,
|
"campusId": 1,
|
||||||
"campusName": "主校区",
|
"campusName": "主校区",
|
||||||
"totalScore": 85.5,
|
"totalScore": 85.5,
|
||||||
|
"deductionItems": [
|
||||||
|
{
|
||||||
|
"disciplineTypeId": 1963793291531026434,
|
||||||
|
"disciplineTypeName": "地面未打扫",
|
||||||
|
"score": 5,
|
||||||
|
"sortOrder": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disciplineTypeId": 1963793291531026435,
|
||||||
|
"disciplineTypeName": "物品摆放凌乱",
|
||||||
|
"score": 3,
|
||||||
|
"sortOrder": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
"hygieneGrade": "B",
|
"hygieneGrade": "B",
|
||||||
"attachmentIds": "oss1,oss2,oss3",
|
"attachmentIds": "oss1,oss2,oss3",
|
||||||
"checkSummary": "宿舍整体卫生良好",
|
"checkSummary": "宿舍整体卫生良好",
|
||||||
@@ -280,7 +315,8 @@ public interface DormHygieneStudentScoreMapper extends BaseMapperPlus<DormHygien
|
|||||||
- `checkDate`: 必填,日期时间格式
|
- `checkDate`: 必填,日期时间格式
|
||||||
- `checkerId`: 必填,关联用户表
|
- `checkerId`: 必填,关联用户表
|
||||||
- `roomId`: 必填,关联房间表
|
- `roomId`: 必填,关联房间表
|
||||||
- `totalScore`: 可选,0-100分
|
- `totalScore`: 可选,0-100分(由 deductionItems 合计自动计算,只读)
|
||||||
|
- `deductionItems`: 可选,扣分明细数组;每项 score 默认 minScore,可在类型 min-max 内调整;合计不超过100
|
||||||
- `hygieneGrade`: 可选,A/B/C/D
|
- `hygieneGrade`: 可选,A/B/C/D
|
||||||
|
|
||||||
### 学生评分表验证
|
### 学生评分表验证
|
||||||
|
|||||||
@@ -11,15 +11,15 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<u-form ref="formRef" :model="formData" :rules="rules" label-width="100">
|
<u-form ref="formRef" :model="formData" :rules="rules" label-width="100">
|
||||||
<!-- 违纪类型 -->
|
<!-- 扣分项(多选) -->
|
||||||
<u-form-item label="违纪类型" prop="disciplineTypeId">
|
<u-form-item label="扣分项">
|
||||||
<view
|
<view
|
||||||
@click="!isViewMode && (showDisciplineTypePicker = true)"
|
@click="!isViewMode && openDeductionPopup()"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<u-input
|
<u-input
|
||||||
:value="disciplineTypeDisplay"
|
:value="deductionSummaryDisplay"
|
||||||
placeholder="请选择违纪类型"
|
placeholder="请选择扣分项(可多选)"
|
||||||
readonly
|
readonly
|
||||||
border="none"
|
border="none"
|
||||||
:suffix-icon="isViewMode ? '' : 'arrow-down'"
|
:suffix-icon="isViewMode ? '' : 'arrow-down'"
|
||||||
@@ -27,38 +27,70 @@
|
|||||||
style="pointer-events: none"
|
style="pointer-events: none"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<u-picker
|
|
||||||
v-if="!isViewMode"
|
|
||||||
:show="showDisciplineTypePicker && disciplineTypeOptions.length > 0"
|
|
||||||
:columns="disciplineTypeColumns"
|
|
||||||
keyName="text"
|
|
||||||
@confirm="handleDisciplineTypeConfirm"
|
|
||||||
@cancel="showDisciplineTypePicker = false"
|
|
||||||
></u-picker>
|
|
||||||
</u-form-item>
|
</u-form-item>
|
||||||
|
|
||||||
<!-- 违纪类型说明 -->
|
<view v-if="deductionItems.length > 0" class="deduction-list">
|
||||||
<view
|
<view
|
||||||
v-if="formData.disciplineTypeId && selectedDisciplineTypeRemark"
|
v-for="(item, index) in deductionItems"
|
||||||
class="discipline-type-remark"
|
:key="item.disciplineTypeId"
|
||||||
>
|
class="deduction-item"
|
||||||
<u-icon name="info-circle" color="#909399" size="14"></u-icon>
|
>
|
||||||
<text class="remark-text">
|
<view class="deduction-item-header">
|
||||||
【{{ formData.disciplineTypeName }}】类型说明:{{
|
<text class="deduction-name">{{ item.disciplineTypeName }}</text>
|
||||||
selectedDisciplineTypeRemark
|
<u-input
|
||||||
}}
|
v-if="!isViewMode"
|
||||||
</text>
|
v-model="item.score"
|
||||||
|
type="digit"
|
||||||
|
placeholder="扣分"
|
||||||
|
@input="handleDeductionScoreChange"
|
||||||
|
/>
|
||||||
|
<text v-else class="deduction-score">{{ item.score }}分</text>
|
||||||
|
<text class="deduction-range">({{ getDeductionMin(item) }}-{{ getDeductionMax(item) }}分)</text>
|
||||||
|
<u-icon
|
||||||
|
v-if="!isViewMode"
|
||||||
|
name="close-circle"
|
||||||
|
color="#f56c6c"
|
||||||
|
size="18"
|
||||||
|
@click="removeDeductionItem(index)"
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
<view v-if="getDeductionRemark(item.disciplineTypeId)" class="deduction-remark">
|
||||||
|
<text>{{ getDeductionRemark(item.disciplineTypeId) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="deduction-total">
|
||||||
|
合计扣分:<text class="total-value">{{ formData.totalScore }}</text> 分
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<u-popup :show="showDeductionPopup" mode="bottom" round="16" @close="showDeductionPopup = false">
|
||||||
|
<view class="deduction-popup">
|
||||||
|
<view class="deduction-popup-header">
|
||||||
|
<text class="deduction-popup-title">选择扣分项</text>
|
||||||
|
<text class="deduction-popup-action" @click="confirmDeductionSelection">确定</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="deduction-popup-body">
|
||||||
|
<u-checkbox-group v-model="tempSelectedTypeIds" placement="column">
|
||||||
|
<u-checkbox
|
||||||
|
v-for="item in disciplineTypeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:name="String(item.id)"
|
||||||
|
:label="`${item.typeName}(${item.minScore || 0}分起)`"
|
||||||
|
customStyle="margin-bottom: 12px;"
|
||||||
|
></u-checkbox>
|
||||||
|
</u-checkbox-group>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
|
||||||
<!-- 宿舍扣分 -->
|
<!-- 宿舍扣分 -->
|
||||||
<u-form-item label="宿舍扣分" prop="totalScore">
|
<u-form-item label="宿舍扣分" prop="totalScore">
|
||||||
<u-input
|
<u-input
|
||||||
v-model.number="formData.totalScore"
|
v-model="formData.totalScore"
|
||||||
type="digit"
|
type="digit"
|
||||||
placeholder="请输入扣分(0-100分)"
|
placeholder="自动合计"
|
||||||
:disabled="isViewMode"
|
disabled
|
||||||
:readonly="isViewMode"
|
readonly
|
||||||
@input="handleTotalScoreChange"
|
|
||||||
/>
|
/>
|
||||||
</u-form-item>
|
</u-form-item>
|
||||||
|
|
||||||
@@ -272,25 +304,9 @@
|
|||||||
<text class="student-name">{{ student.studentName }}</text>
|
<text class="student-name">{{ student.studentName }}</text>
|
||||||
<!-- 性别标签 -->
|
<!-- 性别标签 -->
|
||||||
<u-tag
|
<u-tag
|
||||||
v-if="
|
v-if="normalizeGender(student.gender)"
|
||||||
student.gender !== undefined &&
|
:text="normalizeGender(student.gender) === '0' ? '男' : '女'"
|
||||||
student.gender !== null &&
|
:type="normalizeGender(student.gender) === '0' ? 'primary' : 'error'"
|
||||||
student.gender !== ''
|
|
||||||
"
|
|
||||||
:text="
|
|
||||||
student.gender === '0'
|
|
||||||
? '男'
|
|
||||||
: student.gender === '1'
|
|
||||||
? '女'
|
|
||||||
: '未知'
|
|
||||||
"
|
|
||||||
:type="
|
|
||||||
student.gender === '0'
|
|
||||||
? 'primary'
|
|
||||||
: student.gender === '1'
|
|
||||||
? 'error'
|
|
||||||
: 'warning'
|
|
||||||
"
|
|
||||||
size="mini"
|
size="mini"
|
||||||
></u-tag>
|
></u-tag>
|
||||||
</view>
|
</view>
|
||||||
@@ -583,6 +599,8 @@ import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import weChatSDK from '@/utils/wechat.js';
|
import weChatSDK from '@/utils/wechat.js';
|
||||||
import ViolationRecordDialog from './ViolationRecordDialog.vue';
|
import ViolationRecordDialog from './ViolationRecordDialog.vue';
|
||||||
|
import { normalizeStudentId, normalizeId } from '../utils/studentId.js';
|
||||||
|
import { calcStudentViolationScoreTotal } from '../utils/violationScore.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SingleRoomCheck',
|
name: 'SingleRoomCheck',
|
||||||
@@ -622,13 +640,16 @@ export default {
|
|||||||
attachmentIds: '',
|
attachmentIds: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deductionItems: [],
|
||||||
|
showDeductionPopup: false,
|
||||||
|
tempSelectedTypeIds: [],
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
rules: {
|
rules: {
|
||||||
hygieneGrade: [{ required: true, message: '请选择卫生等级', trigger: 'change' }],
|
hygieneGrade: [{ required: true, message: '请选择卫生等级', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Picker显示状态
|
// Picker显示状态
|
||||||
showDisciplineTypePicker: false,
|
|
||||||
showHygieneGradePicker: false,
|
showHygieneGradePicker: false,
|
||||||
|
|
||||||
// 违纪类型选项
|
// 违纪类型选项
|
||||||
@@ -692,32 +713,9 @@ export default {
|
|||||||
return name;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 违纪类型列
|
deductionSummaryDisplay() {
|
||||||
disciplineTypeColumns() {
|
if (!this.deductionItems.length) return '';
|
||||||
return [
|
return this.deductionItems.map((item) => item.disciplineTypeName).join('、');
|
||||||
this.disciplineTypeOptions.map((item) => ({
|
|
||||||
text: item.typeName,
|
|
||||||
value: item.id,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 违纪类型显示文本
|
|
||||||
disciplineTypeDisplay() {
|
|
||||||
if (!this.formData.disciplineTypeId) return '';
|
|
||||||
const item = this.disciplineTypeOptions.find(
|
|
||||||
(d) => d.id === this.formData.disciplineTypeId,
|
|
||||||
);
|
|
||||||
return item ? item.typeName : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
// 违纪类型说明
|
|
||||||
selectedDisciplineTypeRemark() {
|
|
||||||
if (!this.formData.disciplineTypeId) return '';
|
|
||||||
const item = this.disciplineTypeOptions.find(
|
|
||||||
(d) => d.id === this.formData.disciplineTypeId,
|
|
||||||
);
|
|
||||||
return item ? item.remark : '';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 卫生等级列
|
// 卫生等级列
|
||||||
@@ -822,12 +820,10 @@ export default {
|
|||||||
// 加载违纪类型
|
// 加载违纪类型
|
||||||
await this.loadDisciplineTypes();
|
await this.loadDisciplineTypes();
|
||||||
|
|
||||||
// 加载房间学生信息
|
if (this.existingData?.id) {
|
||||||
await this.loadRoomStudents();
|
await this.loadEditData();
|
||||||
|
} else {
|
||||||
// 如果有现有数据,加载编辑数据
|
await this.loadRoomStudents();
|
||||||
if (this.existingData) {
|
|
||||||
this.loadEditData();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化组件失败:', error);
|
console.error('初始化组件失败:', error);
|
||||||
@@ -850,6 +846,52 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
normalizeGender(gender) {
|
||||||
|
if (gender === null || gender === undefined || gender === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const value = String(gender);
|
||||||
|
if (value === '0' || value === '男') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
if (value === '1' || value === '女') {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
mergeGenderFromRoomStudents(students, roomStudents) {
|
||||||
|
if (!students.length || !roomStudents.length) {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderByStudentId = {};
|
||||||
|
const genderByStudentNo = {};
|
||||||
|
roomStudents.forEach((student) => {
|
||||||
|
const gender = this.normalizeGender(student.gender);
|
||||||
|
if (!gender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (student.studentId !== undefined && student.studentId !== null) {
|
||||||
|
genderByStudentId[String(student.studentId)] = gender;
|
||||||
|
}
|
||||||
|
if (student.studentNo) {
|
||||||
|
genderByStudentNo[student.studentNo] = gender;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return students.map((student) => {
|
||||||
|
if (this.normalizeGender(student.gender)) {
|
||||||
|
return { ...student, gender: this.normalizeGender(student.gender) };
|
||||||
|
}
|
||||||
|
const gender =
|
||||||
|
(student.studentId !== undefined && student.studentId !== null
|
||||||
|
? genderByStudentId[String(student.studentId)]
|
||||||
|
: undefined) || (student.studentNo ? genderByStudentNo[student.studentNo] : undefined);
|
||||||
|
return gender ? { ...student, gender } : student;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 加载违纪类型
|
// 加载违纪类型
|
||||||
async loadDisciplineTypes() {
|
async loadDisciplineTypes() {
|
||||||
try {
|
try {
|
||||||
@@ -897,6 +939,7 @@ export default {
|
|||||||
|
|
||||||
this.roomStudents = res.data.map((student) => ({
|
this.roomStudents = res.data.map((student) => ({
|
||||||
...student,
|
...student,
|
||||||
|
studentId: normalizeStudentId(student.studentId),
|
||||||
student_score: 0,
|
student_score: 0,
|
||||||
attendance_status: '1',
|
attendance_status: '1',
|
||||||
student_grade: 'A',
|
student_grade: 'A',
|
||||||
@@ -937,10 +980,10 @@ export default {
|
|||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
this.studentLeaves.clear();
|
this.studentLeaves.clear();
|
||||||
Object.keys(res.data).forEach((studentId) => {
|
Object.keys(res.data).forEach((studentId) => {
|
||||||
this.studentLeaves.set(Number(studentId), res.data[studentId]);
|
const normalizedId = normalizeStudentId(studentId);
|
||||||
// 自动设置出勤状态为请假
|
this.studentLeaves.set(normalizedId, res.data[studentId]);
|
||||||
const student = this.roomStudents.find(
|
const student = this.roomStudents.find(
|
||||||
(s) => s.studentId === Number(studentId),
|
(s) => normalizeStudentId(s.studentId) === normalizedId,
|
||||||
);
|
);
|
||||||
if (student) {
|
if (student) {
|
||||||
student.attendance_status = '3';
|
student.attendance_status = '3';
|
||||||
@@ -969,6 +1012,7 @@ export default {
|
|||||||
this.formData.hygieneGrade = this.existingData.hygieneGrade || 'A';
|
this.formData.hygieneGrade = this.existingData.hygieneGrade || 'A';
|
||||||
this.formData.checkRemarks = this.existingData.checkRemarks || '';
|
this.formData.checkRemarks = this.existingData.checkRemarks || '';
|
||||||
this.formData.attachmentIds = this.existingData.attachmentIds || '';
|
this.formData.attachmentIds = this.existingData.attachmentIds || '';
|
||||||
|
this.initDeductionItemsFromData(this.existingData);
|
||||||
|
|
||||||
console.log('✅ 表单基础数据回显完成:', this.formData);
|
console.log('✅ 表单基础数据回显完成:', this.formData);
|
||||||
|
|
||||||
@@ -980,7 +1024,7 @@ export default {
|
|||||||
// 3️⃣ 从后端接口加载学生评分数据、违纪记录和请假信息(复用Web端接口)
|
// 3️⃣ 从后端接口加载学生评分数据、违纪记录和请假信息(复用Web端接口)
|
||||||
// 注意:loadStudentScoresFromAPI 内部会自动调用 loadViolationRecordsFromAPI 和 loadStudentLeaves
|
// 注意:loadStudentScoresFromAPI 内部会自动调用 loadViolationRecordsFromAPI 和 loadStudentLeaves
|
||||||
if (this.existingData.id) {
|
if (this.existingData.id) {
|
||||||
await this.loadStudentScoresFromAPI(this.existingData.id);
|
await this.loadStudentScoresFromAPI(normalizeId(this.existingData.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ 所有数据回显完成');
|
console.log('✅ 所有数据回显完成');
|
||||||
@@ -992,17 +1036,18 @@ export default {
|
|||||||
|
|
||||||
// 从后端API加载学生评分数据(复用Web端接口)
|
// 从后端API加载学生评分数据(复用Web端接口)
|
||||||
async loadStudentScoresFromAPI(checkId) {
|
async loadStudentScoresFromAPI(checkId) {
|
||||||
if (!checkId) {
|
const normalizedCheckId = normalizeId(checkId);
|
||||||
|
if (!normalizedCheckId) {
|
||||||
console.warn('⚠️ 没有检查ID,无法加载学生评分数据');
|
console.warn('⚠️ 没有检查ID,无法加载学生评分数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.studentsLoading = true;
|
this.studentsLoading = true;
|
||||||
try {
|
try {
|
||||||
console.log('🔄 从API加载学生评分数据,检查ID:', checkId);
|
console.log('🔄 从API加载学生评分数据,检查ID:', normalizedCheckId);
|
||||||
|
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${checkId}`,
|
url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${normalizedCheckId}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1010,9 +1055,9 @@ export default {
|
|||||||
console.log('✅ 成功获取学生评分数据:', res.data.length, '条');
|
console.log('✅ 成功获取学生评分数据:', res.data.length, '条');
|
||||||
|
|
||||||
// 将后端返回的学生评分数据映射到roomStudents
|
// 将后端返回的学生评分数据映射到roomStudents
|
||||||
this.roomStudents = res.data.map((studentScore) => ({
|
let students = res.data.map((studentScore) => ({
|
||||||
// 学生基本信息
|
// 学生基本信息
|
||||||
studentId: studentScore.studentId,
|
studentId: normalizeStudentId(studentScore.studentId),
|
||||||
studentName: studentScore.studentName,
|
studentName: studentScore.studentName,
|
||||||
studentNo: studentScore.studentNo,
|
studentNo: studentScore.studentNo,
|
||||||
className: studentScore.className,
|
className: studentScore.className,
|
||||||
@@ -1021,7 +1066,7 @@ export default {
|
|||||||
bedId: studentScore.bedId,
|
bedId: studentScore.bedId,
|
||||||
bedName: studentScore.bedCode,
|
bedName: studentScore.bedCode,
|
||||||
bedCode: studentScore.bedCode,
|
bedCode: studentScore.bedCode,
|
||||||
gender: studentScore.gender || '1',
|
gender: this.normalizeGender(studentScore.gender),
|
||||||
// 评分数据
|
// 评分数据
|
||||||
student_score: studentScore.studentScore || 0,
|
student_score: studentScore.studentScore || 0,
|
||||||
attendance_status: studentScore.attendanceStatus || '1',
|
attendance_status: studentScore.attendanceStatus || '1',
|
||||||
@@ -1034,13 +1079,27 @@ export default {
|
|||||||
showGradePicker: false,
|
showGradePicker: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (students.some((student) => !student.gender) && this.roomInfo && this.roomInfo.id) {
|
||||||
|
const roomRes = await request({
|
||||||
|
url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
||||||
|
method: 'GET',
|
||||||
|
params: { roomId: this.roomInfo.id },
|
||||||
|
});
|
||||||
|
if (roomRes.code === 200 && roomRes.data) {
|
||||||
|
students = this.mergeGenderFromRoomStudents(students, roomRes.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roomStudents = students;
|
||||||
|
|
||||||
console.log('✅ 学生评分数据映射完成');
|
console.log('✅ 学生评分数据映射完成');
|
||||||
|
|
||||||
// 🔴 同时加载违纪记录和请假信息(复用Web端逻辑)
|
// 🔴 同时加载违纪记录和请假信息(复用Web端逻辑)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadViolationRecordsFromAPI(checkId),
|
this.loadViolationRecordsFromAPI(normalizedCheckId),
|
||||||
this.loadStudentLeaves(),
|
this.loadStudentLeaves(),
|
||||||
]);
|
]);
|
||||||
|
this.syncStudentScoreFromViolations();
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ 未获取到学生评分数据,保持当前学生列表');
|
console.warn('⚠️ 未获取到学生评分数据,保持当前学生列表');
|
||||||
}
|
}
|
||||||
@@ -1054,16 +1113,17 @@ export default {
|
|||||||
|
|
||||||
// 从后端API加载违纪记录数据(复用Web端接口)
|
// 从后端API加载违纪记录数据(复用Web端接口)
|
||||||
async loadViolationRecordsFromAPI(checkId) {
|
async loadViolationRecordsFromAPI(checkId) {
|
||||||
if (!checkId) {
|
const normalizedCheckId = normalizeId(checkId);
|
||||||
|
if (!normalizedCheckId) {
|
||||||
console.warn('⚠️ 没有检查ID,无法加载违纪记录数据');
|
console.warn('⚠️ 没有检查ID,无法加载违纪记录数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('🔄 从API加载违纪记录数据,检查ID:', checkId);
|
console.log('🔄 从API加载违纪记录数据,检查ID:', normalizedCheckId);
|
||||||
|
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${checkId}`,
|
url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${normalizedCheckId}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1075,7 +1135,7 @@ export default {
|
|||||||
|
|
||||||
// 按学生ID分组违纪记录
|
// 按学生ID分组违纪记录
|
||||||
res.data.forEach((violation) => {
|
res.data.forEach((violation) => {
|
||||||
const studentId = violation.studentId;
|
const studentId = normalizeStudentId(violation.studentId);
|
||||||
if (!studentId) return;
|
if (!studentId) return;
|
||||||
|
|
||||||
const violations = this.violationRecords.get(studentId) || [];
|
const violations = this.violationRecords.get(studentId) || [];
|
||||||
@@ -1101,6 +1161,7 @@ export default {
|
|||||||
'✅ 所有违纪记录数据加载完成,总学生数:',
|
'✅ 所有违纪记录数据加载完成,总学生数:',
|
||||||
this.violationRecords.size,
|
this.violationRecords.size,
|
||||||
);
|
);
|
||||||
|
this.syncStudentScoreFromViolations();
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ 未获取到违纪记录数据');
|
console.warn('⚠️ 未获取到违纪记录数据');
|
||||||
}
|
}
|
||||||
@@ -1318,7 +1379,9 @@ export default {
|
|||||||
console.log('👥 回显学生评分数据:', studentScores);
|
console.log('👥 回显学生评分数据:', studentScores);
|
||||||
|
|
||||||
studentScores.forEach((scoreData) => {
|
studentScores.forEach((scoreData) => {
|
||||||
const student = this.roomStudents.find((s) => s.studentId === scoreData.studentId);
|
const student = this.roomStudents.find(
|
||||||
|
(s) => normalizeStudentId(s.studentId) === normalizeStudentId(scoreData.studentId),
|
||||||
|
);
|
||||||
if (student) {
|
if (student) {
|
||||||
student.student_score = scoreData.studentScore || 0;
|
student.student_score = scoreData.studentScore || 0;
|
||||||
student.attendance_status = scoreData.attendanceStatus || '1';
|
student.attendance_status = scoreData.attendanceStatus || '1';
|
||||||
@@ -1337,7 +1400,7 @@ export default {
|
|||||||
this.violationRecords.clear();
|
this.violationRecords.clear();
|
||||||
|
|
||||||
violationData.forEach((violation) => {
|
violationData.forEach((violation) => {
|
||||||
const studentId = violation.studentId;
|
const studentId = normalizeStudentId(violation.studentId);
|
||||||
if (!studentId) return;
|
if (!studentId) return;
|
||||||
|
|
||||||
const violations = this.violationRecords.get(studentId) || [];
|
const violations = this.violationRecords.get(studentId) || [];
|
||||||
@@ -1360,6 +1423,28 @@ export default {
|
|||||||
violation.violationAttachmentIds || violation.attachmentOssIds,
|
violation.violationAttachmentIds || violation.attachmentOssIds,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
this.syncStudentScoreFromViolations();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据违纪记录合计同步个人扣分
|
||||||
|
syncStudentScoreFromViolations(studentId) {
|
||||||
|
const syncOne = (sid) => {
|
||||||
|
const student = this.roomStudents.find(
|
||||||
|
(item) => normalizeStudentId(item.studentId) === sid,
|
||||||
|
);
|
||||||
|
if (student) {
|
||||||
|
student.student_score = calcStudentViolationScoreTotal(this.getStudentViolations(sid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (studentId != null && studentId !== '') {
|
||||||
|
syncOne(normalizeStudentId(studentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roomStudents.forEach((student) => {
|
||||||
|
syncOne(normalizeStudentId(student.studentId));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清理blob URL,防止内存泄漏
|
// 清理blob URL,防止内存泄漏
|
||||||
@@ -1377,38 +1462,104 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 违纪类型确认处理
|
initDeductionItemsFromData(data) {
|
||||||
handleDisciplineTypeConfirm(e) {
|
if (data?.deductionItems && data.deductionItems.length > 0) {
|
||||||
if (e && e.value && e.value.length > 0) {
|
this.deductionItems = data.deductionItems.map((item, index) => ({
|
||||||
const selectedType = this.disciplineTypeOptions.find(
|
...item,
|
||||||
(item) => item.id === e.value[0].value,
|
score: item.score ?? 0,
|
||||||
);
|
sortOrder: item.sortOrder ?? index + 1,
|
||||||
if (selectedType) {
|
}));
|
||||||
this.formData.disciplineTypeId = selectedType.id;
|
} else if (data?.disciplineTypeId) {
|
||||||
this.formData.disciplineTypeName = selectedType.typeName;
|
this.deductionItems = [
|
||||||
|
{
|
||||||
// 自动填入最低扣分
|
disciplineTypeId: data.disciplineTypeId,
|
||||||
if (selectedType.minScore !== null && selectedType.minScore !== undefined) {
|
disciplineTypeName: data.disciplineTypeName,
|
||||||
this.formData.totalScore = selectedType.minScore;
|
score: data.totalScore || 0,
|
||||||
uni.showToast({
|
sortOrder: 1,
|
||||||
title: `已自动填入最低扣分:${selectedType.minScore}分`,
|
},
|
||||||
icon: 'none',
|
];
|
||||||
});
|
} else {
|
||||||
}
|
this.deductionItems = [];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.showDisciplineTypePicker = false;
|
this.recalcDeductionTotal();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 宿舍总分变化处理
|
openDeductionPopup() {
|
||||||
handleTotalScoreChange() {
|
this.tempSelectedTypeIds = this.deductionItems.map((item) => String(item.disciplineTypeId));
|
||||||
const score = parseFloat(this.formData.totalScore);
|
this.showDeductionPopup = true;
|
||||||
if (!isNaN(score) && (score < 0 || score > 100)) {
|
},
|
||||||
uni.showToast({ title: '宿舍扣分必须在0-100分之间', icon: 'none' });
|
|
||||||
this.formData.totalScore = 0;
|
confirmDeductionSelection() {
|
||||||
|
const currentMap = new Map(
|
||||||
|
this.deductionItems.map((item) => [String(item.disciplineTypeId), item]),
|
||||||
|
);
|
||||||
|
const nextItems = [];
|
||||||
|
this.tempSelectedTypeIds.forEach((typeId, index) => {
|
||||||
|
const option = this.disciplineTypeOptions.find((item) => String(item.id) === String(typeId));
|
||||||
|
if (!option) return;
|
||||||
|
const existing = currentMap.get(String(typeId));
|
||||||
|
nextItems.push({
|
||||||
|
disciplineTypeId: option.id,
|
||||||
|
disciplineTypeName: option.typeName,
|
||||||
|
score: existing ? existing.score : Number(option.minScore || 0),
|
||||||
|
sortOrder: index + 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.deductionItems = nextItems;
|
||||||
|
this.recalcDeductionTotal();
|
||||||
|
this.showDeductionPopup = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeDeductionItem(index) {
|
||||||
|
this.deductionItems.splice(index, 1);
|
||||||
|
this.recalcDeductionTotal();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDeductionScoreChange() {
|
||||||
|
this.deductionItems.forEach((item) => {
|
||||||
|
let score = parseFloat(item.score);
|
||||||
|
if (isNaN(score)) {
|
||||||
|
score = this.getDeductionMin(item);
|
||||||
|
}
|
||||||
|
const min = this.getDeductionMin(item);
|
||||||
|
const max = this.getDeductionMax(item);
|
||||||
|
if (score < min) score = min;
|
||||||
|
if (score > max) score = max;
|
||||||
|
item.score = score;
|
||||||
|
});
|
||||||
|
this.recalcDeductionTotal();
|
||||||
|
if ((this.formData.totalScore || 0) > 100) {
|
||||||
|
uni.showToast({ title: '合计扣分不能超过100分', icon: 'none' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
recalcDeductionTotal() {
|
||||||
|
const total = this.deductionItems.reduce(
|
||||||
|
(sum, item) => sum + (parseFloat(item.score) || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
this.formData.totalScore = Number(total.toFixed(2));
|
||||||
|
},
|
||||||
|
|
||||||
|
getDeductionOption(typeId) {
|
||||||
|
return this.disciplineTypeOptions.find((item) => String(item.id) === String(typeId));
|
||||||
|
},
|
||||||
|
|
||||||
|
getDeductionMin(item) {
|
||||||
|
const option = this.getDeductionOption(item.disciplineTypeId);
|
||||||
|
return option?.minScore ?? 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
getDeductionMax(item) {
|
||||||
|
const option = this.getDeductionOption(item.disciplineTypeId);
|
||||||
|
return option?.maxScore ?? 100;
|
||||||
|
},
|
||||||
|
|
||||||
|
getDeductionRemark(typeId) {
|
||||||
|
const option = this.getDeductionOption(typeId);
|
||||||
|
return option?.remark || '';
|
||||||
|
},
|
||||||
|
|
||||||
// 卫生等级确认处理
|
// 卫生等级确认处理
|
||||||
handleHygieneGradeConfirm(e) {
|
handleHygieneGradeConfirm(e) {
|
||||||
if (e && e.value && e.value.length > 0) {
|
if (e && e.value && e.value.length > 0) {
|
||||||
@@ -2114,7 +2265,7 @@ export default {
|
|||||||
|
|
||||||
// 获取学生请假信息
|
// 获取学生请假信息
|
||||||
getStudentLeave(studentId) {
|
getStudentLeave(studentId) {
|
||||||
return this.studentLeaves.get(studentId) || null;
|
return this.studentLeaves.get(normalizeStudentId(studentId)) || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 格式化日期时间
|
// 格式化日期时间
|
||||||
@@ -2141,8 +2292,7 @@ export default {
|
|||||||
|
|
||||||
// 删除违纪记录
|
// 删除违纪记录
|
||||||
removeViolationRecord(studentId, index) {
|
removeViolationRecord(studentId, index) {
|
||||||
// 保存要删除的记录信息
|
this.deleteViolationStudentId = normalizeStudentId(studentId);
|
||||||
this.deleteViolationStudentId = studentId;
|
|
||||||
this.deleteViolationIndex = index;
|
this.deleteViolationIndex = index;
|
||||||
// 显示确认对话框
|
// 显示确认对话框
|
||||||
this.showDeleteConfirm = true;
|
this.showDeleteConfirm = true;
|
||||||
@@ -2159,6 +2309,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.violationRecords.set(this.deleteViolationStudentId, violations);
|
this.violationRecords.set(this.deleteViolationStudentId, violations);
|
||||||
}
|
}
|
||||||
|
this.syncStudentScoreFromViolations(this.deleteViolationStudentId);
|
||||||
|
|
||||||
uni.showToast({ title: '违纪记录删除成功', icon: 'success' });
|
uni.showToast({ title: '违纪记录删除成功', icon: 'success' });
|
||||||
}
|
}
|
||||||
@@ -2178,25 +2329,31 @@ export default {
|
|||||||
|
|
||||||
// 获取学生违纪记录
|
// 获取学生违纪记录
|
||||||
getStudentViolations(studentId) {
|
getStudentViolations(studentId) {
|
||||||
return this.violationRecords.get(studentId) || [];
|
return this.violationRecords.get(normalizeStudentId(studentId)) || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
// 违纪记录确认
|
// 违纪记录确认
|
||||||
handleViolationConfirm(violationData) {
|
handleViolationConfirm(violationData) {
|
||||||
const studentId = this.currentStudent.studentId;
|
const studentId = normalizeStudentId(this.currentStudent.studentId);
|
||||||
const violations = this.violationRecords.get(studentId) || [];
|
const violations = this.violationRecords.get(studentId) || [];
|
||||||
|
|
||||||
if (this.currentViolationIndex >= 0) {
|
if (Array.isArray(violationData)) {
|
||||||
// 编辑模式
|
violations.push(...violationData);
|
||||||
|
uni.showToast({
|
||||||
|
title: `已添加${violationData.length}条违纪记录`,
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
} else if (this.currentViolationIndex >= 0) {
|
||||||
violations[this.currentViolationIndex] = violationData;
|
violations[this.currentViolationIndex] = violationData;
|
||||||
|
uni.showToast({ title: '违纪记录保存成功', icon: 'success' });
|
||||||
} else {
|
} else {
|
||||||
// 新增模式
|
|
||||||
violations.push(violationData);
|
violations.push(violationData);
|
||||||
|
uni.showToast({ title: '违纪记录保存成功', icon: 'success' });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.violationRecords.set(studentId, violations);
|
this.violationRecords.set(studentId, violations);
|
||||||
|
this.syncStudentScoreFromViolations(studentId);
|
||||||
this.violationDialogVisible = false;
|
this.violationDialogVisible = false;
|
||||||
uni.showToast({ title: '违纪记录保存成功', icon: 'success' });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 违纪记录弹窗关闭
|
// 违纪记录弹窗关闭
|
||||||
@@ -2210,8 +2367,15 @@ export default {
|
|||||||
// 表单验证
|
// 表单验证
|
||||||
await this.$refs.formRef.validate();
|
await this.$refs.formRef.validate();
|
||||||
|
|
||||||
|
if ((this.formData.totalScore || 0) > 100) {
|
||||||
|
uni.showToast({ title: '宿舍合计扣分不能超过100分', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.submitLoading = true;
|
this.submitLoading = true;
|
||||||
|
|
||||||
|
this.syncStudentScoreFromViolations();
|
||||||
|
|
||||||
// 处理附件数据
|
// 处理附件数据
|
||||||
const attachmentOssIds = this.attachmentList
|
const attachmentOssIds = this.attachmentList
|
||||||
.filter((file) => file.status === 'success' && file.ossId)
|
.filter((file) => file.status === 'success' && file.ossId)
|
||||||
@@ -2270,6 +2434,7 @@ export default {
|
|||||||
|
|
||||||
// 宿舍整体评分
|
// 宿舍整体评分
|
||||||
...this.formData,
|
...this.formData,
|
||||||
|
deductionItems: this.deductionItems,
|
||||||
|
|
||||||
// 学生评分数据
|
// 学生评分数据
|
||||||
studentScores,
|
studentScores,
|
||||||
@@ -2791,4 +2956,90 @@ export default {
|
|||||||
padding: 20rpx 30rpx;
|
padding: 20rpx 30rpx;
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deduction-list {
|
||||||
|
margin: 16rpx 0 24rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
background: #f8f9fb;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-item + .deduction-item {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx dashed #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
min-width: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-score {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-range {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-remark {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-total {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx solid #e4e7ed;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-total .total-value {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-popup {
|
||||||
|
padding: 24rpx 30rpx 40rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-popup-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-popup-action {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #3c9cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-popup-body {
|
||||||
|
max-height: 55vh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -36,40 +36,120 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 违纪类型选择 -->
|
<!-- 违纪类型:编辑模式单选 -->
|
||||||
<u-form-item label="违纪类型" prop="disciplineTypeId" required>
|
<template v-if="isEditMode">
|
||||||
<view @click="handleDisciplineTypeClick" style="width: 100%">
|
<u-form-item label="违纪类型" prop="disciplineTypeId" required>
|
||||||
<u-input
|
<view @click="handleDisciplineTypeClick" style="width: 100%">
|
||||||
:value="disciplineTypeDisplay"
|
<u-input
|
||||||
placeholder="请选择违纪类型"
|
:value="disciplineTypeDisplay"
|
||||||
readonly
|
placeholder="请选择违纪类型"
|
||||||
suffix-icon="arrow-down"
|
readonly
|
||||||
suffix-icon-style="color: #c8c9cc;"
|
suffix-icon="arrow-down"
|
||||||
@click.stop="handleDisciplineTypeClick"
|
suffix-icon-style="color: #c8c9cc;"
|
||||||
/>
|
@click.stop="handleDisciplineTypeClick"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</u-form-item>
|
||||||
|
|
||||||
|
<u-picker
|
||||||
|
:show="showDisciplineTypePicker"
|
||||||
|
:columns="disciplineTypeColumns"
|
||||||
|
keyName="text"
|
||||||
|
@confirm="handleDisciplineTypeConfirm"
|
||||||
|
@cancel="showDisciplineTypePicker = false"
|
||||||
|
@close="showDisciplineTypePicker = false"
|
||||||
|
></u-picker>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="formData.disciplineTypeId && selectedDisciplineTypeRemark"
|
||||||
|
class="discipline-type-remark"
|
||||||
|
>
|
||||||
|
<u-icon name="info-circle" color="#909399" size="14"></u-icon>
|
||||||
|
<text class="remark-text">
|
||||||
|
类型说明:{{ selectedDisciplineTypeRemark }}
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</u-form-item>
|
</template>
|
||||||
|
|
||||||
<!-- 违纪类型选择器 -->
|
<!-- 违纪类型:新增模式多选 -->
|
||||||
<u-picker
|
<template v-else>
|
||||||
:show="showDisciplineTypePicker"
|
<u-form-item label="违纪类型">
|
||||||
:columns="disciplineTypeColumns"
|
<view @click="openViolationTypePopup" style="width: 100%">
|
||||||
keyName="text"
|
<u-input
|
||||||
@confirm="handleDisciplineTypeConfirm"
|
:value="violationSummaryDisplay"
|
||||||
@cancel="showDisciplineTypePicker = false"
|
placeholder="请选择违纪类型(可多选)"
|
||||||
@close="showDisciplineTypePicker = false"
|
readonly
|
||||||
></u-picker>
|
suffix-icon="arrow-down"
|
||||||
|
suffix-icon-style="color: #c8c9cc;"
|
||||||
|
style="pointer-events: none"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</u-form-item>
|
||||||
|
|
||||||
<!-- 违纪类型说明 -->
|
<view v-if="violationItems.length > 0" class="violation-item-list">
|
||||||
<view
|
<view
|
||||||
v-if="formData.disciplineTypeId && selectedDisciplineTypeRemark"
|
v-for="(item, index) in violationItems"
|
||||||
class="discipline-type-remark"
|
:key="item.disciplineTypeId"
|
||||||
>
|
class="violation-item-row"
|
||||||
<u-icon name="info-circle" color="#909399" size="14"></u-icon>
|
>
|
||||||
<text class="remark-text">
|
<view class="violation-item-header">
|
||||||
类型说明:{{ selectedDisciplineTypeRemark }}
|
<text class="violation-item-name">{{ item.disciplineTypeName }}</text>
|
||||||
</text>
|
<u-input
|
||||||
</view>
|
v-model="item.scoreDeduction"
|
||||||
|
type="digit"
|
||||||
|
placeholder="扣分"
|
||||||
|
@input="handleViolationScoreChange"
|
||||||
|
/>
|
||||||
|
<text class="score-range">
|
||||||
|
({{ getViolationMin(item) }}-{{ getViolationMax(item) }}分)
|
||||||
|
</text>
|
||||||
|
<u-icon
|
||||||
|
name="close-circle"
|
||||||
|
color="#f56c6c"
|
||||||
|
size="18"
|
||||||
|
@click="removeViolationItem(index)"
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="getViolationRemark(item.disciplineTypeId)"
|
||||||
|
class="discipline-type-remark"
|
||||||
|
>
|
||||||
|
<text>类型说明:{{ getViolationRemark(item.disciplineTypeId) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="violation-item-total">
|
||||||
|
合计扣分:<text class="total-value">{{ violationItemsTotalScore }}</text> 分
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<u-popup
|
||||||
|
:show="showViolationTypePopup"
|
||||||
|
mode="bottom"
|
||||||
|
round="16"
|
||||||
|
@close="showViolationTypePopup = false"
|
||||||
|
>
|
||||||
|
<view class="violation-type-popup">
|
||||||
|
<view class="violation-type-popup-header">
|
||||||
|
<text class="violation-type-popup-title">选择违纪类型</text>
|
||||||
|
<text
|
||||||
|
class="violation-type-popup-action"
|
||||||
|
@click="confirmViolationTypeSelection"
|
||||||
|
>确定</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="violation-type-popup-body">
|
||||||
|
<u-checkbox-group v-model="tempSelectedTypeIds" placement="column">
|
||||||
|
<u-checkbox
|
||||||
|
v-for="item in allViolationTypes"
|
||||||
|
:key="item.id"
|
||||||
|
:name="String(item.id)"
|
||||||
|
:label="getViolationTypeLabel(item)"
|
||||||
|
customStyle="margin-bottom: 12px;"
|
||||||
|
></u-checkbox>
|
||||||
|
</u-checkbox-group>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 违纪日期 -->
|
<!-- 违纪日期 -->
|
||||||
<u-form-item label="违纪日期" prop="disciplineDate" required>
|
<u-form-item label="违纪日期" prop="disciplineDate" required>
|
||||||
@@ -93,8 +173,13 @@
|
|||||||
></u-datetime-picker>
|
></u-datetime-picker>
|
||||||
</u-form-item>
|
</u-form-item>
|
||||||
|
|
||||||
<!-- 扣分分值 -->
|
<!-- 扣分分值(编辑模式) -->
|
||||||
<u-form-item label="扣分分值" prop="scoreDeduction" required>
|
<u-form-item
|
||||||
|
v-if="isEditMode"
|
||||||
|
label="扣分分值"
|
||||||
|
prop="scoreDeduction"
|
||||||
|
required
|
||||||
|
>
|
||||||
<u-input
|
<u-input
|
||||||
v-model.number="formData.scoreDeduction"
|
v-model.number="formData.scoreDeduction"
|
||||||
type="digit"
|
type="digit"
|
||||||
@@ -104,7 +189,7 @@
|
|||||||
</u-form-item>
|
</u-form-item>
|
||||||
|
|
||||||
<!-- 违纪原因 -->
|
<!-- 违纪原因 -->
|
||||||
<u-form-item label="违纪原因" prop="disciplineReason" required>
|
<u-form-item label="违纪原因" prop="disciplineReason">
|
||||||
<u-textarea
|
<u-textarea
|
||||||
v-model="formData.disciplineReason"
|
v-model="formData.disciplineReason"
|
||||||
placeholder="请输入违纪原因"
|
placeholder="请输入违纪原因"
|
||||||
@@ -398,11 +483,14 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
disciplineReason: [{ required: true, message: '请输入违纪原因', trigger: 'blur' }],
|
disciplineReason: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Picker显示状态
|
// Picker显示状态
|
||||||
showDisciplineTypePicker: false,
|
showDisciplineTypePicker: false,
|
||||||
|
showViolationTypePopup: false,
|
||||||
|
tempSelectedTypeIds: [],
|
||||||
|
violationItems: [],
|
||||||
showDatePicker: false,
|
showDatePicker: false,
|
||||||
|
|
||||||
// 违纪日期
|
// 违纪日期
|
||||||
@@ -435,6 +523,23 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isEditMode() {
|
||||||
|
return !!this.violationData;
|
||||||
|
},
|
||||||
|
|
||||||
|
violationSummaryDisplay() {
|
||||||
|
if (!this.violationItems.length) return '';
|
||||||
|
return this.violationItems.map((item) => item.disciplineTypeName).join('、');
|
||||||
|
},
|
||||||
|
|
||||||
|
violationItemsTotalScore() {
|
||||||
|
const sum = this.violationItems.reduce(
|
||||||
|
(acc, item) => acc + (parseFloat(item.scoreDeduction) || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
return Number(sum.toFixed(2));
|
||||||
|
},
|
||||||
|
|
||||||
// 违纪类型列(带区域标签)
|
// 违纪类型列(带区域标签)
|
||||||
disciplineTypeColumns() {
|
disciplineTypeColumns() {
|
||||||
return [
|
return [
|
||||||
@@ -768,8 +873,9 @@ export default {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('违纪记录:新增模式,重置附件列表');
|
console.log('违纪记录:新增模式,重置附件列表');
|
||||||
// 重置附件列表
|
|
||||||
this.attachmentList = [];
|
this.attachmentList = [];
|
||||||
|
this.violationItems = [];
|
||||||
|
this.tempSelectedTypeIds = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -919,6 +1025,11 @@ export default {
|
|||||||
|
|
||||||
// 违纪类型点击处理
|
// 违纪类型点击处理
|
||||||
handleDisciplineTypeClick() {
|
handleDisciplineTypeClick() {
|
||||||
|
if (!this.isEditMode) {
|
||||||
|
this.openViolationTypePopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('=== 违纪类型点击 ===');
|
console.log('=== 违纪类型点击 ===');
|
||||||
console.log('违纪类型数据量:', this.allViolationTypes.length);
|
console.log('违纪类型数据量:', this.allViolationTypes.length);
|
||||||
console.log('违纪类型数据:', this.allViolationTypes);
|
console.log('违纪类型数据:', this.allViolationTypes);
|
||||||
@@ -996,6 +1107,100 @@ export default {
|
|||||||
this.showDisciplineTypePicker = false;
|
this.showDisciplineTypePicker = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openViolationTypePopup() {
|
||||||
|
if (this.allViolationTypes.length === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '暂无可用的违纪类型',
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.tempSelectedTypeIds = this.violationItems.map((item) => String(item.disciplineTypeId));
|
||||||
|
this.showViolationTypePopup = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmViolationTypeSelection() {
|
||||||
|
const currentMap = new Map(
|
||||||
|
this.violationItems.map((item) => [String(item.disciplineTypeId), item]),
|
||||||
|
);
|
||||||
|
const nextItems = [];
|
||||||
|
this.tempSelectedTypeIds.forEach((typeId) => {
|
||||||
|
const option = this.allViolationTypes.find((item) => String(item.id) === String(typeId));
|
||||||
|
if (!option) return;
|
||||||
|
const existing = currentMap.get(String(typeId));
|
||||||
|
nextItems.push({
|
||||||
|
disciplineTypeId: option.id,
|
||||||
|
disciplineTypeName: option.typeName,
|
||||||
|
scoreDeduction: existing
|
||||||
|
? existing.scoreDeduction
|
||||||
|
: parseFloat(option.minScore) || 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.violationItems = nextItems;
|
||||||
|
this.showViolationTypePopup = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
removeViolationItem(index) {
|
||||||
|
this.violationItems.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
getViolationOption(typeId) {
|
||||||
|
return this.allViolationTypes.find((item) => String(item.id) === String(typeId));
|
||||||
|
},
|
||||||
|
|
||||||
|
getViolationMin(item) {
|
||||||
|
const option = this.getViolationOption(item.disciplineTypeId);
|
||||||
|
return option?.minScore ?? 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
getViolationMax(item) {
|
||||||
|
const option = this.getViolationOption(item.disciplineTypeId);
|
||||||
|
return option?.maxScore ?? 100;
|
||||||
|
},
|
||||||
|
|
||||||
|
getViolationRemark(typeId) {
|
||||||
|
const option = this.getViolationOption(typeId);
|
||||||
|
return option?.remark || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
handleViolationScoreChange() {
|
||||||
|
this.violationItems.forEach((item) => {
|
||||||
|
let score = parseFloat(item.scoreDeduction);
|
||||||
|
if (isNaN(score)) {
|
||||||
|
score = this.getViolationMin(item);
|
||||||
|
}
|
||||||
|
const min = this.getViolationMin(item);
|
||||||
|
const max = this.getViolationMax(item);
|
||||||
|
if (score < min) score = min;
|
||||||
|
if (score > max) score = max;
|
||||||
|
item.scoreDeduction = score;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSharedViolationFields() {
|
||||||
|
const attachmentOssIds = this.attachmentList
|
||||||
|
.filter((file) => file.status === 'success' && file.ossId)
|
||||||
|
.map((file) => file.ossId)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
return {
|
||||||
|
studentId: this.formData.studentId,
|
||||||
|
studentNo: this.formData.studentNo,
|
||||||
|
studentName: this.formData.studentName,
|
||||||
|
collegeId: this.formData.collegeId,
|
||||||
|
collegeName: this.formData.collegeName,
|
||||||
|
majorId: this.formData.majorId,
|
||||||
|
majorName: this.formData.majorName,
|
||||||
|
classId: this.formData.classId,
|
||||||
|
className: this.formData.className,
|
||||||
|
disciplineDate: this.formData.disciplineDate,
|
||||||
|
disciplineReason: this.formData.disciplineReason,
|
||||||
|
disciplineContent: this.formData.disciplineContent,
|
||||||
|
attachmentOssIds,
|
||||||
|
violationSource: this.formData.violationSource || '1',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
// 扣分变化处理
|
// 扣分变化处理
|
||||||
handleScoreChange() {
|
handleScoreChange() {
|
||||||
// 确保转换为数字类型
|
// 确保转换为数字类型
|
||||||
@@ -1844,18 +2049,55 @@ export default {
|
|||||||
// 确定
|
// 确定
|
||||||
async handleConfirm() {
|
async handleConfirm() {
|
||||||
try {
|
try {
|
||||||
// 表单验证
|
|
||||||
await this.$refs.formRef.validate();
|
|
||||||
|
|
||||||
this.confirmLoading = true;
|
this.confirmLoading = true;
|
||||||
|
|
||||||
// 处理附件数据
|
if (this.isEditMode) {
|
||||||
const attachmentOssIds = this.attachmentList
|
await this.$refs.formRef.validate();
|
||||||
.filter((file) => file.status === 'success' && file.ossId)
|
const attachmentOssIds = this.attachmentList
|
||||||
.map((file) => file.ossId);
|
.filter((file) => file.status === 'success' && file.ossId)
|
||||||
this.formData.attachmentOssIds = attachmentOssIds.join(',');
|
.map((file) => file.ossId);
|
||||||
|
this.formData.attachmentOssIds = attachmentOssIds.join(',');
|
||||||
|
this.$emit('confirm', { ...this.formData });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('confirm', { ...this.formData });
|
if (!this.formData.disciplineDate) {
|
||||||
|
uni.showToast({ title: '请选择违纪日期', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.violationItems.length === 0) {
|
||||||
|
uni.showToast({ title: '请至少选择一个违纪类型', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of this.violationItems) {
|
||||||
|
const min = this.getViolationMin(item);
|
||||||
|
const max = this.getViolationMax(item);
|
||||||
|
const score = parseFloat(item.scoreDeduction);
|
||||||
|
if (isNaN(score)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `请填写「${item.disciplineTypeName}」扣分`,
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (score < min || score > max) {
|
||||||
|
uni.showToast({
|
||||||
|
title: `「${item.disciplineTypeName}」须在${min}-${max}分`,
|
||||||
|
icon: 'none',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedFields = this.buildSharedViolationFields();
|
||||||
|
const records = this.violationItems.map((item) => ({
|
||||||
|
...sharedFields,
|
||||||
|
disciplineTypeId: item.disciplineTypeId,
|
||||||
|
disciplineTypeName: item.disciplineTypeName,
|
||||||
|
scoreDeduction: item.scoreDeduction,
|
||||||
|
}));
|
||||||
|
this.$emit('confirm', records);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交失败:', error);
|
console.error('提交失败:', error);
|
||||||
uni.showToast({ title: '请检查表单填写是否完整', icon: 'none' });
|
uni.showToast({ title: '请检查表单填写是否完整', icon: 'none' });
|
||||||
@@ -1915,6 +2157,9 @@ export default {
|
|||||||
this.currentTypeMaxScore = 100;
|
this.currentTypeMaxScore = 100;
|
||||||
this.disciplineDateDisplay = dayjs().format('YYYY-MM-DD');
|
this.disciplineDateDisplay = dayjs().format('YYYY-MM-DD');
|
||||||
this.disciplineDateValue = Number(new Date());
|
this.disciplineDateValue = Number(new Date());
|
||||||
|
this.violationItems = [];
|
||||||
|
this.tempSelectedTypeIds = [];
|
||||||
|
this.showViolationTypePopup = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -2200,4 +2445,76 @@ export default {
|
|||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
border-top: 2rpx solid #f0f0f0;
|
border-top: 2rpx solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.violation-item-list {
|
||||||
|
margin: 0 0 24rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
background: #f8f9fb;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-row + .violation-item-row {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx dashed #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
min-width: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-range {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-total {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx solid #e4e7ed;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-total .total-value {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-type-popup {
|
||||||
|
padding: 24rpx 30rpx 40rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-type-popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-type-popup-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-type-popup-action {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #3c9cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-type-popup-body {
|
||||||
|
max-height: 55vh;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** 雪花ID统一转字符串,避免 JS Number 精度丢失导致 Map/接口匹配失败 */
|
||||||
|
export function normalizeStudentId(studentId) {
|
||||||
|
if (studentId === null || studentId === undefined || studentId === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(studentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用雪花ID归一化(检查ID、学生ID等) */
|
||||||
|
export const normalizeId = normalizeStudentId;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/** 计算学生有效违纪记录的扣分合计(已撤销 status=2 不计入) */
|
||||||
|
export function calcStudentViolationScoreTotal(violations) {
|
||||||
|
const total = (violations || [])
|
||||||
|
.filter((item) => item && item.status !== '2')
|
||||||
|
.reduce((sum, item) => sum + (parseFloat(String(item.scoreDeduction ?? 0)) || 0), 0);
|
||||||
|
return parseFloat(total.toFixed(2));
|
||||||
|
}
|
||||||
@@ -270,6 +270,7 @@
|
|||||||
import { request } from '@/common/request.js';
|
import { request } from '@/common/request.js';
|
||||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||||
import SingleRoomCheck from './components/SingleRoomCheck.vue';
|
import SingleRoomCheck from './components/SingleRoomCheck.vue';
|
||||||
|
import { normalizeId } from './utils/studentId.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DormHygieneCheck',
|
name: 'DormHygieneCheck',
|
||||||
@@ -544,7 +545,7 @@ export default {
|
|||||||
|
|
||||||
// 加载检查记录详情
|
// 加载检查记录详情
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
url: `/dormHygieneCheck/dormHygieneCheck/${normalizeId(item.id)}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -552,7 +553,10 @@ export default {
|
|||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
this.currentViewItem = item;
|
this.currentViewItem = item;
|
||||||
this.currentViewData = res.data;
|
this.currentViewData = {
|
||||||
|
...res.data,
|
||||||
|
id: normalizeId(res.data.id),
|
||||||
|
};
|
||||||
|
|
||||||
// 构造房间信息
|
// 构造房间信息
|
||||||
this.currentViewRoomInfo = {
|
this.currentViewRoomInfo = {
|
||||||
@@ -605,7 +609,7 @@ export default {
|
|||||||
|
|
||||||
// 加载检查记录详情
|
// 加载检查记录详情
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
url: `/dormHygieneCheck/dormHygieneCheck/${normalizeId(item.id)}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -613,7 +617,10 @@ export default {
|
|||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
this.currentEditItem = item;
|
this.currentEditItem = item;
|
||||||
this.currentEditData = res.data;
|
this.currentEditData = {
|
||||||
|
...res.data,
|
||||||
|
id: normalizeId(res.data.id),
|
||||||
|
};
|
||||||
|
|
||||||
// 构造房间信息
|
// 构造房间信息
|
||||||
this.currentRoomInfo = {
|
this.currentRoomInfo = {
|
||||||
@@ -708,7 +715,7 @@ export default {
|
|||||||
uni.showLoading({ title: '删除中...' });
|
uni.showLoading({ title: '删除中...' });
|
||||||
|
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
url: `/dormHygieneCheck/dormHygieneCheck/${normalizeId(item.id)}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1309,6 +1309,52 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
normalizeGender(gender) {
|
||||||
|
if (gender === null || gender === undefined || gender === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const value = String(gender);
|
||||||
|
if (value === '0' || value === '男') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
if (value === '1' || value === '女') {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
mergeGenderFromRoomStudents(students, roomStudents) {
|
||||||
|
if (!students.length || !roomStudents.length) {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderByStudentId = {};
|
||||||
|
const genderByStudentNo = {};
|
||||||
|
roomStudents.forEach((student) => {
|
||||||
|
const gender = this.normalizeGender(student.gender);
|
||||||
|
if (!gender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (student.studentId !== undefined && student.studentId !== null) {
|
||||||
|
genderByStudentId[String(student.studentId)] = gender;
|
||||||
|
}
|
||||||
|
if (student.studentNo) {
|
||||||
|
genderByStudentNo[student.studentNo] = gender;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return students.map((student) => {
|
||||||
|
if (this.normalizeGender(student.gender)) {
|
||||||
|
return { ...student, gender: this.normalizeGender(student.gender) };
|
||||||
|
}
|
||||||
|
const gender =
|
||||||
|
(student.studentId !== undefined && student.studentId !== null
|
||||||
|
? genderByStudentId[String(student.studentId)]
|
||||||
|
: undefined) || (student.studentNo ? genderByStudentNo[student.studentNo] : undefined);
|
||||||
|
return gender ? { ...student, gender } : student;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 加载房间学生信息
|
// 加载房间学生信息
|
||||||
async loadRoomStudents() {
|
async loadRoomStudents() {
|
||||||
if (!this.formData.roomId) {
|
if (!this.formData.roomId) {
|
||||||
@@ -1329,8 +1375,9 @@ export default {
|
|||||||
studentScoresRes.data &&
|
studentScoresRes.data &&
|
||||||
studentScoresRes.data.length > 0
|
studentScoresRes.data.length > 0
|
||||||
) {
|
) {
|
||||||
this.roomStudents = studentScoresRes.data.map((item) => ({
|
let students = studentScoresRes.data.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
|
gender: this.normalizeGender(item.gender),
|
||||||
student_score: item.studentScore || null,
|
student_score: item.studentScore || null,
|
||||||
attendance_status: item.attendanceStatus || '1',
|
attendance_status: item.attendanceStatus || '1',
|
||||||
student_grade: item.studentGrade || 'A',
|
student_grade: item.studentGrade || 'A',
|
||||||
@@ -1339,6 +1386,19 @@ export default {
|
|||||||
bedCode: item.bedCode,
|
bedCode: item.bedCode,
|
||||||
bedName: item.bedCode,
|
bedName: item.bedCode,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (students.some((student) => !student.gender) && this.formData.roomId) {
|
||||||
|
const roomRes = await request({
|
||||||
|
url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
||||||
|
method: 'GET',
|
||||||
|
params: { roomId: this.formData.roomId },
|
||||||
|
});
|
||||||
|
if (roomRes.code === 200 && roomRes.data) {
|
||||||
|
students = this.mergeGenderFromRoomStudents(students, roomRes.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roomStudents = students;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import { AxiosPromise } from 'axios';
|
||||||
|
import { TableDataInfo } from '@/api/types';
|
||||||
|
|
||||||
|
export interface ClassTeacherClassInfo {
|
||||||
|
classId: string | number;
|
||||||
|
className: string;
|
||||||
|
classCode?: string;
|
||||||
|
collegeName?: string;
|
||||||
|
majorName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClassTeacherDormQuery {
|
||||||
|
beginDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
studentNo?: string;
|
||||||
|
studentName?: string;
|
||||||
|
rollCallStatus?: string;
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClassTeacherDormViolationVO {
|
||||||
|
id: string | number;
|
||||||
|
dataSource: string;
|
||||||
|
dataSourceName: string;
|
||||||
|
studentNo: string;
|
||||||
|
studentName: string;
|
||||||
|
className: string;
|
||||||
|
buildingName: string;
|
||||||
|
roomName: string;
|
||||||
|
violationTypeName: string;
|
||||||
|
scoreDeduction?: number;
|
||||||
|
violationLevel?: string;
|
||||||
|
violationDate: string;
|
||||||
|
violationReason?: string;
|
||||||
|
status?: string;
|
||||||
|
createTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClassTeacherDormRollCallVO {
|
||||||
|
id: string | number;
|
||||||
|
studentNo: string;
|
||||||
|
studentName: string;
|
||||||
|
className: string;
|
||||||
|
batchNo: string;
|
||||||
|
checkDate: string;
|
||||||
|
checkerName: string;
|
||||||
|
buildingName: string;
|
||||||
|
roomName: string;
|
||||||
|
bedCode: string;
|
||||||
|
rollCallStatus: string;
|
||||||
|
leaveTypeName?: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验业务 code(GET 请求失败时拦截器不会 reject,需手动检查) */
|
||||||
|
function unwrapResponse<T = any>(res: any, action: string): T {
|
||||||
|
const code = res?.code;
|
||||||
|
if (code !== undefined && code !== null && code !== 200 && code !== 0) {
|
||||||
|
throw new Error(res?.msg || `${action}失败`);
|
||||||
|
}
|
||||||
|
return res as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前班主任班级信息 */
|
||||||
|
export const getMyClassInfo = (): AxiosPromise<ClassTeacherClassInfo> => {
|
||||||
|
return request({
|
||||||
|
url: '/classTeacherDorm/classTeacherDorm/myClass',
|
||||||
|
method: 'get'
|
||||||
|
}).then((res) => unwrapResponse(res, '获取班级信息'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 本班宿舍违纪列表 */
|
||||||
|
export const listClassTeacherViolations = (
|
||||||
|
query?: ClassTeacherDormQuery
|
||||||
|
): AxiosPromise<TableDataInfo<ClassTeacherDormViolationVO[]>> => {
|
||||||
|
return request({
|
||||||
|
url: '/classTeacherDorm/classTeacherDorm/violation/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
}).then((res) => unwrapResponse(res, '查询宿舍违纪'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 本班宿舍点名列表 */
|
||||||
|
export const listClassTeacherRollCalls = (
|
||||||
|
query?: ClassTeacherDormQuery
|
||||||
|
): AxiosPromise<TableDataInfo<ClassTeacherDormRollCallVO[]>> => {
|
||||||
|
return request({
|
||||||
|
url: '/classTeacherDorm/classTeacherDorm/rollCall/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
}).then((res) => unwrapResponse(res, '查询宿舍点名'));
|
||||||
|
};
|
||||||
@@ -142,6 +142,17 @@ export const getStudentScoresByCheckId = (hygieneCheckId: string | number): Axio
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据卫生检查ID查询违纪记录列表
|
||||||
|
* @param hygieneCheckId 卫生检查ID
|
||||||
|
*/
|
||||||
|
export const getViolationsByCheckId = (hygieneCheckId: string | number) => {
|
||||||
|
return request({
|
||||||
|
url: '/dormHygieneCheck/dormHygieneCheck/getViolations/' + hygieneCheckId,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除指定卫生检查的学生评分记录
|
* 批量删除指定卫生检查的学生评分记录
|
||||||
* @param hygieneCheckId 卫生检查ID
|
* @param hygieneCheckId 卫生检查ID
|
||||||
|
|||||||
@@ -113,6 +113,32 @@ export interface DormHygieneCheckVO {
|
|||||||
* 检查备注
|
* 检查备注
|
||||||
*/
|
*/
|
||||||
checkRemarks: string;
|
checkRemarks: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 违纪类型ID(兼容旧数据,展示用)
|
||||||
|
*/
|
||||||
|
disciplineTypeId?: string | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 违纪类型名称(多项以、拼接)
|
||||||
|
*/
|
||||||
|
disciplineTypeName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分明细
|
||||||
|
*/
|
||||||
|
deductionItems?: HygieneDeductionItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HygieneDeductionItem {
|
||||||
|
id?: string | number;
|
||||||
|
disciplineTypeId: string | number;
|
||||||
|
disciplineTypeName?: string;
|
||||||
|
score?: number;
|
||||||
|
minScore?: number;
|
||||||
|
maxScore?: number;
|
||||||
|
sortOrder?: number;
|
||||||
|
remark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DormHygieneCheckForm extends BaseEntity {
|
export interface DormHygieneCheckForm extends BaseEntity {
|
||||||
@@ -230,6 +256,21 @@ export interface DormHygieneCheckForm extends BaseEntity {
|
|||||||
* 检查备注
|
* 检查备注
|
||||||
*/
|
*/
|
||||||
checkRemarks?: string;
|
checkRemarks?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 违纪类型ID(兼容)
|
||||||
|
*/
|
||||||
|
disciplineTypeId?: string | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 违纪类型名称(兼容)
|
||||||
|
*/
|
||||||
|
disciplineTypeName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分明细
|
||||||
|
*/
|
||||||
|
deductionItems?: HygieneDeductionItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DormHygieneCheckQuery extends PageQuery {
|
export interface DormHygieneCheckQuery extends PageQuery {
|
||||||
@@ -639,4 +680,9 @@ export interface DormHygieneCheckCompleteForm extends DormHygieneCheckForm {
|
|||||||
* 学生评分数据列表
|
* 学生评分数据列表
|
||||||
*/
|
*/
|
||||||
studentScores?: DormHygieneStudentScoreForm[];
|
studentScores?: DormHygieneStudentScoreForm[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣分明细
|
||||||
|
*/
|
||||||
|
deductionItems?: HygieneDeductionItem[];
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2 page-container">
|
||||||
|
<el-card shadow="hover" class="mb-[10px]" v-loading="classLoading">
|
||||||
|
<div v-if="classInfo" class="class-info-bar">
|
||||||
|
<span class="class-info-label">所带班级:</span>
|
||||||
|
<el-tag type="primary" size="large">{{ classInfo.className }}</el-tag>
|
||||||
|
<span v-if="classInfo.collegeName" class="class-info-extra">{{ classInfo.collegeName }}</span>
|
||||||
|
<span v-if="classInfo.majorName" class="class-info-extra">{{ classInfo.majorName }}</span>
|
||||||
|
<span class="class-info-tip">(仅显示本班学生数据,按学生当前所在班级过滤)</span>
|
||||||
|
</div>
|
||||||
|
<el-alert
|
||||||
|
v-else-if="classLoadError"
|
||||||
|
type="error"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="classLoadError"
|
||||||
|
class="mb-0"
|
||||||
|
/>
|
||||||
|
<el-empty v-else-if="!classLoading" description="当前账号未绑定班主任班级" />
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="loadStatus"
|
||||||
|
:type="loadStatusType"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="loadStatus"
|
||||||
|
class="mb-[10px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="listLoadError"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="listLoadError"
|
||||||
|
class="mb-[10px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template v-if="pageReady">
|
||||||
|
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||||
|
<div v-show="showSearch" class="mb-[10px]">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<el-form ref="queryFormRef" :model="queryParams" label-width="80px">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="日期范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="5">
|
||||||
|
<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="5">
|
||||||
|
<el-form-item label="姓名" prop="studentName">
|
||||||
|
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="activeTab === 'rollCall'" :span="5">
|
||||||
|
<el-form-item label="点名状态" prop="rollCallStatus">
|
||||||
|
<el-select v-model="queryParams.rollCallStatus" placeholder="全部状态" clearable style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in xg_dorm_roll_call_status"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="activeTab === 'rollCall' ? 6 : 6" class="search-btn-col">
|
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="font-medium">班级宿舍情况</span>
|
||||||
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
|
||||||
|
<el-tab-pane label="宿舍违纪" name="violation">
|
||||||
|
<el-table v-loading="violationLoading" border :data="violationList" empty-text="当前日期范围内暂无违纪记录,请尝试扩大日期范围">
|
||||||
|
<el-table-column label="学号" prop="studentNo" width="130" align="center" />
|
||||||
|
<el-table-column label="姓名" prop="studentName" width="100" align="center" />
|
||||||
|
<el-table-column label="来源" prop="dataSourceName" width="110" align="center" />
|
||||||
|
<el-table-column label="违纪类型" prop="violationTypeName" min-width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column label="扣分" prop="scoreDeduction" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.scoreDeduction != null">{{ scope.row.scoreDeduction }}</span>
|
||||||
|
<span v-else class="text-gray-400">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="违纪日期" prop="violationDate" width="120" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ proxy?.parseTime(scope.row.violationDate, '{y}-{m}-{d}') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="宿舍楼" prop="buildingName" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="房间" prop="roomName" width="100" align="center" />
|
||||||
|
<el-table-column label="违纪原因" prop="violationReason" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="状态" prop="status" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :options="student_discipline_status" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<pagination
|
||||||
|
v-show="violationTotal > 0"
|
||||||
|
v-model:page="violationQuery.pageNum"
|
||||||
|
v-model:limit="violationQuery.pageSize"
|
||||||
|
:total="violationTotal"
|
||||||
|
@pagination="getViolationList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="宿舍点名" name="rollCall">
|
||||||
|
<el-table
|
||||||
|
v-loading="rollCallLoading"
|
||||||
|
border
|
||||||
|
:data="rollCallList"
|
||||||
|
:row-class-name="rollCallRowClassName"
|
||||||
|
empty-text="当前日期范围内暂无点名记录,请尝试扩大日期范围或调整筛选条件"
|
||||||
|
>
|
||||||
|
<el-table-column label="班级" prop="className" width="120" align="center" show-overflow-tooltip />
|
||||||
|
<el-table-column label="学号" prop="studentNo" width="130" align="center" />
|
||||||
|
<el-table-column label="姓名" prop="studentName" width="100" align="center" />
|
||||||
|
<el-table-column label="点名时间" prop="checkDate" width="170" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ proxy?.parseTime(scope.row.checkDate, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="点名状态" prop="rollCallStatus" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :options="xg_dorm_roll_call_status" :value="scope.row.rollCallStatus" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="批次号" prop="batchNo" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column label="检查员" prop="checkerName" width="100" align="center" />
|
||||||
|
<el-table-column label="宿舍楼" prop="buildingName" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="房间" prop="roomName" width="100" align="center" />
|
||||||
|
<el-table-column label="床位" prop="bedCode" width="80" align="center" />
|
||||||
|
<el-table-column label="请假类型" prop="leaveTypeName" width="100" align="center" />
|
||||||
|
</el-table>
|
||||||
|
<pagination
|
||||||
|
v-show="rollCallTotal > 0"
|
||||||
|
v-model:page="rollCallQuery.pageNum"
|
||||||
|
v-model:limit="rollCallQuery.pageSize"
|
||||||
|
:total="rollCallTotal"
|
||||||
|
@pagination="getRollCallList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="ClassTeacherDormView" lang="ts">
|
||||||
|
import { ref, reactive, onMounted, getCurrentInstance, type ComponentInternalInstance, toRefs } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import {
|
||||||
|
getMyClassInfo,
|
||||||
|
listClassTeacherViolations,
|
||||||
|
listClassTeacherRollCalls,
|
||||||
|
type ClassTeacherClassInfo,
|
||||||
|
type ClassTeacherDormViolationVO,
|
||||||
|
type ClassTeacherDormRollCallVO
|
||||||
|
} from '@/api/xg/xgss/classTeacherDorm/index';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
const { student_discipline_status, xg_dorm_roll_call_status } = toRefs<any>(
|
||||||
|
proxy?.useDict('student_discipline_status', 'xg_dorm_roll_call_status')
|
||||||
|
);
|
||||||
|
|
||||||
|
const classLoading = ref(false);
|
||||||
|
const classInfo = ref<ClassTeacherClassInfo | null>(null);
|
||||||
|
const classLoadError = ref('');
|
||||||
|
const listLoadError = ref('');
|
||||||
|
const loadStatus = ref('');
|
||||||
|
const loadStatusType = ref<'success' | 'warning' | 'error' | 'info'>('info');
|
||||||
|
const pageReady = ref(false);
|
||||||
|
const showSearch = ref(true);
|
||||||
|
const activeTab = ref('violation');
|
||||||
|
const queryFormRef = ref();
|
||||||
|
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const today = () => formatDate(new Date());
|
||||||
|
|
||||||
|
const defaultDateRange = (): [string, string] => [today(), today()];
|
||||||
|
|
||||||
|
const dateRange = ref<[string, string]>(defaultDateRange());
|
||||||
|
|
||||||
|
const queryParams = reactive({
|
||||||
|
studentNo: '',
|
||||||
|
studentName: '',
|
||||||
|
rollCallStatus: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const violationLoading = ref(false);
|
||||||
|
const violationList = ref<ClassTeacherDormViolationVO[]>([]);
|
||||||
|
const violationTotal = ref(0);
|
||||||
|
const violationQuery = reactive({ pageNum: 1, pageSize: 10 });
|
||||||
|
|
||||||
|
const rollCallLoading = ref(false);
|
||||||
|
const rollCallList = ref<ClassTeacherDormRollCallVO[]>([]);
|
||||||
|
const rollCallTotal = ref(0);
|
||||||
|
const rollCallQuery = reactive({ pageNum: 1, pageSize: 10 });
|
||||||
|
|
||||||
|
const buildDateParams = () => ({
|
||||||
|
beginDate: dateRange.value?.[0] || defaultDateRange()[0],
|
||||||
|
endDate: dateRange.value?.[1] || today(),
|
||||||
|
studentNo: queryParams.studentNo || undefined,
|
||||||
|
studentName: queryParams.studentName || undefined,
|
||||||
|
rollCallStatus: activeTab.value === 'rollCall' && queryParams.rollCallStatus ? queryParams.rollCallStatus : undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 不在寝(status=2)整行高亮 */
|
||||||
|
const rollCallRowClassName = ({ row }: { row: ClassTeacherDormRollCallVO }) => {
|
||||||
|
return row.rollCallStatus === '2' ? 'roll-call-absent-row' : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractRows = (res: any) => res?.rows ?? res?.data?.rows ?? [];
|
||||||
|
const extractTotal = (res: any) => Number(res?.total ?? res?.data?.total ?? 0);
|
||||||
|
const extractErrorMsg = (error: any) => error?.message || error?.response?.data?.msg || '请求失败,请稍后重试';
|
||||||
|
|
||||||
|
const updateLoadStatus = () => {
|
||||||
|
const range = `${dateRange.value?.[0] || ''} ~ ${dateRange.value?.[1] || ''}`;
|
||||||
|
if (classLoadError.value || listLoadError.value) {
|
||||||
|
loadStatusType.value = 'error';
|
||||||
|
loadStatus.value = [classLoadError.value, listLoadError.value].filter(Boolean).join(';');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadStatusType.value = 'success';
|
||||||
|
loadStatus.value = `查询完成(${range}):班级「${classInfo.value?.className || '未知'}」,违纪 ${violationTotal.value} 条,点名 ${rollCallTotal.value} 条`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadClassInfo = async () => {
|
||||||
|
classLoading.value = true;
|
||||||
|
classLoadError.value = '';
|
||||||
|
try {
|
||||||
|
const res: any = await getMyClassInfo();
|
||||||
|
classInfo.value = res?.data ?? null;
|
||||||
|
if (!classInfo.value?.className) {
|
||||||
|
classLoadError.value = res?.msg || '未获取到班级信息,请确认当前账号已绑定班主任班级';
|
||||||
|
ElMessage.error(classLoadError.value);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
classInfo.value = null;
|
||||||
|
classLoadError.value = extractErrorMsg(error);
|
||||||
|
ElMessage.error(classLoadError.value);
|
||||||
|
} finally {
|
||||||
|
classLoading.value = false;
|
||||||
|
updateLoadStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getViolationList = async () => {
|
||||||
|
violationLoading.value = true;
|
||||||
|
listLoadError.value = '';
|
||||||
|
try {
|
||||||
|
const res: any = await listClassTeacherViolations({
|
||||||
|
...buildDateParams(),
|
||||||
|
pageNum: violationQuery.pageNum,
|
||||||
|
pageSize: violationQuery.pageSize
|
||||||
|
});
|
||||||
|
violationList.value = extractRows(res);
|
||||||
|
violationTotal.value = extractTotal(res);
|
||||||
|
} catch (error: any) {
|
||||||
|
violationList.value = [];
|
||||||
|
violationTotal.value = 0;
|
||||||
|
listLoadError.value = `宿舍违纪:${extractErrorMsg(error)}`;
|
||||||
|
ElMessage.error(listLoadError.value);
|
||||||
|
} finally {
|
||||||
|
violationLoading.value = false;
|
||||||
|
updateLoadStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRollCallList = async () => {
|
||||||
|
rollCallLoading.value = true;
|
||||||
|
listLoadError.value = '';
|
||||||
|
try {
|
||||||
|
const res: any = await listClassTeacherRollCalls({
|
||||||
|
...buildDateParams(),
|
||||||
|
pageNum: rollCallQuery.pageNum,
|
||||||
|
pageSize: rollCallQuery.pageSize
|
||||||
|
});
|
||||||
|
rollCallList.value = extractRows(res);
|
||||||
|
rollCallTotal.value = extractTotal(res);
|
||||||
|
} catch (error: any) {
|
||||||
|
rollCallList.value = [];
|
||||||
|
rollCallTotal.value = 0;
|
||||||
|
listLoadError.value = `宿舍点名:${extractErrorMsg(error)}`;
|
||||||
|
ElMessage.error(listLoadError.value);
|
||||||
|
} finally {
|
||||||
|
rollCallLoading.value = false;
|
||||||
|
updateLoadStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
violationQuery.pageNum = 1;
|
||||||
|
rollCallQuery.pageNum = 1;
|
||||||
|
if (activeTab.value === 'violation') {
|
||||||
|
getViolationList();
|
||||||
|
} else {
|
||||||
|
getRollCallList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value?.resetFields();
|
||||||
|
queryParams.studentNo = '';
|
||||||
|
queryParams.studentName = '';
|
||||||
|
queryParams.rollCallStatus = '';
|
||||||
|
dateRange.value = defaultDateRange();
|
||||||
|
handleQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (name: string | number) => {
|
||||||
|
if (name === 'violation') {
|
||||||
|
getViolationList();
|
||||||
|
} else {
|
||||||
|
getRollCallList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadClassInfo();
|
||||||
|
pageReady.value = true;
|
||||||
|
await getViolationList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-container {
|
||||||
|
min-height: calc(100vh - 120px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info-extra {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-info-tip {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.roll-call-absent-row) {
|
||||||
|
background-color: #fef0f0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.roll-call-absent-row:hover > td) {
|
||||||
|
background-color: #fde2e2 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -201,8 +201,8 @@
|
|||||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||||
<el-table-column prop="gender" label="性别" width="60">
|
<el-table-column prop="gender" label="性别" width="60">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.gender === '0'" type="primary" size="small">男</el-tag>
|
<el-tag v-if="isMaleGender(scope.row.gender)" type="primary" size="small">男</el-tag>
|
||||||
<el-tag v-else-if="scope.row.gender === '1'" class="female-tag" size="small">女</el-tag>
|
<el-tag v-else-if="isFemaleGender(scope.row.gender)" class="female-tag" size="small">女</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="collegeName" label="学院名称" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="collegeName" label="学院名称" min-width="120" show-overflow-tooltip />
|
||||||
@@ -356,6 +356,7 @@ import { getToken } from '@/utils/auth';
|
|||||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import { DormHygieneCheckForm, DormHygieneCheckCompleteForm } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
import { DormHygieneCheckForm, DormHygieneCheckCompleteForm } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||||
|
import { isFemaleGender, isMaleGender, mergeGenderFromRoomStudents, normalizeGender } from './genderUtils';
|
||||||
// 暂时注释掉用户信息获取,避免linter错误
|
// 暂时注释掉用户信息获取,避免linter错误
|
||||||
// import { getInfo } from '@/api/login';
|
// import { getInfo } from '@/api/login';
|
||||||
|
|
||||||
@@ -1035,7 +1036,7 @@ const loadRoomStudents = async () => {
|
|||||||
console.log('获取到历史学生评分数据:', studentScoresRes.data);
|
console.log('获取到历史学生评分数据:', studentScoresRes.data);
|
||||||
|
|
||||||
// 将历史评分数据转换为表格显示格式
|
// 将历史评分数据转换为表格显示格式
|
||||||
roomStudents.value = studentScoresRes.data.map((studentScore: any) => ({
|
let mappedStudents = studentScoresRes.data.map((studentScore: any) => ({
|
||||||
// 保存学生评分记录的ID,用于编辑时提交
|
// 保存学生评分记录的ID,用于编辑时提交
|
||||||
id: studentScore.id,
|
id: studentScore.id,
|
||||||
studentId: studentScore.studentId,
|
studentId: studentScore.studentId,
|
||||||
@@ -1047,7 +1048,7 @@ const loadRoomStudents = async () => {
|
|||||||
bedId: studentScore.bedId,
|
bedId: studentScore.bedId,
|
||||||
bedName: studentScore.bedCode, // 使用bedCode作为bedName显示
|
bedName: studentScore.bedCode, // 使用bedCode作为bedName显示
|
||||||
bedCode: studentScore.bedCode,
|
bedCode: studentScore.bedCode,
|
||||||
gender: studentScore.gender || '1', // 默认女性,如果没有性别字段
|
gender: normalizeGender(studentScore.gender),
|
||||||
// 使用历史评分数据
|
// 使用历史评分数据
|
||||||
student_score: studentScore.studentScore || null,
|
student_score: studentScore.studentScore || null,
|
||||||
attendance_status: studentScore.attendanceStatus || '1',
|
attendance_status: studentScore.attendanceStatus || '1',
|
||||||
@@ -1057,6 +1058,20 @@ const loadRoomStudents = async () => {
|
|||||||
improvementNotes: studentScore.improvementNotes || ''
|
improvementNotes: studentScore.improvementNotes || ''
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const missingGender = mappedStudents.some((student: any) => !student.gender);
|
||||||
|
if (missingGender && form.roomId) {
|
||||||
|
const roomRes = await request({
|
||||||
|
url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
||||||
|
method: 'get',
|
||||||
|
params: { roomId: form.roomId }
|
||||||
|
});
|
||||||
|
if (roomRes.code === 200 && roomRes.data) {
|
||||||
|
mappedStudents = mergeGenderFromRoomStudents(mappedStudents, roomRes.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roomStudents.value = mappedStudents;
|
||||||
|
|
||||||
console.log('转换后的学生数据:', roomStudents.value);
|
console.log('转换后的学生数据:', roomStudents.value);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -1076,6 +1091,7 @@ const loadRoomStudents = async () => {
|
|||||||
// 为每个学生设置默认值
|
// 为每个学生设置默认值
|
||||||
roomStudents.value = res.data.map((student: any) => ({
|
roomStudents.value = res.data.map((student: any) => ({
|
||||||
...student,
|
...student,
|
||||||
|
gender: normalizeGender(student.gender),
|
||||||
student_score: 0, // 个人得分,新增时默认为0
|
student_score: 0, // 个人得分,新增时默认为0
|
||||||
attendance_status: student.attendance_status || '1', // 出勤状态,默认1-在寝
|
attendance_status: student.attendance_status || '1', // 出勤状态,默认1-在寝
|
||||||
student_grade: student.student_grade || 'A' // 学生评级,默认A-优秀
|
student_grade: student.student_grade || 'A' // 学生评级,默认A-优秀
|
||||||
|
|||||||
+246
-132
@@ -47,30 +47,16 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="6">
|
<el-col :span="24">
|
||||||
<el-form-item label="违纪类型" prop="disciplineTypeId" class="discipline-form-item">
|
<DisciplineDeductionMultiSelect
|
||||||
<el-select
|
v-model="deductionItems"
|
||||||
v-model="hygieneForm.disciplineTypeId"
|
:options="disciplineTypeOptions"
|
||||||
placeholder="请选择违纪类型"
|
@update:total-score="handleDeductionTotalChange"
|
||||||
style="width: 100%"
|
/>
|
||||||
@change="handleDisciplineTypeChange"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in disciplineTypeOptions"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.typeName"
|
|
||||||
:value="item.id"
|
|
||||||
:data-score="item.minScore"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
|
|
||||||
<!-- 违纪类型说明展示 -->
|
|
||||||
<div v-if="hygieneForm.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-col>
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
<el-col :span="6">
|
<el-col :span="6">
|
||||||
<el-form-item label="宿舍扣分" prop="totalScore">
|
<el-form-item label="宿舍扣分" prop="totalScore">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -78,9 +64,9 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
placeholder="请输入扣分"
|
placeholder="自动合计"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleTotalScoreChange"
|
disabled
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -193,8 +179,8 @@
|
|||||||
<div class="student-basic-info">
|
<div class="student-basic-info">
|
||||||
<div class="student-name-with-gender">
|
<div class="student-name-with-gender">
|
||||||
<h4>{{ student.studentName }}</h4>
|
<h4>{{ student.studentName }}</h4>
|
||||||
<el-tag v-if="student.gender === '0'" type="primary" size="small">男</el-tag>
|
<el-tag v-if="isMaleGender(student.gender)" type="primary" size="small">男</el-tag>
|
||||||
<el-tag v-else class="female-tag" size="small">女</el-tag>
|
<el-tag v-else-if="isFemaleGender(student.gender)" class="female-tag" size="small">女</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ student.studentNo }}</p>
|
<p>{{ student.studentNo }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,6 +354,11 @@ import request from '@/utils/request';
|
|||||||
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
||||||
// @ts-expect-error - Component import without type declarations
|
// @ts-expect-error - Component import without type declarations
|
||||||
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
||||||
|
import DisciplineDeductionMultiSelect from './components/DisciplineDeductionMultiSelect.vue';
|
||||||
|
import type { HygieneDeductionItem } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||||
|
import { isFemaleGender, isMaleGender, mergeGenderFromRoomStudents, normalizeGender } from './genderUtils';
|
||||||
|
import { normalizeStudentId, normalizeId } from './studentIdUtils';
|
||||||
|
import { calcStudentViolationScoreTotal } from './violationScoreUtils';
|
||||||
|
|
||||||
// 组件实例
|
// 组件实例
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@@ -396,6 +387,7 @@ const studentsLoading = ref(false);
|
|||||||
const violationsLoading = ref(false);
|
const violationsLoading = ref(false);
|
||||||
const attachmentList = ref<any[]>([]);
|
const attachmentList = ref<any[]>([]);
|
||||||
const disciplineTypeOptions = ref<any[]>([]);
|
const disciplineTypeOptions = ref<any[]>([]);
|
||||||
|
const deductionItems = ref<HygieneDeductionItem[]>([]);
|
||||||
|
|
||||||
// 宿舍卫生检查表单
|
// 宿舍卫生检查表单
|
||||||
const hygieneForm = reactive({
|
const hygieneForm = reactive({
|
||||||
@@ -449,8 +441,8 @@ const hygieneRules = reactive({
|
|||||||
// 学生评分数据
|
// 学生评分数据
|
||||||
const studentScores = ref<any[]>([]);
|
const studentScores = ref<any[]>([]);
|
||||||
|
|
||||||
// 违纪记录数据
|
// 违纪记录数据(使用普通对象,避免 Map 变更无法触发 Vue 响应式更新)
|
||||||
const violationRecords = ref<Map<string | number, any[]>>(new Map());
|
const violationRecords = ref<Record<string, any[]>>({});
|
||||||
|
|
||||||
// 学生请假数据
|
// 学生请假数据
|
||||||
const studentLeaves = ref<Map<string | number, any>>(new Map());
|
const studentLeaves = ref<Map<string | number, any>>(new Map());
|
||||||
@@ -491,14 +483,10 @@ const violationDialogTitle = computed(() => {
|
|||||||
return currentStudent.value ? `${currentStudent.value.studentName} - 违纪记录录入` : '违纪记录录入';
|
return currentStudent.value ? `${currentStudent.value.studentName} - 违纪记录录入` : '违纪记录录入';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取违纪类型说明
|
// 扣分明细合计同步到宿舍扣分
|
||||||
const selectedDisciplineTypeRemark = computed(() => {
|
const handleDeductionTotalChange = (total: number) => {
|
||||||
if (hygieneForm.disciplineTypeId) {
|
hygieneForm.totalScore = total;
|
||||||
const selectedType = disciplineTypeOptions.value.find((item) => item.id === hygieneForm.disciplineTypeId);
|
};
|
||||||
return selectedType ? selectedType.remark : '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 上传配置
|
// 上传配置
|
||||||
const uploadAction = computed(() => {
|
const uploadAction = computed(() => {
|
||||||
@@ -558,24 +546,26 @@ const loadDisciplineTypes = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理违纪类型变化
|
// 初始化扣分明细
|
||||||
const handleDisciplineTypeChange = () => {
|
const initDeductionItems = (data?: any) => {
|
||||||
if (hygieneForm.disciplineTypeId) {
|
if (data?.deductionItems && data.deductionItems.length > 0) {
|
||||||
const selectedType = disciplineTypeOptions.value.find((item) => item.id === hygieneForm.disciplineTypeId);
|
deductionItems.value = data.deductionItems.map((item: HygieneDeductionItem, index: number) => ({
|
||||||
if (selectedType) {
|
...item,
|
||||||
hygieneForm.disciplineTypeName = selectedType.typeName;
|
sortOrder: item.sortOrder ?? index + 1
|
||||||
// 自动填入最低分
|
}));
|
||||||
if (selectedType.minScore !== null && selectedType.minScore !== undefined) {
|
} else if (data?.disciplineTypeId) {
|
||||||
const minScore = parseFloat(selectedType.minScore);
|
deductionItems.value = [
|
||||||
if (!isNaN(minScore)) {
|
{
|
||||||
hygieneForm.totalScore = minScore;
|
disciplineTypeId: data.disciplineTypeId,
|
||||||
ElMessage.success(`已自动填入最低扣分:${minScore}分`);
|
disciplineTypeName: data.disciplineTypeName,
|
||||||
}
|
score: data.totalScore ?? 0,
|
||||||
|
sortOrder: 1
|
||||||
}
|
}
|
||||||
}
|
];
|
||||||
} else {
|
} else {
|
||||||
hygieneForm.disciplineTypeName = '';
|
deductionItems.value = [];
|
||||||
}
|
}
|
||||||
|
hygieneForm.totalScore = deductionItems.value.reduce((sum, item) => sum + (Number(item.score) || 0), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化表单数据(已简化,主要逻辑移到watch中)
|
// 初始化表单数据(已简化,主要逻辑移到watch中)
|
||||||
@@ -594,32 +584,48 @@ const initFormData = async () => {
|
|||||||
|
|
||||||
// 加载学生评分数据
|
// 加载学生评分数据
|
||||||
const loadStudentScores = async () => {
|
const loadStudentScores = async () => {
|
||||||
console.log('loadStudentScores 方法被调用,hygieneForm.id:', hygieneForm.id);
|
const checkId = normalizeId(hygieneForm.id);
|
||||||
|
if (!checkId) {
|
||||||
if (!hygieneForm.id) {
|
|
||||||
console.log('没有检查ID,跳过学生数据加载');
|
console.log('没有检查ID,跳过学生数据加载');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始加载学生评分数据,检查ID:', hygieneForm.id);
|
console.log('开始加载学生评分数据,检查ID:', checkId);
|
||||||
studentsLoading.value = true;
|
studentsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${hygieneForm.id}`,
|
url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${checkId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('学生评分数据API响应:', res);
|
if (res.code === 200 && Array.isArray(res.data)) {
|
||||||
|
let scores = res.data.map((student: any) => ({
|
||||||
|
...student,
|
||||||
|
studentId: normalizeStudentId(student.studentId),
|
||||||
|
gender: normalizeGender(student.gender)
|
||||||
|
}));
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
const missingGender = scores.some((student: any) => !student.gender);
|
||||||
studentScores.value = res.data;
|
if (missingGender && hygieneForm.roomId) {
|
||||||
console.log('学生评分数据加载成功:', res.data.length, '条');
|
try {
|
||||||
|
const roomRes = await request({
|
||||||
|
url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
||||||
|
method: 'get',
|
||||||
|
params: { roomId: hygieneForm.roomId }
|
||||||
|
});
|
||||||
|
if (roomRes.code === 200 && roomRes.data) {
|
||||||
|
scores = mergeGenderFromRoomStudents(scores, roomRes.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('补全学生性别失败,将仅展示已关联到的性别信息', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 同时加载违纪记录和请假信息
|
studentScores.value = scores;
|
||||||
await Promise.all([loadViolationRecords(), loadStudentLeaves()]);
|
console.log('学生评分数据加载成功:', scores.length, '条');
|
||||||
|
await loadStudentLeaves();
|
||||||
} else {
|
} else {
|
||||||
studentScores.value = [];
|
studentScores.value = [];
|
||||||
console.log('学生评分数据为空,响应码:', res.code);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载学生评分数据失败:', error);
|
console.error('加载学生评分数据失败:', error);
|
||||||
@@ -631,29 +637,55 @@ const loadStudentScores = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 加载违纪记录数据
|
// 加载违纪记录数据
|
||||||
const loadViolationRecords = async () => {
|
const applyViolationList = (list: any[]) => {
|
||||||
if (!hygieneForm.id) return;
|
const grouped: Record<string, any[]> = {};
|
||||||
|
(list || []).forEach((violation: any) => {
|
||||||
|
const studentId = normalizeStudentId(violation.studentId);
|
||||||
|
if (!studentId) return;
|
||||||
|
if (!grouped[studentId]) {
|
||||||
|
grouped[studentId] = [];
|
||||||
|
}
|
||||||
|
grouped[studentId].push({
|
||||||
|
...violation,
|
||||||
|
studentId,
|
||||||
|
disciplineRecordId:
|
||||||
|
violation.disciplineRecordId != null ? normalizeId(violation.disciplineRecordId) : undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
violationRecords.value = grouped;
|
||||||
|
if (studentScores.value.length > 0) {
|
||||||
|
syncStudentScoreFromViolations();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadViolationRecords = async (prefetched?: any[] | null) => {
|
||||||
|
if (prefetched !== undefined) {
|
||||||
|
applyViolationList(prefetched || []);
|
||||||
|
console.log('违纪记录(预加载):', (prefetched || []).length, '条');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkId = normalizeId(hygieneForm.id || props.formData?.id);
|
||||||
|
if (!checkId) return;
|
||||||
|
|
||||||
|
violationsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await request({
|
const res = await request({
|
||||||
url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${hygieneForm.id}`,
|
url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${checkId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && Array.isArray(res.data)) {
|
||||||
// 重新组织违纪记录数据,按学生ID分组
|
applyViolationList(res.data);
|
||||||
violationRecords.value.clear();
|
console.log('违纪记录加载成功:', res.data.length, '条');
|
||||||
res.data.forEach((violation: any) => {
|
} else {
|
||||||
const studentId = violation.studentId;
|
violationRecords.value = {};
|
||||||
if (!violationRecords.value.has(studentId)) {
|
|
||||||
violationRecords.value.set(studentId, []);
|
|
||||||
}
|
|
||||||
violationRecords.value.get(studentId)?.push(violation);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载违纪记录数据失败:', error);
|
console.error('加载违纪记录数据失败:', error);
|
||||||
ElMessage.error('加载违纪记录数据失败');
|
ElMessage.error('加载违纪记录数据失败');
|
||||||
|
} finally {
|
||||||
|
violationsLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -675,9 +707,9 @@ const loadStudentLeaves = async () => {
|
|||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
studentLeaves.value.clear();
|
studentLeaves.value.clear();
|
||||||
Object.keys(res.data).forEach((studentId) => {
|
Object.keys(res.data).forEach((studentId) => {
|
||||||
studentLeaves.value.set(Number(studentId), res.data[studentId]);
|
const normalizedId = normalizeStudentId(studentId);
|
||||||
// 如果学生有请假信息,自动设置出勤状态为3(请假)
|
studentLeaves.value.set(normalizedId, res.data[studentId]);
|
||||||
const student = studentScores.value.find((s) => s.studentId === Number(studentId));
|
const student = studentScores.value.find((s) => normalizeStudentId(s.studentId) === normalizedId);
|
||||||
if (student) {
|
if (student) {
|
||||||
student.attendanceStatus = '3';
|
student.attendanceStatus = '3';
|
||||||
}
|
}
|
||||||
@@ -691,12 +723,31 @@ const loadStudentLeaves = async () => {
|
|||||||
|
|
||||||
// 获取学生的违纪记录
|
// 获取学生的违纪记录
|
||||||
const getStudentViolations = (studentId: string | number) => {
|
const getStudentViolations = (studentId: string | number) => {
|
||||||
return violationRecords.value.get(studentId) || [];
|
return violationRecords.value[normalizeStudentId(studentId)] || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 根据违纪记录合计同步个人扣分 */
|
||||||
|
const syncStudentScoreFromViolations = (studentId?: string | number) => {
|
||||||
|
const syncOne = (sid: string) => {
|
||||||
|
const student = studentScores.value.find((item) => normalizeStudentId(item.studentId) === sid);
|
||||||
|
if (student) {
|
||||||
|
student.studentScore = calcStudentViolationScoreTotal(getStudentViolations(sid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (studentId != null && studentId !== '') {
|
||||||
|
syncOne(normalizeStudentId(studentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
studentScores.value.forEach((student) => {
|
||||||
|
syncOne(normalizeStudentId(student.studentId));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取学生的请假信息
|
// 获取学生的请假信息
|
||||||
const getStudentLeave = (studentId: string | number) => {
|
const getStudentLeave = (studentId: string | number) => {
|
||||||
return studentLeaves.value.get(studentId) || null;
|
return studentLeaves.value.get(normalizeStudentId(studentId)) || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理附件数据
|
// 处理附件数据
|
||||||
@@ -829,7 +880,8 @@ const editViolationRecord = (student: any, violation: any, index: number) => {
|
|||||||
|
|
||||||
// 删除违纪记录
|
// 删除违纪记录
|
||||||
const removeViolationRecord = async (studentId: string | number, index: number) => {
|
const removeViolationRecord = async (studentId: string | number, index: number) => {
|
||||||
const violations = violationRecords.value.get(studentId) || [];
|
const normalizedId = normalizeStudentId(studentId);
|
||||||
|
const violations = [...(violationRecords.value[normalizedId] || [])];
|
||||||
const violation = violations[index];
|
const violation = violations[index];
|
||||||
|
|
||||||
// 检查是否为已撤销的违纪记录
|
// 检查是否为已撤销的违纪记录
|
||||||
@@ -851,11 +903,14 @@ const removeViolationRecord = async (studentId: string | number, index: number)
|
|||||||
|
|
||||||
violations.splice(index, 1);
|
violations.splice(index, 1);
|
||||||
|
|
||||||
|
const nextRecords = { ...violationRecords.value };
|
||||||
if (violations.length === 0) {
|
if (violations.length === 0) {
|
||||||
violationRecords.value.delete(studentId);
|
delete nextRecords[normalizedId];
|
||||||
} else {
|
} else {
|
||||||
violationRecords.value.set(studentId, violations);
|
nextRecords[normalizedId] = violations;
|
||||||
}
|
}
|
||||||
|
violationRecords.value = nextRecords;
|
||||||
|
syncStudentScoreFromViolations(normalizedId);
|
||||||
|
|
||||||
if (window.ElMessage) {
|
if (window.ElMessage) {
|
||||||
window.ElMessage.success('违纪记录删除成功');
|
window.ElMessage.success('违纪记录删除成功');
|
||||||
@@ -867,23 +922,32 @@ const removeViolationRecord = async (studentId: string | number, index: number)
|
|||||||
|
|
||||||
// 违纪记录确认
|
// 违纪记录确认
|
||||||
const handleViolationConfirm = (violationData: any) => {
|
const handleViolationConfirm = (violationData: any) => {
|
||||||
const studentId = currentStudent.value.studentId;
|
const studentId = normalizeStudentId(currentStudent.value.studentId);
|
||||||
const violations = violationRecords.value.get(studentId) || [];
|
const violations = [...(violationRecords.value[studentId] || [])];
|
||||||
|
|
||||||
if (currentViolationIndex.value >= 0) {
|
if (Array.isArray(violationData)) {
|
||||||
// 编辑模式
|
violations.push(...violationData);
|
||||||
|
if (window.ElMessage) {
|
||||||
|
window.ElMessage.success(`已添加 ${violationData.length} 条违纪记录`);
|
||||||
|
}
|
||||||
|
} else if (currentViolationIndex.value >= 0) {
|
||||||
violations[currentViolationIndex.value] = violationData;
|
violations[currentViolationIndex.value] = violationData;
|
||||||
|
if (window.ElMessage) {
|
||||||
|
window.ElMessage.success('违纪记录保存成功');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增模式
|
|
||||||
violations.push(violationData);
|
violations.push(violationData);
|
||||||
|
if (window.ElMessage) {
|
||||||
|
window.ElMessage.success('违纪记录保存成功');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
violationRecords.value.set(studentId, violations);
|
violationRecords.value = {
|
||||||
|
...violationRecords.value,
|
||||||
|
[studentId]: violations
|
||||||
|
};
|
||||||
|
syncStudentScoreFromViolations(studentId);
|
||||||
violationDialogVisible.value = false;
|
violationDialogVisible.value = false;
|
||||||
|
|
||||||
if (window.ElMessage) {
|
|
||||||
window.ElMessage.success('违纪记录保存成功');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 违纪记录弹窗关闭
|
// 违纪记录弹窗关闭
|
||||||
@@ -1110,8 +1174,16 @@ const submitForm = async () => {
|
|||||||
try {
|
try {
|
||||||
await hygieneFormRef.value?.validate();
|
await hygieneFormRef.value?.validate();
|
||||||
|
|
||||||
|
if ((hygieneForm.totalScore || 0) > 100) {
|
||||||
|
ElMessage.error('宿舍合计扣分不能超过100分');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
buttonLoading.value = true;
|
buttonLoading.value = true;
|
||||||
|
|
||||||
|
// 提交前同步个人扣分为违纪合计
|
||||||
|
syncStudentScoreFromViolations();
|
||||||
|
|
||||||
// 处理附件数据
|
// 处理附件数据
|
||||||
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
||||||
hygieneForm.attachmentIds = attachmentOssIds.join(',');
|
hygieneForm.attachmentIds = attachmentOssIds.join(',');
|
||||||
@@ -1138,12 +1210,30 @@ const submitForm = async () => {
|
|||||||
|
|
||||||
// 处理违纪记录数据
|
// 处理违纪记录数据
|
||||||
const violationData: any[] = [];
|
const violationData: any[] = [];
|
||||||
violationRecords.value.forEach((violations, studentId) => {
|
Object.entries(violationRecords.value).forEach(([studentId, violations]) => {
|
||||||
|
const student = studentScores.value.find((item) => normalizeStudentId(item.studentId) === studentId);
|
||||||
violations.forEach((violation) => {
|
violations.forEach((violation) => {
|
||||||
violationData.push({
|
violationData.push({
|
||||||
...violation,
|
disciplineRecordId:
|
||||||
studentId,
|
violation.disciplineRecordId != null ? normalizeId(violation.disciplineRecordId) : undefined,
|
||||||
violationSource: '1' // 宿舍卫生检查来源
|
studentId: normalizeStudentId(student?.studentId || violation.studentId || studentId),
|
||||||
|
studentNo: violation.studentNo || student?.studentNo || '',
|
||||||
|
studentName: violation.studentName || student?.studentName || '',
|
||||||
|
classId: violation.classId ?? student?.classId,
|
||||||
|
className: violation.className || student?.className || '',
|
||||||
|
majorId: violation.majorId ?? student?.majorId,
|
||||||
|
majorName: violation.majorName || student?.majorName || '',
|
||||||
|
collegeId: violation.collegeId ?? student?.collegeId,
|
||||||
|
collegeName: violation.collegeName || student?.collegeName || '',
|
||||||
|
disciplineTypeId: violation.disciplineTypeId,
|
||||||
|
disciplineTypeName: violation.disciplineTypeName,
|
||||||
|
disciplineDate: violation.disciplineDate,
|
||||||
|
disciplineReason: violation.disciplineReason,
|
||||||
|
disciplineContent: violation.disciplineContent,
|
||||||
|
scoreDeduction: violation.scoreDeduction,
|
||||||
|
status: violation.status || '1',
|
||||||
|
attachmentOssIds: violation.attachmentOssIds || '',
|
||||||
|
violationSource: '1'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1151,6 +1241,7 @@ const submitForm = async () => {
|
|||||||
// 构建提交数据
|
// 构建提交数据
|
||||||
const submitData = {
|
const submitData = {
|
||||||
...hygieneForm,
|
...hygieneForm,
|
||||||
|
deductionItems: deductionItems.value,
|
||||||
studentScores: studentScoresData,
|
studentScores: studentScoresData,
|
||||||
violationData
|
violationData
|
||||||
};
|
};
|
||||||
@@ -1171,44 +1262,67 @@ const handleClose = () => {
|
|||||||
emit('close');
|
emit('close');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听弹窗显示状态
|
const detailLoadToken = ref(0);
|
||||||
watch(
|
|
||||||
() => props.visible,
|
const syncFormFromProps = (data?: any) => {
|
||||||
async (visible, oldVisible) => {
|
if (!data) return;
|
||||||
console.log('弹窗显示状态变化:', oldVisible, '->', visible);
|
Object.assign(hygieneForm, {
|
||||||
if (visible) {
|
...data,
|
||||||
console.log('弹窗打开,开始初始化表单数据');
|
id: data.id != null ? normalizeId(data.id) : undefined,
|
||||||
// 只加载违纪类型数据,其他数据通过formData的watch来加载
|
roomId: data.roomId != null ? normalizeId(data.roomId) : undefined
|
||||||
await loadDisciplineTypes();
|
});
|
||||||
} else {
|
initDeductionItems(data);
|
||||||
console.log('弹窗关闭');
|
};
|
||||||
}
|
|
||||||
|
const loadCheckDetailData = async () => {
|
||||||
|
const checkId = normalizeId(props.formData?.id);
|
||||||
|
if (!props.visible || !checkId) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// 监听表单数据变化
|
const token = ++detailLoadToken.value;
|
||||||
|
syncFormFromProps(props.formData);
|
||||||
|
hygieneForm.id = checkId;
|
||||||
|
|
||||||
|
const hasPrefetchedViolations =
|
||||||
|
props.formData != null && Object.prototype.hasOwnProperty.call(props.formData, '_prefetchedViolations');
|
||||||
|
|
||||||
|
console.log('开始加载检查详情,checkId:', checkId);
|
||||||
|
try {
|
||||||
|
await loadStudentScores();
|
||||||
|
if (token !== detailLoadToken.value) return;
|
||||||
|
|
||||||
|
await loadViolationRecords(hasPrefetchedViolations ? props.formData._prefetchedViolations || [] : undefined);
|
||||||
|
if (token !== detailLoadToken.value) return;
|
||||||
|
|
||||||
|
syncStudentScoreFromViolations();
|
||||||
|
|
||||||
|
await handleAttachmentData();
|
||||||
|
console.log('检查详情加载完成');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载检查详情失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 弹窗打开 / 检查ID变化时加载详情(合并 visible + formData.id,避免竞态)
|
||||||
watch(
|
watch(
|
||||||
() => props.formData,
|
() => [props.visible, normalizeId(props.formData?.id)] as const,
|
||||||
async (newData, oldData) => {
|
async ([visible, checkId]) => {
|
||||||
console.log('props.formData 变化:', oldData, '->', newData);
|
if (!visible) {
|
||||||
if (newData) {
|
detailLoadToken.value += 1;
|
||||||
console.log('更新表单数据');
|
violationRecords.value = {};
|
||||||
Object.assign(hygieneForm, newData);
|
studentScores.value = [];
|
||||||
console.log('表单数据更新后,hygieneForm.id:', hygieneForm.id);
|
hygieneForm.id = undefined;
|
||||||
|
return;
|
||||||
// 如果弹窗是打开状态且有ID,需要重新加载相关数据
|
|
||||||
if (props.visible && hygieneForm.id) {
|
|
||||||
console.log('检测到有检查ID,重新加载学生数据和附件数据...');
|
|
||||||
try {
|
|
||||||
await Promise.all([loadStudentScores(), handleAttachmentData()]);
|
|
||||||
console.log('学生数据和附件数据重新加载完成');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('重新加载数据失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!checkId) return;
|
||||||
|
|
||||||
|
await loadDisciplineTypes();
|
||||||
|
await nextTick();
|
||||||
|
await loadCheckDetailData();
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+20
-5
@@ -201,12 +201,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="DormHygieneCheckMainDialog" lang="ts">
|
<script setup name="DormHygieneCheckMainDialog" lang="ts">
|
||||||
import { ref, reactive, computed, watch, getCurrentInstance, ComponentInternalInstance } from 'vue';
|
import { ref, reactive, computed, watch, getCurrentInstance, ComponentInternalInstance, nextTick } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { Check, Calendar } from '@element-plus/icons-vue';
|
import { Check, Calendar } from '@element-plus/icons-vue';
|
||||||
import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue';
|
import DormHygieneCheckSingleDialog from './DormHygieneCheckSingleDialog.vue';
|
||||||
import DormHygieneCheckEditDialog from './DormHygieneCheckEditDialog.vue';
|
import DormHygieneCheckEditDialog from './DormHygieneCheckEditDialog.vue';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
import { normalizeId } from './studentIdUtils';
|
||||||
|
|
||||||
// 组件实例
|
// 组件实例
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@@ -531,7 +532,7 @@ const handleRoomDoubleClick = async (room: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已有记录ID(新增成功后会保存ID)
|
// 检查是否已有记录ID(新增成功后会保存ID)
|
||||||
const checkId = roomCheckIds.value.get(room.id);
|
const checkId = normalizeId(roomCheckIds.value.get(room.id));
|
||||||
|
|
||||||
if (checkId) {
|
if (checkId) {
|
||||||
// 有记录ID,说明已经检查过,打开编辑弹窗
|
// 有记录ID,说明已经检查过,打开编辑弹窗
|
||||||
@@ -542,8 +543,22 @@ const handleRoomDoubleClick = async (room: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.code === 200 && response.data) {
|
if (response.code === 200 && response.data) {
|
||||||
|
let editPayload: Record<string, any> = {
|
||||||
|
...response.data,
|
||||||
|
id: checkId
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const violationsRes = await request({
|
||||||
|
url: `/dormHygieneCheck/dormHygieneCheck/getViolations/${checkId}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
editPayload._prefetchedViolations = violationsRes.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('预加载违纪记录失败,将在弹窗内重试', error);
|
||||||
|
}
|
||||||
currentRoom.value = room;
|
currentRoom.value = room;
|
||||||
currentEditData.value = response.data;
|
currentEditData.value = editPayload;
|
||||||
|
await nextTick();
|
||||||
editCheckVisible.value = true;
|
editCheckVisible.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -570,7 +585,7 @@ const handleSingleCheckSubmit = (data: any) => {
|
|||||||
|
|
||||||
// 保存检查记录ID(如果有返回)
|
// 保存检查记录ID(如果有返回)
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
roomCheckIds.value.set(currentRoom.value.id, data.id);
|
roomCheckIds.value.set(currentRoom.value.id, normalizeId(data.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessage.success(`${currentRoom.value.roomName} 检查完成`);
|
ElMessage.success(`${currentRoom.value.roomName} 检查完成`);
|
||||||
@@ -595,7 +610,7 @@ const handleEditCheckSubmit = async (data: any) => {
|
|||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
// 标记房间为已完成
|
// 标记房间为已完成
|
||||||
completedRooms.value.add(currentRoom.value.id);
|
completedRooms.value.add(currentRoom.value.id);
|
||||||
roomCheckIds.value.set(currentRoom.value.id, data.id);
|
roomCheckIds.value.set(currentRoom.value.id, normalizeId(data.id));
|
||||||
|
|
||||||
ElMessage.success(`${currentRoom.value.roomName} 检查记录更新完成`);
|
ElMessage.success(`${currentRoom.value.roomName} 检查记录更新完成`);
|
||||||
editCheckVisible.value = false;
|
editCheckVisible.value = false;
|
||||||
|
|||||||
+67
-65
@@ -12,30 +12,16 @@
|
|||||||
|
|
||||||
<el-form ref="hygieneFormRef" :model="hygieneForm" :rules="hygieneRules" label-width="120px">
|
<el-form ref="hygieneFormRef" :model="hygieneForm" :rules="hygieneRules" label-width="120px">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="24">
|
||||||
<el-form-item label="违纪类型" prop="disciplineTypeId" class="discipline-form-item">
|
<DisciplineDeductionMultiSelect
|
||||||
<el-select
|
v-model="deductionItems"
|
||||||
v-model="hygieneForm.disciplineTypeId"
|
:options="disciplineTypeOptions"
|
||||||
placeholder="请选择违纪类型"
|
@update:total-score="handleDeductionTotalChange"
|
||||||
style="width: 100%"
|
/>
|
||||||
@change="handleDisciplineTypeChange"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in disciplineTypeOptions"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.typeName"
|
|
||||||
:value="item.id"
|
|
||||||
:data-score="item.minScore"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
|
|
||||||
<!-- 违纪类型说明展示 -->
|
|
||||||
<div v-if="hygieneForm.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-col>
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="宿舍扣分" prop="totalScore">
|
<el-form-item label="宿舍扣分" prop="totalScore">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
@@ -43,9 +29,9 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="100"
|
:max="100"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
placeholder="请输入扣分"
|
placeholder="自动合计"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleTotalScoreChange"
|
disabled
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -151,8 +137,8 @@
|
|||||||
<div class="student-basic-info">
|
<div class="student-basic-info">
|
||||||
<div class="student-name-with-gender">
|
<div class="student-name-with-gender">
|
||||||
<h4>{{ student.studentName }}</h4>
|
<h4>{{ student.studentName }}</h4>
|
||||||
<el-tag v-if="student.gender === '0'" type="primary" size="small">男</el-tag>
|
<el-tag v-if="isMaleGender(student.gender)" type="primary" size="small">男</el-tag>
|
||||||
<el-tag v-else class="female-tag" size="small">女</el-tag>
|
<el-tag v-else-if="isFemaleGender(student.gender)" class="female-tag" size="small">女</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ student.studentNo }}</p>
|
<p>{{ student.studentNo }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -311,6 +297,11 @@ import request from '@/utils/request';
|
|||||||
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
||||||
// @ts-expect-error - Component import without type declarations
|
// @ts-expect-error - Component import without type declarations
|
||||||
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
||||||
|
import DisciplineDeductionMultiSelect from './components/DisciplineDeductionMultiSelect.vue';
|
||||||
|
import type { HygieneDeductionItem } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||||
|
import { isFemaleGender, isMaleGender } from './genderUtils';
|
||||||
|
import { normalizeStudentId } from './studentIdUtils';
|
||||||
|
import { calcStudentViolationScoreTotal } from './violationScoreUtils';
|
||||||
|
|
||||||
// 组件实例
|
// 组件实例
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@@ -339,6 +330,7 @@ const buttonLoading = ref(false);
|
|||||||
const studentsLoading = ref(false);
|
const studentsLoading = ref(false);
|
||||||
const attachmentList = ref<any[]>([]);
|
const attachmentList = ref<any[]>([]);
|
||||||
const disciplineTypeOptions = ref<any[]>([]);
|
const disciplineTypeOptions = ref<any[]>([]);
|
||||||
|
const deductionItems = ref<HygieneDeductionItem[]>([]);
|
||||||
|
|
||||||
// 宿舍卫生检查表单
|
// 宿舍卫生检查表单
|
||||||
const hygieneForm = reactive({
|
const hygieneForm = reactive({
|
||||||
@@ -448,21 +440,8 @@ const loadDisciplineTypes = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理违纪类型变化
|
const handleDeductionTotalChange = (total: number) => {
|
||||||
const handleDisciplineTypeChange = () => {
|
hygieneForm.totalScore = total;
|
||||||
if (hygieneForm.disciplineTypeId) {
|
|
||||||
const selectedType = disciplineTypeOptions.value.find((item) => item.id === hygieneForm.disciplineTypeId);
|
|
||||||
if (selectedType) {
|
|
||||||
hygieneForm.disciplineTypeName = selectedType.typeName;
|
|
||||||
// 自动填入最低分
|
|
||||||
if (selectedType.minScore !== null && selectedType.minScore !== undefined) {
|
|
||||||
hygieneForm.totalScore = selectedType.minScore;
|
|
||||||
ElMessage.success(`已自动填入最低扣分:${selectedType.minScore}分`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hygieneForm.disciplineTypeName = '';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载房间学生信息
|
// 加载房间学生信息
|
||||||
@@ -483,6 +462,7 @@ const loadRoomStudents = async () => {
|
|||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
roomStudents.value = res.data.map((student: any) => ({
|
roomStudents.value = res.data.map((student: any) => ({
|
||||||
...student,
|
...student,
|
||||||
|
studentId: normalizeStudentId(student.studentId),
|
||||||
student_score: 0,
|
student_score: 0,
|
||||||
attendance_status: '1',
|
attendance_status: '1',
|
||||||
student_grade: 'A'
|
student_grade: 'A'
|
||||||
@@ -520,9 +500,9 @@ const loadStudentLeaves = async () => {
|
|||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
studentLeaves.value.clear();
|
studentLeaves.value.clear();
|
||||||
Object.keys(res.data).forEach((studentId) => {
|
Object.keys(res.data).forEach((studentId) => {
|
||||||
studentLeaves.value.set(Number(studentId), res.data[studentId]);
|
const normalizedId = normalizeStudentId(studentId);
|
||||||
// 如果学生有请假信息,自动设置出勤状态为3(请假)
|
studentLeaves.value.set(normalizedId, res.data[studentId]);
|
||||||
const student = roomStudents.value.find((s) => s.studentId === Number(studentId));
|
const student = roomStudents.value.find((s) => normalizeStudentId(s.studentId) === normalizedId);
|
||||||
if (student) {
|
if (student) {
|
||||||
student.attendance_status = '3';
|
student.attendance_status = '3';
|
||||||
}
|
}
|
||||||
@@ -534,23 +514,32 @@ const loadStudentLeaves = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取违纪类型说明
|
|
||||||
const selectedDisciplineTypeRemark = computed(() => {
|
|
||||||
if (hygieneForm.disciplineTypeId) {
|
|
||||||
const selectedType = disciplineTypeOptions.value.find((item) => item.id === hygieneForm.disciplineTypeId);
|
|
||||||
return selectedType ? selectedType.remark : '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取学生的违纪记录
|
// 获取学生的违纪记录
|
||||||
const getStudentViolations = (studentId: string | number) => {
|
const getStudentViolations = (studentId: string | number) => {
|
||||||
return violationRecords.value.get(studentId) || [];
|
return violationRecords.value.get(normalizeStudentId(studentId)) || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncStudentScoreFromViolations = (studentId?: string | number) => {
|
||||||
|
const syncOne = (sid: string) => {
|
||||||
|
const student = roomStudents.value.find((item) => normalizeStudentId(item.studentId) === sid);
|
||||||
|
if (student) {
|
||||||
|
student.student_score = calcStudentViolationScoreTotal(getStudentViolations(sid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (studentId != null && studentId !== '') {
|
||||||
|
syncOne(normalizeStudentId(studentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
roomStudents.value.forEach((student) => {
|
||||||
|
syncOne(normalizeStudentId(student.studentId));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取学生的请假信息
|
// 获取学生的请假信息
|
||||||
const getStudentLeave = (studentId: string | number) => {
|
const getStudentLeave = (studentId: string | number) => {
|
||||||
return studentLeaves.value.get(studentId) || null;
|
return studentLeaves.value.get(normalizeStudentId(studentId)) || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 格式化日期时间
|
// 格式化日期时间
|
||||||
@@ -584,14 +573,16 @@ const removeViolationRecord = async (studentId: string | number, index: number)
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
});
|
});
|
||||||
|
|
||||||
const violations = violationRecords.value.get(studentId) || [];
|
const normalizedId = normalizeStudentId(studentId);
|
||||||
|
const violations = violationRecords.value.get(normalizedId) || [];
|
||||||
violations.splice(index, 1);
|
violations.splice(index, 1);
|
||||||
|
|
||||||
if (violations.length === 0) {
|
if (violations.length === 0) {
|
||||||
violationRecords.value.delete(studentId);
|
violationRecords.value.delete(normalizedId);
|
||||||
} else {
|
} else {
|
||||||
violationRecords.value.set(studentId, violations);
|
violationRecords.value.set(normalizedId, violations);
|
||||||
}
|
}
|
||||||
|
syncStudentScoreFromViolations(normalizedId);
|
||||||
|
|
||||||
ElMessage.success('违纪记录删除成功');
|
ElMessage.success('违纪记录删除成功');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -601,21 +592,23 @@ const removeViolationRecord = async (studentId: string | number, index: number)
|
|||||||
|
|
||||||
// 违纪记录确认
|
// 违纪记录确认
|
||||||
const handleViolationConfirm = (violationData: any) => {
|
const handleViolationConfirm = (violationData: any) => {
|
||||||
const studentId = currentStudent.value.studentId;
|
const studentId = normalizeStudentId(currentStudent.value.studentId);
|
||||||
const violations = violationRecords.value.get(studentId) || [];
|
const violations = violationRecords.value.get(studentId) || [];
|
||||||
|
|
||||||
if (currentViolationIndex.value >= 0) {
|
if (Array.isArray(violationData)) {
|
||||||
// 编辑模式
|
violations.push(...violationData);
|
||||||
|
ElMessage.success(`已添加 ${violationData.length} 条违纪记录`);
|
||||||
|
} else if (currentViolationIndex.value >= 0) {
|
||||||
violations[currentViolationIndex.value] = violationData;
|
violations[currentViolationIndex.value] = violationData;
|
||||||
|
ElMessage.success('违纪记录保存成功');
|
||||||
} else {
|
} else {
|
||||||
// 新增模式
|
|
||||||
violations.push(violationData);
|
violations.push(violationData);
|
||||||
|
ElMessage.success('违纪记录保存成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
violationRecords.value.set(studentId, violations);
|
violationRecords.value.set(studentId, violations);
|
||||||
|
syncStudentScoreFromViolations(studentId);
|
||||||
violationDialogVisible.value = false;
|
violationDialogVisible.value = false;
|
||||||
|
|
||||||
ElMessage.success('违纪记录保存成功');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 违纪记录弹窗关闭
|
// 违纪记录弹窗关闭
|
||||||
@@ -880,8 +873,15 @@ const submitForm = async () => {
|
|||||||
try {
|
try {
|
||||||
await hygieneFormRef.value?.validate();
|
await hygieneFormRef.value?.validate();
|
||||||
|
|
||||||
|
if ((hygieneForm.totalScore || 0) > 100) {
|
||||||
|
ElMessage.error('宿舍合计扣分不能超过100分');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
buttonLoading.value = true;
|
buttonLoading.value = true;
|
||||||
|
|
||||||
|
syncStudentScoreFromViolations();
|
||||||
|
|
||||||
// 处理附件数据
|
// 处理附件数据
|
||||||
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
||||||
hygieneForm.attachmentIds = attachmentOssIds.join(',');
|
hygieneForm.attachmentIds = attachmentOssIds.join(',');
|
||||||
@@ -938,6 +938,7 @@ const submitForm = async () => {
|
|||||||
|
|
||||||
// 宿舍整体评分
|
// 宿舍整体评分
|
||||||
...hygieneForm,
|
...hygieneForm,
|
||||||
|
deductionItems: deductionItems.value,
|
||||||
|
|
||||||
// 学生评分数据
|
// 学生评分数据
|
||||||
studentScores,
|
studentScores,
|
||||||
@@ -1003,6 +1004,7 @@ const resetForm = () => {
|
|||||||
checkRemarks: '',
|
checkRemarks: '',
|
||||||
attachmentIds: ''
|
attachmentIds: ''
|
||||||
});
|
});
|
||||||
|
deductionItems.value = [];
|
||||||
|
|
||||||
roomStudents.value = [];
|
roomStudents.value = [];
|
||||||
violationRecords.value.clear();
|
violationRecords.value.clear();
|
||||||
|
|||||||
+231
@@ -0,0 +1,231 @@
|
|||||||
|
<template>
|
||||||
|
<div class="deduction-multi-select">
|
||||||
|
<el-form-item label="扣分项" class="discipline-form-item">
|
||||||
|
<el-select
|
||||||
|
v-if="!disabled"
|
||||||
|
v-model="selectedTypeIds"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择扣分项(可多选)"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleTypeSelectionChange"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in options" :key="item.id" :label="formatOptionLabel(item)" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
<div v-else-if="!items.length" class="empty-text">无扣分项</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div v-if="items.length > 0" class="deduction-list">
|
||||||
|
<div v-for="(item, index) in items" :key="item.disciplineTypeId" class="deduction-item">
|
||||||
|
<div class="deduction-item-main">
|
||||||
|
<span class="deduction-name">{{ item.disciplineTypeName }}</span>
|
||||||
|
<el-input-number
|
||||||
|
v-model="item.score"
|
||||||
|
:min="getMinScore(item)"
|
||||||
|
:max="getMaxScore(item)"
|
||||||
|
:precision="2"
|
||||||
|
:disabled="disabled"
|
||||||
|
controls-position="right"
|
||||||
|
size="small"
|
||||||
|
@change="handleScoreChange"
|
||||||
|
/>
|
||||||
|
<span class="deduction-range">({{ getMinScore(item) }}-{{ getMaxScore(item) }}分)</span>
|
||||||
|
<el-button v-if="!disabled" link type="danger" @click="removeItem(index)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="getTypeRemark(item.disciplineTypeId)" class="deduction-remark">
|
||||||
|
<el-icon class="remark-icon"><InfoFilled /></el-icon>
|
||||||
|
<span>{{ getTypeRemark(item.disciplineTypeId) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="deduction-total">
|
||||||
|
合计扣分:<strong>{{ totalScore }}</strong> 分
|
||||||
|
<span v-if="totalScore > 100" class="total-warning">(已超过100分上限)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import type { HygieneDeductionItem } from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||||
|
|
||||||
|
interface DisciplineTypeOption {
|
||||||
|
id: number | string;
|
||||||
|
typeName: string;
|
||||||
|
minScore?: number | string;
|
||||||
|
maxScore?: number | string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: HygieneDeductionItem[];
|
||||||
|
options: DisciplineTypeOption[];
|
||||||
|
disabled?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
modelValue: () => [],
|
||||||
|
options: () => [],
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: HygieneDeductionItem[]): void;
|
||||||
|
(e: 'update:totalScore', value: number): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const selectedTypeIds = ref<Array<number | string>>([]);
|
||||||
|
|
||||||
|
const items = computed({
|
||||||
|
get: () => props.modelValue || [],
|
||||||
|
set: (value: HygieneDeductionItem[]) => emit('update:modelValue', value)
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalScore = computed(() => {
|
||||||
|
const sum = (props.modelValue || []).reduce((acc, item) => acc + (Number(item.score) || 0), 0);
|
||||||
|
return Number(sum.toFixed(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
selectedTypeIds.value = (value || []).map((item) => item.disciplineTypeId).filter((id) => id !== undefined && id !== null);
|
||||||
|
emit('update:totalScore', totalScore.value);
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatOptionLabel = (item: DisciplineTypeOption) => {
|
||||||
|
const min = item.minScore ?? 0;
|
||||||
|
return `${item.typeName}(${min}分起)`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeOption = (typeId?: number | string) => props.options.find((item) => String(item.id) === String(typeId));
|
||||||
|
|
||||||
|
const getMinScore = (item: HygieneDeductionItem) => {
|
||||||
|
const option = getTypeOption(item.disciplineTypeId);
|
||||||
|
return Number(option?.minScore ?? item.minScore ?? 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMaxScore = (item: HygieneDeductionItem) => {
|
||||||
|
const option = getTypeOption(item.disciplineTypeId);
|
||||||
|
return Number(option?.maxScore ?? item.maxScore ?? 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeRemark = (typeId?: number | string) => getTypeOption(typeId)?.remark || '';
|
||||||
|
|
||||||
|
const emitItems = (nextItems: HygieneDeductionItem[]) => {
|
||||||
|
items.value = nextItems;
|
||||||
|
emit('update:totalScore', nextItems.reduce((acc, item) => acc + (Number(item.score) || 0), 0));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTypeSelectionChange = (typeIds: Array<number | string>) => {
|
||||||
|
const currentMap = new Map((props.modelValue || []).map((item) => [String(item.disciplineTypeId), item]));
|
||||||
|
const nextItems: HygieneDeductionItem[] = [];
|
||||||
|
|
||||||
|
typeIds.forEach((typeId, index) => {
|
||||||
|
const option = getTypeOption(typeId);
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const existing = currentMap.get(String(typeId));
|
||||||
|
nextItems.push({
|
||||||
|
disciplineTypeId: option.id,
|
||||||
|
disciplineTypeName: option.typeName,
|
||||||
|
score: existing?.score ?? Number(option.minScore ?? 0),
|
||||||
|
minScore: Number(option.minScore ?? 0),
|
||||||
|
maxScore: Number(option.maxScore ?? 100),
|
||||||
|
sortOrder: index + 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
emitItems(nextItems);
|
||||||
|
if (nextItems.length > typeIds.length - 1) {
|
||||||
|
ElMessage.success('已添加扣分项');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScoreChange = () => {
|
||||||
|
if (totalScore.value > 100) {
|
||||||
|
ElMessage.warning('合计扣分不能超过100分');
|
||||||
|
}
|
||||||
|
emit('update:totalScore', totalScore.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = (index: number) => {
|
||||||
|
const nextItems = [...(props.modelValue || [])];
|
||||||
|
nextItems.splice(index, 1);
|
||||||
|
selectedTypeIds.value = nextItems.map((item) => item.disciplineTypeId);
|
||||||
|
emitItems(nextItems);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.deduction-list {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-item + .deduction-item {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px dashed #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-item-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-name {
|
||||||
|
min-width: 120px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-range {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-remark {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-total {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deduction-total strong {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-warning {
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+226
-18
@@ -11,8 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 违纪类型选择 -->
|
<!-- 违纪类型选择:编辑模式单选 -->
|
||||||
<el-form-item label="违纪类型" prop="disciplineTypeId" class="discipline-form-item">
|
<el-form-item v-if="isEditMode" label="违纪类型" prop="disciplineTypeId" class="discipline-form-item">
|
||||||
<el-select v-model="form.disciplineTypeId" placeholder="请选择违纪类型" style="width: 100%" @change="handleDisciplineTypeChange" filterable>
|
<el-select v-model="form.disciplineTypeId" placeholder="请选择违纪类型" style="width: 100%" @change="handleDisciplineTypeChange" filterable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in allViolationTypes"
|
v-for="item in allViolationTypes"
|
||||||
@@ -31,13 +31,70 @@
|
|||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<!-- 违纪类型说明展示 -->
|
|
||||||
<div v-if="form.disciplineTypeId && selectedDisciplineTypeRemark" class="discipline-type-remark">
|
<div v-if="form.disciplineTypeId && selectedDisciplineTypeRemark" class="discipline-type-remark">
|
||||||
<el-icon class="remark-icon"><InfoFilled /></el-icon>
|
<el-icon class="remark-icon"><InfoFilled /></el-icon>
|
||||||
<span class="remark-text">类型说明:{{ selectedDisciplineTypeRemark }}</span>
|
<span class="remark-text">类型说明:{{ selectedDisciplineTypeRemark }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 违纪类型选择:新增模式多选 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-form-item label="违纪类型" class="discipline-form-item">
|
||||||
|
<el-select
|
||||||
|
v-model="selectedTypeIds"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择违纪类型(可多选)"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleTypeSelectionChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in allViolationTypes"
|
||||||
|
:key="item.id"
|
||||||
|
:label="getViolationTypeLabel(item)"
|
||||||
|
:value="item.id"
|
||||||
|
:disabled="item.status !== '0'"
|
||||||
|
>
|
||||||
|
<div class="violation-type-option">
|
||||||
|
<span class="violation-type-name" :title="getViolationTypeLabel(item)">
|
||||||
|
<span class="area-tag" :class="getAreaTypeClass(item.areaType)">{{ getAreaTypeText(item.areaType) }}</span>
|
||||||
|
{{ getShortTypeName(item.typeName) }}
|
||||||
|
</span>
|
||||||
|
<span class="score-range">{{ item.minScore }}-{{ item.maxScore }}分</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<div v-if="violationItems.length > 0" class="violation-item-list">
|
||||||
|
<div v-for="(item, index) in violationItems" :key="item.disciplineTypeId" class="violation-item-row">
|
||||||
|
<div class="violation-item-main">
|
||||||
|
<span class="violation-item-name">{{ item.disciplineTypeName }}</span>
|
||||||
|
<el-input-number
|
||||||
|
v-model="item.scoreDeduction"
|
||||||
|
:min="getItemMinScore(item)"
|
||||||
|
:max="getItemMaxScore(item)"
|
||||||
|
:precision="2"
|
||||||
|
controls-position="right"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<span class="score-range">({{ getItemMinScore(item) }}-{{ getItemMaxScore(item) }}分)</span>
|
||||||
|
<el-button link type="danger" @click="removeViolationItem(index)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="getItemRemark(item.disciplineTypeId)" class="discipline-type-remark inline-remark">
|
||||||
|
<el-icon class="remark-icon"><InfoFilled /></el-icon>
|
||||||
|
<span class="remark-text">类型说明:{{ getItemRemark(item.disciplineTypeId) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="violation-item-total">
|
||||||
|
合计扣分:<strong>{{ violationItemsTotalScore }}</strong> 分
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 违纪日期和扣分分值 - 同一行显示 -->
|
<!-- 违纪日期和扣分分值 - 同一行显示 -->
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -53,12 +110,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="扣分分值" prop="scoreDeduction">
|
<el-form-item v-if="isEditMode" label="扣分分值" prop="scoreDeduction">
|
||||||
<el-input-number v-model="form.scoreDeduction" :precision="2" placeholder="请输入扣分" style="width: 100%" />
|
<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-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -186,7 +239,7 @@ interface Props {
|
|||||||
// 组件事件
|
// 组件事件
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'update:modelValue', value: boolean): void;
|
(e: 'update:modelValue', value: boolean): void;
|
||||||
(e: 'confirm', data: any): void;
|
(e: 'confirm', data: any | any[]): void;
|
||||||
(e: 'close'): void;
|
(e: 'close'): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +260,15 @@ const attachmentList = ref<any[]>([]);
|
|||||||
// 所有违纪类型列表
|
// 所有违纪类型列表
|
||||||
const allViolationTypes = ref<any[]>([]);
|
const allViolationTypes = ref<any[]>([]);
|
||||||
|
|
||||||
|
interface ViolationItem {
|
||||||
|
disciplineTypeId: string | number;
|
||||||
|
disciplineTypeName: string;
|
||||||
|
scoreDeduction: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedTypeIds = ref<Array<string | number>>([]);
|
||||||
|
const violationItems = ref<ViolationItem[]>([]);
|
||||||
|
|
||||||
// 当前选择的违纪类型扣分范围
|
// 当前选择的违纪类型扣分范围
|
||||||
const currentTypeMinScore = ref(0);
|
const currentTypeMinScore = ref(0);
|
||||||
const currentTypeMaxScore = ref(100);
|
const currentTypeMaxScore = ref(100);
|
||||||
@@ -236,8 +298,7 @@ const form = reactive({
|
|||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
disciplineTypeId: [{ required: true, message: '请选择违纪类型', trigger: 'change' }],
|
disciplineTypeId: [{ required: true, message: '请选择违纪类型', trigger: 'change' }],
|
||||||
disciplineDate: [{ required: true, message: '请选择违纪日期', trigger: 'change' }],
|
disciplineDate: [{ required: true, message: '请选择违纪日期', trigger: 'change' }],
|
||||||
scoreDeduction: [{ required: true, message: '请输入扣分分值', trigger: 'blur' }],
|
scoreDeduction: [{ required: true, message: '请输入扣分分值', trigger: 'blur' }]
|
||||||
disciplineReason: [{ required: true, message: '请输入违纪原因', trigger: 'blur' }]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
@@ -248,6 +309,13 @@ const dialogVisible = computed({
|
|||||||
|
|
||||||
const dialogTitle = computed(() => props.title);
|
const dialogTitle = computed(() => props.title);
|
||||||
|
|
||||||
|
const isEditMode = computed(() => !!props.violationData);
|
||||||
|
|
||||||
|
const violationItemsTotalScore = computed(() => {
|
||||||
|
const sum = violationItems.value.reduce((acc, item) => acc + (Number(item.scoreDeduction) || 0), 0);
|
||||||
|
return Number(sum.toFixed(2));
|
||||||
|
});
|
||||||
|
|
||||||
// 上传配置
|
// 上传配置
|
||||||
const uploadAction = computed(() => {
|
const uploadAction = computed(() => {
|
||||||
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
|
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
|
||||||
@@ -314,6 +382,67 @@ const getAllViolationTypes = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTypeOption = (typeId?: string | number) =>
|
||||||
|
allViolationTypes.value.find((item) => String(item.id) === String(typeId));
|
||||||
|
|
||||||
|
const parseScore = (value: unknown, fallback = 0) => {
|
||||||
|
if (value === null || value === undefined) return fallback;
|
||||||
|
return typeof value === 'number' ? value : parseFloat(String(value)) || fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemMinScore = (item: ViolationItem) => parseScore(getTypeOption(item.disciplineTypeId)?.minScore, 0);
|
||||||
|
|
||||||
|
const getItemMaxScore = (item: ViolationItem) => parseScore(getTypeOption(item.disciplineTypeId)?.maxScore, 100);
|
||||||
|
|
||||||
|
const getItemRemark = (typeId?: string | number) => getTypeOption(typeId)?.remark || '';
|
||||||
|
|
||||||
|
const handleTypeSelectionChange = (typeIds: Array<string | number>) => {
|
||||||
|
const currentMap = new Map(violationItems.value.map((item) => [String(item.disciplineTypeId), item]));
|
||||||
|
const nextItems: ViolationItem[] = [];
|
||||||
|
|
||||||
|
typeIds.forEach((typeId) => {
|
||||||
|
const option = getTypeOption(typeId);
|
||||||
|
if (!option) return;
|
||||||
|
const existing = currentMap.get(String(typeId));
|
||||||
|
nextItems.push({
|
||||||
|
disciplineTypeId: option.id,
|
||||||
|
disciplineTypeName: option.typeName,
|
||||||
|
scoreDeduction: existing?.scoreDeduction ?? parseScore(option.minScore, 0)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
violationItems.value = nextItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeViolationItem = (index: number) => {
|
||||||
|
violationItems.value.splice(index, 1);
|
||||||
|
selectedTypeIds.value = violationItems.value.map((item) => item.disciplineTypeId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildSharedViolationFields = () => {
|
||||||
|
const attachmentOssIds = attachmentList.value
|
||||||
|
.filter((file) => file.status === 'success' && file.ossId)
|
||||||
|
.map((file) => file.ossId)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
return {
|
||||||
|
studentId: form.studentId,
|
||||||
|
studentNo: form.studentNo,
|
||||||
|
studentName: form.studentName,
|
||||||
|
collegeId: form.collegeId,
|
||||||
|
collegeName: form.collegeName,
|
||||||
|
majorId: form.majorId,
|
||||||
|
majorName: form.majorName,
|
||||||
|
classId: form.classId,
|
||||||
|
className: form.className,
|
||||||
|
disciplineDate: form.disciplineDate,
|
||||||
|
disciplineReason: form.disciplineReason,
|
||||||
|
disciplineContent: form.disciplineContent,
|
||||||
|
attachmentOssIds,
|
||||||
|
violationSource: form.violationSource || '1'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 违纪类型变化处理
|
// 违纪类型变化处理
|
||||||
const handleDisciplineTypeChange = (typeId: string | number) => {
|
const handleDisciplineTypeChange = (typeId: string | number) => {
|
||||||
const selectedType = allViolationTypes.value.find((item) => item.id === typeId);
|
const selectedType = allViolationTypes.value.find((item) => item.id === typeId);
|
||||||
@@ -414,8 +543,9 @@ const initForm = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('违纪记录:新增模式,重置附件列表');
|
console.log('违纪记录:新增模式,重置附件列表');
|
||||||
// 重置附件列表
|
|
||||||
attachmentList.value = [];
|
attachmentList.value = [];
|
||||||
|
selectedTypeIds.value = [];
|
||||||
|
violationItems.value = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -695,15 +825,49 @@ const handleDownloadAttachment = async (file: any) => {
|
|||||||
// 确认提交
|
// 确认提交
|
||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
await violationFormRef.value?.validate();
|
|
||||||
|
|
||||||
buttonLoading.value = true;
|
buttonLoading.value = true;
|
||||||
|
|
||||||
// 处理附件数据
|
if (isEditMode.value) {
|
||||||
const attachmentOssIds = attachmentList.value.filter((file) => file.status === 'success' && file.ossId).map((file) => file.ossId);
|
await violationFormRef.value?.validate();
|
||||||
form.attachmentOssIds = attachmentOssIds.join(',');
|
const attachmentOssIds = attachmentList.value
|
||||||
|
.filter((file) => file.status === 'success' && file.ossId)
|
||||||
|
.map((file) => file.ossId);
|
||||||
|
form.attachmentOssIds = attachmentOssIds.join(',');
|
||||||
|
emit('confirm', { ...form });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emit('confirm', { ...form });
|
if (!form.disciplineDate) {
|
||||||
|
ElMessage.warning('请选择违纪日期');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (violationItems.value.length === 0) {
|
||||||
|
ElMessage.warning('请至少选择一个违纪类型');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of violationItems.value) {
|
||||||
|
const min = getItemMinScore(item);
|
||||||
|
const max = getItemMaxScore(item);
|
||||||
|
const score = Number(item.scoreDeduction);
|
||||||
|
if (Number.isNaN(score)) {
|
||||||
|
ElMessage.warning(`请填写「${item.disciplineTypeName}」的扣分分值`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (score < min || score > max) {
|
||||||
|
ElMessage.warning(`「${item.disciplineTypeName}」扣分须在 ${min}-${max} 分之间`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedFields = buildSharedViolationFields();
|
||||||
|
const records = violationItems.value.map((item) => ({
|
||||||
|
...sharedFields,
|
||||||
|
disciplineTypeId: item.disciplineTypeId,
|
||||||
|
disciplineTypeName: item.disciplineTypeName,
|
||||||
|
scoreDeduction: item.scoreDeduction
|
||||||
|
}));
|
||||||
|
emit('confirm', records);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交失败:', error);
|
console.error('提交失败:', error);
|
||||||
ElMessage.error('请检查表单填写是否完整');
|
ElMessage.error('请检查表单填写是否完整');
|
||||||
@@ -729,6 +893,8 @@ const handleClose = () => {
|
|||||||
attachmentList.value = [];
|
attachmentList.value = [];
|
||||||
currentTypeMinScore.value = 0;
|
currentTypeMinScore.value = 0;
|
||||||
currentTypeMaxScore.value = 100;
|
currentTypeMaxScore.value = 100;
|
||||||
|
selectedTypeIds.value = [];
|
||||||
|
violationItems.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
@@ -775,6 +941,8 @@ const resetForm = () => {
|
|||||||
attachmentList.value = [];
|
attachmentList.value = [];
|
||||||
currentTypeMinScore.value = 0;
|
currentTypeMinScore.value = 0;
|
||||||
currentTypeMaxScore.value = 100;
|
currentTypeMaxScore.value = 100;
|
||||||
|
selectedTypeIds.value = [];
|
||||||
|
violationItems.value = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取违纪类型说明
|
// 获取违纪类型说明
|
||||||
@@ -898,6 +1066,46 @@ onMounted(() => {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.violation-item-list {
|
||||||
|
margin: 0 0 16px 120px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fb;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-row + .violation-item-row {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px dashed #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-name {
|
||||||
|
min-width: 120px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.violation-item-total {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-remark {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 区域标签样式 */
|
/* 区域标签样式 */
|
||||||
.area-tag {
|
.area-tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/** 归一化性别值:0=男,1=女 */
|
||||||
|
export function normalizeGender(gender: unknown): string | undefined {
|
||||||
|
if (gender === null || gender === undefined || gender === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const value = String(gender);
|
||||||
|
if (value === '0' || value === '男') {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
if (value === '1' || value === '女') {
|
||||||
|
return '1';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMaleGender(gender: unknown): boolean {
|
||||||
|
return normalizeGender(gender) === '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFemaleGender(gender: unknown): boolean {
|
||||||
|
return normalizeGender(gender) === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用房间在住学生信息补全缺失的性别 */
|
||||||
|
export function mergeGenderFromRoomStudents<T extends { studentId?: unknown; studentNo?: string; gender?: unknown }>(
|
||||||
|
students: T[],
|
||||||
|
roomStudents: Array<{ studentId?: unknown; studentNo?: string; gender?: unknown }>
|
||||||
|
): T[] {
|
||||||
|
if (!students.length || !roomStudents.length) {
|
||||||
|
return students;
|
||||||
|
}
|
||||||
|
|
||||||
|
const genderByStudentId = new Map<string, string>();
|
||||||
|
const genderByStudentNo = new Map<string, string>();
|
||||||
|
roomStudents.forEach((student) => {
|
||||||
|
const gender = normalizeGender(student.gender);
|
||||||
|
if (!gender) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (student.studentId !== undefined && student.studentId !== null) {
|
||||||
|
genderByStudentId.set(String(student.studentId), gender);
|
||||||
|
}
|
||||||
|
if (student.studentNo) {
|
||||||
|
genderByStudentNo.set(student.studentNo, gender);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return students.map((student) => {
|
||||||
|
if (normalizeGender(student.gender)) {
|
||||||
|
return { ...student, gender: normalizeGender(student.gender) };
|
||||||
|
}
|
||||||
|
const gender =
|
||||||
|
(student.studentId !== undefined && student.studentId !== null
|
||||||
|
? genderByStudentId.get(String(student.studentId))
|
||||||
|
: undefined) || (student.studentNo ? genderByStudentNo.get(student.studentNo) : undefined);
|
||||||
|
return gender ? { ...student, gender } : student;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
<el-table-column label="房间名称" align="center" prop="roomName" />
|
<el-table-column label="房间名称" align="center" prop="roomName" />
|
||||||
<el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
|
<el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
|
||||||
<el-table-column label="校区名称" align="center" prop="campusName" />
|
<el-table-column label="校区名称" align="center" prop="campusName" />
|
||||||
|
<el-table-column label="扣分项" align="center" prop="disciplineTypeName" min-width="160" show-overflow-tooltip />
|
||||||
<el-table-column label="宿舍扣分" align="center" prop="totalScore">
|
<el-table-column label="宿舍扣分" align="center" prop="totalScore">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>-{{ scope.row.totalScore }}</span>
|
<span>-{{ scope.row.totalScore }}</span>
|
||||||
@@ -163,6 +164,7 @@
|
|||||||
|
|
||||||
<!-- 编辑宿舍卫生检查弹窗组件(修改使用) -->
|
<!-- 编辑宿舍卫生检查弹窗组件(修改使用) -->
|
||||||
<DormHygieneCheckEditDialog
|
<DormHygieneCheckEditDialog
|
||||||
|
:key="editDialogKey"
|
||||||
:visible="editDialog.visible"
|
:visible="editDialog.visible"
|
||||||
:title="editDialog.title"
|
:title="editDialog.title"
|
||||||
:form-data="currentEditData"
|
:form-data="currentEditData"
|
||||||
@@ -186,7 +188,8 @@ import {
|
|||||||
delDormHygieneCheck,
|
delDormHygieneCheck,
|
||||||
addDormHygieneCheck,
|
addDormHygieneCheck,
|
||||||
updateDormHygieneCheck,
|
updateDormHygieneCheck,
|
||||||
exportDormHygieneCheck
|
exportDormHygieneCheck,
|
||||||
|
getViolationsByCheckId
|
||||||
} from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index';
|
} from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/index';
|
||||||
import {
|
import {
|
||||||
DormHygieneCheckVO,
|
DormHygieneCheckVO,
|
||||||
@@ -196,6 +199,7 @@ import {
|
|||||||
} from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
} from '@/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types';
|
||||||
import DormHygieneCheckMainDialog from './DormHygieneCheckMainDialog.vue';
|
import DormHygieneCheckMainDialog from './DormHygieneCheckMainDialog.vue';
|
||||||
import DormHygieneCheckEditDialog from './DormHygieneCheckEditDialog.vue';
|
import DormHygieneCheckEditDialog from './DormHygieneCheckEditDialog.vue';
|
||||||
|
import { normalizeId } from './studentIdUtils';
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
interface DialogOption {
|
interface DialogOption {
|
||||||
@@ -238,6 +242,7 @@ const editDialog = reactive<DialogOption>({
|
|||||||
|
|
||||||
// 存储当前编辑的数据
|
// 存储当前编辑的数据
|
||||||
const currentEditData = ref<any>(null);
|
const currentEditData = ref<any>(null);
|
||||||
|
const editDialogKey = ref(0);
|
||||||
|
|
||||||
const initFormData: DormHygieneCheckForm = {
|
const initFormData: DormHygieneCheckForm = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -311,7 +316,10 @@ const getList = async () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await listDormHygieneCheck(queryParams.value);
|
const res = await listDormHygieneCheck(queryParams.value);
|
||||||
dormHygieneCheckList.value = res.rows;
|
dormHygieneCheckList.value = (res.rows || []).map((row: DormHygieneCheckVO) => ({
|
||||||
|
...row,
|
||||||
|
id: row.id != null ? normalizeId(row.id) : row.id
|
||||||
|
}));
|
||||||
total.value = res.total;
|
total.value = res.total;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('查询列表失败:', error);
|
console.error('查询列表失败:', error);
|
||||||
@@ -407,7 +415,6 @@ const handleAdd = () => {
|
|||||||
|
|
||||||
/** 修改按钮操作 */
|
/** 修改按钮操作 */
|
||||||
const handleUpdate = async (row?: DormHygieneCheckVO) => {
|
const handleUpdate = async (row?: DormHygieneCheckVO) => {
|
||||||
editDialog.visible = true;
|
|
||||||
editDialog.title = '修改宿舍卫生检查';
|
editDialog.title = '修改宿舍卫生检查';
|
||||||
|
|
||||||
const loadingInstance = ElLoading.service({
|
const loadingInstance = ElLoading.service({
|
||||||
@@ -417,9 +424,15 @@ const handleUpdate = async (row?: DormHygieneCheckVO) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const _id = row?.id || ids.value[0];
|
const _id = normalizeId(row?.id || ids.value[0]);
|
||||||
const res = await getDormHygieneCheck(_id);
|
const [res, violationsRes] = await Promise.all([getDormHygieneCheck(_id), getViolationsByCheckId(_id)]);
|
||||||
currentEditData.value = res.data;
|
currentEditData.value = {
|
||||||
|
...res.data,
|
||||||
|
id: res.data?.id != null ? normalizeId(res.data.id) : res.data?.id,
|
||||||
|
_prefetchedViolations: violationsRes.data || []
|
||||||
|
};
|
||||||
|
editDialogKey.value += 1;
|
||||||
|
editDialog.visible = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载编辑数据失败:', error);
|
console.error('加载编辑数据失败:', error);
|
||||||
ElMessage.error('加载数据失败,请重试');
|
ElMessage.error('加载数据失败,请重试');
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** 雪花ID统一转字符串,避免 JS Number 精度丢失导致 Map/接口匹配失败 */
|
||||||
|
export function normalizeStudentId(studentId: unknown): string {
|
||||||
|
if (studentId === null || studentId === undefined || studentId === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(studentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用雪花ID归一化(检查ID、学生ID等) */
|
||||||
|
export const normalizeId = normalizeStudentId;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/** 计算学生有效违纪记录的扣分合计(已撤销 status=2 不计入) */
|
||||||
|
export function calcStudentViolationScoreTotal(violations: any[] | undefined | null): number {
|
||||||
|
const total = (violations || [])
|
||||||
|
.filter((item) => item?.status !== '2')
|
||||||
|
.reduce((sum, item) => sum + (parseFloat(String(item?.scoreDeduction ?? 0)) || 0), 0);
|
||||||
|
return parseFloat(total.toFixed(2));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user