Przeglądaj źródła

wuzhen update 招生就业相关修改

“wuzhen” 4 miesięcy temu
rodzic
commit
6546732bb2
29 zmienionych plików z 1389 dodań i 287 usunięć
  1. 17 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBedDetailVo.java
  2. 37 3
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java
  3. 36 0
      backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java
  4. 7 9
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentInfoController.java
  5. 54 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentMajorController.java
  6. 11 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentProcessController.java
  7. 43 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/ZsjyEnrollmentMajor.java
  8. 23 2
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyEnrollmentInfoBo.java
  9. 19 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyEnrollmentMajorSaveBo.java
  10. 59 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentDeptVo.java
  11. 5 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentProcessVo.java
  12. 40 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentRegisterExportVo.java
  13. 28 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/mapper/ZsjyEnrollmentMajorMapper.java
  14. 22 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/IZsjyEnrollmentMajorService.java
  15. 9 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/IZsjyEnrollmentProcessService.java
  16. 120 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyEnrollmentMajorServiceImpl.java
  17. 55 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyEnrollmentProcessServiceImpl.java
  18. 88 0
      backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/zsjy/ZsjyEnrollmentMajorMapper.xml
  19. 2 1
      backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/zsjy/ZsjyEnrollmentProcessMapper.xml
  20. 3 3
      mobile/src/pages.json
  21. 73 63
      mobile/src/pages/enrollment/index.vue
  22. 1 1
      mobile/src/pages/index/index.vue
  23. 30 53
      web/src/views/zsjy/enrollment/apply.vue
  24. 58 0
      web/src/views/zsjy/enrollment/enrollmentFormRules.ts
  25. 147 0
      web/src/views/zsjy/enrollment/enrollmentPrint.ts
  26. 140 149
      web/src/views/zsjy/enrollment/index.vue
  27. 28 0
      web/src/views/zsjy/enrollment/uniformSizeOptions.ts
  28. 227 0
      web/src/views/zsjy/enrollmentMajor/index.vue
  29. 7 3
      web/src/views/zsjy/enrollmentStatistics/index.vue

+ 17 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormBedDetailVo.java

@@ -199,4 +199,21 @@ public class XgDormBedDetailVo implements Serializable {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
+
+    // =============== 请假信息 ===============
+
+    private LeaveInfo leaveInfo;
+
+    @Data
+    public static class LeaveInfo implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        private Long leaveId;
+        private String leaveTypeName;
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date leaveStartTime;
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date leaveEndTime;
+    }
 }

+ 37 - 3
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java

@@ -16,12 +16,13 @@ import org.dromara.xg.domain.vo.XgDormBedDetailVo;
 import org.dromara.xg.domain.XgDormBed;
 import org.dromara.xg.domain.XgDormRoom;
 import org.dromara.xg.domain.XgDormBedStudent;
-import org.dromara.xg.domain.XgSysDept;
+import org.dromara.xg.domain.vo.XgStudentLeaveVo;
 import org.dromara.xg.mapper.XgDormBedMapper;
 import org.dromara.xg.mapper.XgDormRoomMapper;
 import org.dromara.xg.mapper.XgDormBedStudentMapper;
 import org.dromara.xg.mapper.XgSysDeptMapper;
 import org.dromara.xg.service.IXgDormBedService;
+import org.dromara.xg.service.IXgStudentLeaveService;
 import org.dromara.common.satoken.utils.LoginHelper;
 
 import java.util.List;
@@ -31,6 +32,8 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Objects;
+import java.time.LocalDateTime;
 
 /**
  * 宿舍床位信息Service业务层处理
@@ -47,6 +50,7 @@ public class XgDormBedServiceImpl implements IXgDormBedService {
     private final XgDormRoomMapper xgDormRoomMapper;
     private final XgDormBedStudentMapper xgDormBedStudentMapper;
     private final XgSysDeptMapper xgSysDeptMapper;
+    private final IXgStudentLeaveService xgStudentLeaveService;
 
     /**
      * 查询宿舍床位信息
@@ -160,9 +164,25 @@ public class XgDormBedServiceImpl implements IXgDormBedService {
     public List<XgDormBedDetailVo> queryBedDetailsByRoomId(Long roomId) {
         List<XgDormBedDetailVo> result = new ArrayList<>();
 
-        // 第一步:从 xg_dorm_bed_student 为主表查有学生的床位
         List<Map<String, Object>> studentBeds = xgDormBedStudentMapper.selectRoomStudents(roomId);
 
+        List<Long> studentIds = studentBeds.stream()
+            .map(row -> {
+                Object studentId = row.get("student_id");
+                if (studentId instanceof Number) {
+                    return ((Number) studentId).longValue();
+                }
+                return null;
+            })
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
+
+        Map<Long, XgStudentLeaveVo> leaveMap = Map.of();
+        if (!studentIds.isEmpty()) {
+            leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate(
+                studentIds, LocalDateTime.now());
+        }
+
         Set<Long> occupiedBedIds = new HashSet<>();
         for (Map<String, Object> row : studentBeds) {
             XgDormBedDetailVo vo = new XgDormBedDetailVo();
@@ -184,10 +204,24 @@ public class XgDormBedServiceImpl implements IXgDormBedService {
             vo.setMajorName(row.get("major_name") != null ? row.get("major_name").toString() : null);
             vo.setAssignmentStatus("0");
             vo.setBedStatus("1");
+
+            Object studentIdObj = row.get("student_id");
+            if (studentIdObj instanceof Number) {
+                Long studentIdValue = ((Number) studentIdObj).longValue();
+                XgStudentLeaveVo leaveVo = leaveMap.get(studentIdValue);
+                if (leaveVo != null) {
+                    XgDormBedDetailVo.LeaveInfo leaveInfo = new XgDormBedDetailVo.LeaveInfo();
+                    leaveInfo.setLeaveId(leaveVo.getId());
+                    leaveInfo.setLeaveTypeName(leaveVo.getLeaveTypeName());
+                    leaveInfo.setLeaveStartTime(leaveVo.getStartTime());
+                    leaveInfo.setLeaveEndTime(leaveVo.getEndTime());
+                    vo.setLeaveInfo(leaveInfo);
+                }
+            }
+
             result.add(vo);
         }
 
-        // 第二步:补查 xg_dorm_bed 中未被分配的空床位
         List<XgDormBed> emptyBeds = baseMapper.selectList(
             Wrappers.<XgDormBed>lambdaQuery()
                 .eq(XgDormBed::getRoomId, roomId)

+ 36 - 0
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java

@@ -33,11 +33,14 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.Collections;
+import java.time.LocalDateTime;
 
 import org.dromara.xg.domain.bo.XgDormBedBatchRecallBo;
 import org.dromara.xg.domain.bo.XgDormSelectiveAllocationBo;
 import org.dromara.xg.mapper.XgStudentMsgMapper;
 import org.dromara.xg.domain.XgStudentMsg;
+import org.dromara.xg.service.IXgStudentLeaveService;
+import org.dromara.xg.domain.vo.XgStudentLeaveVo;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.xg.domain.vo.DormBuildingTreeVo;
@@ -75,6 +78,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
     private final XgDormAreaMapper xgDormAreaMapper;
     private final XgSysDeptMapper xgSysDeptMapper;
     private final XgClassMsgMapper xgClassMsgMapper;
+    private final IXgStudentLeaveService xgStudentLeaveService;
 
     /**
      * 查询床位学生分配关系
@@ -2457,6 +2461,7 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
     /**
      * 根据房间ID获取住在当前房间的所有学生信息
      * 包含学生姓名、学号、性别、学院名称、专业名称、班级名称、床位编码、床位ID
+     * 同时查询当前时间有效的请假信息
      */
     @Override
     public List<Object> getRoomStudents(Long roomId) {
@@ -2466,6 +2471,23 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
             // 使用SQL关联查询,一次性获取所有信息
             List<Map<String, Object>> studentDetails = baseMapper.selectRoomStudents(roomId);
 
+            List<Long> studentIds = studentDetails.stream()
+                .map(detail -> {
+                    Object studentId = detail.get("student_id");
+                    if (studentId instanceof Number) {
+                        return ((Number) studentId).longValue();
+                    }
+                    return null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+            Map<Long, XgStudentLeaveVo> leaveMap = Map.of();
+            if (!studentIds.isEmpty()) {
+                leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate(
+                    studentIds, LocalDateTime.now());
+            }
+
             List<Object> result = new ArrayList<>();
 
             for (Map<String, Object> detailMap : studentDetails) {
@@ -2488,6 +2510,20 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
                 studentInfo.put("bedCode", detailMap.get("bed_code"));
                 studentInfo.put("bedId", detailMap.get("bed_id"));
 
+                Object studentIdObj = detailMap.get("student_id");
+                if (studentIdObj instanceof Number) {
+                    Long studentIdValue = ((Number) studentIdObj).longValue();
+                    XgStudentLeaveVo leaveInfo = leaveMap.get(studentIdValue);
+                    if (leaveInfo != null) {
+                        Map<String, Object> leaveInfoMap = new LinkedHashMap<>();
+                        leaveInfoMap.put("leaveId", leaveInfo.getId());
+                        leaveInfoMap.put("leaveTypeName", leaveInfo.getLeaveTypeName());
+                        leaveInfoMap.put("leaveStartTime", leaveInfo.getStartTime());
+                        leaveInfoMap.put("leaveEndTime", leaveInfo.getEndTime());
+                        studentInfo.put("leaveInfo", leaveInfoMap);
+                    }
+                }
+
                 result.add(studentInfo);
             }
 

+ 7 - 9
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentInfoController.java

@@ -22,9 +22,9 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.zsjy.domain.vo.ZsjyEnrollmentInfoVo;
 import org.dromara.zsjy.domain.bo.ZsjyEnrollmentInfoBo;
 import org.dromara.zsjy.service.IZsjyEnrollmentInfoService;
+import org.dromara.zsjy.service.IZsjyEnrollmentMajorService;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.xg.service.IXgSysDeptService;
-import org.dromara.xg.domain.vo.XgSysDeptVo;
 
 /**
  * 招生报名信息Controller
@@ -39,7 +39,7 @@ import org.dromara.xg.domain.vo.XgSysDeptVo;
 public class ZsjyEnrollmentInfoController extends BaseController {
 
     private final IZsjyEnrollmentInfoService enrollmentInfoService;
-    private final IXgSysDeptService xgSysDeptService;
+    private final IZsjyEnrollmentMajorService enrollmentMajorService;
 
     /**
      * 查询招生报名信息列表
@@ -164,10 +164,9 @@ public class ZsjyEnrollmentInfoController extends BaseController {
      */
     @SaIgnore
     @GetMapping("/colleges")
-    public R<List<XgSysDeptVo>> getColleges() {
+    public R<List<ZsjyEnrollmentDeptVo>> getColleges() {
         try {
-            // 调用学院部门服务获取学院列表
-            List<XgSysDeptVo> colleges = xgSysDeptService.selectCollegeList();
+            List<ZsjyEnrollmentDeptVo> colleges = enrollmentMajorService.selectOpenColleges();
             return R.ok(colleges);
         } catch (Exception e) {
             return R.fail("获取学院列表异常: " + e.getMessage());
@@ -179,10 +178,9 @@ public class ZsjyEnrollmentInfoController extends BaseController {
      */
     @SaIgnore
     @GetMapping("/majors/{collegeId}")
-    public R<List<XgSysDeptVo>> getMajorsByCollegeId(@PathVariable Long collegeId) {
+    public R<List<ZsjyEnrollmentDeptVo>> getMajorsByCollegeId(@PathVariable Long collegeId) {
         try {
-            // 调用学院部门服务获取专业列表
-            List<XgSysDeptVo> majors = xgSysDeptService.selectMajorListByCollegeId(collegeId);
+            List<ZsjyEnrollmentDeptVo> majors = enrollmentMajorService.selectOpenMajorsByCollegeId(collegeId);
             return R.ok(majors);
         } catch (Exception e) {
             return R.fail("获取专业列表异常: " + e.getMessage());

+ 54 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentMajorController.java

@@ -0,0 +1,54 @@
+package org.dromara.zsjy.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.zsjy.domain.bo.ZsjyEnrollmentMajorSaveBo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo;
+import org.dromara.zsjy.service.IZsjyEnrollmentMajorService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 招生报名开放专业配置
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/zsjy/enrollment/major")
+public class ZsjyEnrollmentMajorController extends BaseController {
+
+    private final IZsjyEnrollmentMajorService enrollmentMajorService;
+
+    @SaCheckPermission(value = {"zsjy:enrollmentMajor:list", "zsjy:enrollmentMajor:query"}, mode = SaMode.OR)
+    @GetMapping("/colleges")
+    public R<List<ZsjyEnrollmentDeptVo>> colleges() {
+        return R.ok(enrollmentMajorService.selectCollegeList());
+    }
+
+    @SaCheckPermission(value = {"zsjy:enrollmentMajor:list", "zsjy:enrollmentMajor:query"}, mode = SaMode.OR)
+    @GetMapping("/list")
+    public R<List<ZsjyEnrollmentDeptVo>> list(@RequestParam Long collegeDeptId) {
+        return R.ok(enrollmentMajorService.selectMajorConfigByCollegeId(collegeDeptId));
+    }
+
+    @SaCheckPermission("zsjy:enrollmentMajor:save")
+    @Log(title = "招生报名专业配置", businessType = BusinessType.UPDATE)
+    @RepeatSubmit
+    @PostMapping("/save")
+    public R<Void> save(@Validated @RequestBody ZsjyEnrollmentMajorSaveBo bo) {
+        return toAjax(enrollmentMajorService.saveCollegeConfig(bo));
+    }
+}

+ 11 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyEnrollmentProcessController.java

@@ -21,6 +21,7 @@ import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.zsjy.domain.vo.ZsjyEnrollmentProcessVo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentRegisterExportVo;
 import org.dromara.zsjy.domain.bo.ZsjyEnrollmentProcessBo;
 import org.dromara.zsjy.service.IZsjyEnrollmentProcessService;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -59,6 +60,16 @@ public class ZsjyEnrollmentProcessController extends BaseController {
         ExcelUtil.exportExcel(list, "招生录取流程状态", ZsjyEnrollmentProcessVo.class, response);
     }
 
+    /**
+     * 导出新生录取名册(与纸质录取名册字段一致)
+     */
+    @Log(title = "新生录取名册", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportRegister")
+    public void exportRegister(ZsjyEnrollmentProcessBo bo, HttpServletResponse response) {
+        List<ZsjyEnrollmentRegisterExportVo> list = enrollmentProcessService.queryRegisterExportList(bo);
+        ExcelUtil.exportExcel(list, "新生录取名册", ZsjyEnrollmentRegisterExportVo.class, response);
+    }
+
     /**
      * 获取招生录取流程状态详细信息
      */

+ 43 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/ZsjyEnrollmentMajor.java

@@ -0,0 +1,43 @@
+package org.dromara.zsjy.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;
+
+/**
+ * 招生报名开放专业配置 zsjy_enrollment_major
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("zsjy_enrollment_major")
+public class ZsjyEnrollmentMajor extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id")
+    private Long id;
+
+    private Long collegeDeptId;
+
+    private Long majorDeptId;
+
+    private String collegeName;
+
+    private String majorName;
+
+    private String majorCode;
+
+    private Integer sortOrder;
+
+    /** 0正常 1停用 */
+    private String status;
+
+    @TableLogic
+    private Integer deleted;
+}

+ 23 - 2
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyEnrollmentInfoBo.java

@@ -53,11 +53,13 @@ public class ZsjyEnrollmentInfoBo extends BaseEntity {
     /**
      * 是否住校(0-否 1-是)
      */
+    @NotNull(message = "是否住宿不能为空", groups = { AddGroup.class, EditGroup.class })
     private Integer isBoarding;
 
     /**
      * 出生年月
      */
+    @NotNull(message = "出生日期不能为空", groups = { AddGroup.class, EditGroup.class })
     private Date birthDate;
 
     /**
@@ -68,39 +70,46 @@ public class ZsjyEnrollmentInfoBo extends BaseEntity {
     private String phone1;
 
     /**
-     * 联系手机号码2(非必填,填写时需符合手机号格式)
+     * 联系手机号码2
      */
-    @Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "手机号码格式不正确", groups = { AddGroup.class, EditGroup.class })
+    @NotBlank(message = "备用电话不能为空", groups = { AddGroup.class, EditGroup.class })
+    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确", groups = { AddGroup.class, EditGroup.class })
     private String phone2;
 
     /**
      * 民族
      */
+    @NotBlank(message = "民族不能为空", groups = { AddGroup.class, EditGroup.class })
     private String nation;
 
     /**
      * 家庭地址
      */
+    @NotBlank(message = "家庭住址不能为空", groups = { AddGroup.class, EditGroup.class })
     private String homeAddress;
 
     /**
      * 初中毕业学校
      */
+    @NotBlank(message = "毕业中学不能为空", groups = { AddGroup.class, EditGroup.class })
     private String middleSchool;
 
     /**
      * 准考证号码
      */
+    @NotBlank(message = "准考证号不能为空", groups = { AddGroup.class, EditGroup.class })
     private String examNumber;
 
     /**
      * 参加中考地区
      */
+    @NotBlank(message = "中考地区不能为空", groups = { AddGroup.class, EditGroup.class })
     private String examRegion;
 
     /**
      * 当地中考满分
      */
+    @NotNull(message = "中考满分不能为空", groups = { AddGroup.class, EditGroup.class })
     @Min(value = 100, message = "中考满分不能小于100", groups = { AddGroup.class, EditGroup.class })
     @Max(value = 1000, message = "中考满分不能大于1000", groups = { AddGroup.class, EditGroup.class })
     private Integer totalScoreFull;
@@ -108,42 +117,49 @@ public class ZsjyEnrollmentInfoBo extends BaseEntity {
     /**
      * 考生中考成绩(含体育)
      */
+    @NotNull(message = "中考成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @Min(value = 0, message = "中考成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private Integer totalScore;
 
     /**
      * 语文成绩
      */
+    @NotNull(message = "语文成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "语文成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal chineseScore;
 
     /**
      * 数学成绩
      */
+    @NotNull(message = "数学成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "数学成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal mathScore;
 
     /**
      * 英语成绩
      */
+    @NotNull(message = "英语成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "英语成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal englishScore;
 
     /**
      * 科学成绩
      */
+    @NotNull(message = "科学成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "科学成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal scienceScore;
 
     /**
      * 社会成绩
      */
+    @NotNull(message = "社会成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "社会成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal socialScore;
 
     /**
      * 体育成绩
      */
+    @NotNull(message = "体育成绩不能为空", groups = { AddGroup.class, EditGroup.class })
     @DecimalMin(value = "0.0", message = "体育成绩不能小于0", groups = { AddGroup.class, EditGroup.class })
     private BigDecimal peScore;
 
@@ -156,16 +172,19 @@ public class ZsjyEnrollmentInfoBo extends BaseEntity {
     /**
      * 专业代码
      */
+    @NotBlank(message = "专业代码不能为空", groups = { AddGroup.class, EditGroup.class })
     private String majorCode;
 
     /**
      * 校服尺码
      */
+    @NotBlank(message = "校服尺码不能为空", groups = { AddGroup.class, EditGroup.class })
     private String uniformSize;
 
     /**
      * 健康情况
      */
+    @NotBlank(message = "健康状况不能为空", groups = { AddGroup.class, EditGroup.class })
     private String healthStatus;
 
     /**
@@ -196,11 +215,13 @@ public class ZsjyEnrollmentInfoBo extends BaseEntity {
     /**
      * 学院名称
      */
+    @NotBlank(message = "所属学院不能为空", groups = { AddGroup.class, EditGroup.class })
     private String collegeName;
 
     /**
      * 学院代码
      */
+    @NotBlank(message = "学院代码不能为空", groups = { AddGroup.class, EditGroup.class })
     private String collegeId;
 
     // 添加明确的getter和setter方法以解决编译问题

+ 19 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyEnrollmentMajorSaveBo.java

@@ -0,0 +1,19 @@
+package org.dromara.zsjy.domain.bo;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 按学院批量保存招生开放专业
+ */
+@Data
+public class ZsjyEnrollmentMajorSaveBo {
+
+    @NotNull(message = "学院ID不能为空")
+    private Long collegeDeptId;
+
+    /** 勾选的专业 dept_id 列表,可为空表示清空该学院开放专业 */
+    private List<Long> majorDeptIds;
+}

+ 59 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentDeptVo.java

@@ -0,0 +1,59 @@
+package org.dromara.zsjy.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 招生报名学院/专业选项
+ */
+@Data
+@Schema(description = "招生报名学院/专业选项")
+public class ZsjyEnrollmentDeptVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long deptId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long parentId;
+
+    private String deptName;
+
+    private String deptCategory;
+
+    private Integer orderNum;
+
+    /** 配置页:是否已勾选 */
+    private Boolean selected;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long majorDeptId;
+
+    private String majorName;
+
+    private String majorCode;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long collegeDeptId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long createBy;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long updateBy;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+}

+ 5 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentProcessVo.java

@@ -234,6 +234,11 @@ public class ZsjyEnrollmentProcessVo implements Serializable {
      */
     private String majorName;
 
+    /**
+     * 是否住校(0-否 1-是)- 从enrollment_info表获取
+     */
+    private Integer isBoarding;
+
     /**
      * 省份名称 - 从enrollment_info表获取
      */

+ 40 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/vo/ZsjyEnrollmentRegisterExportVo.java

@@ -0,0 +1,40 @@
+package org.dromara.zsjy.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 新生录取名册导出VO(与纸质录取名册字段一致)
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ZsjyEnrollmentRegisterExportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "序号")
+    private Integer serialNumber;
+
+    @ExcelProperty(value = "姓名")
+    private String studentName;
+
+    @ExcelProperty(value = "录取专业")
+    private String admittedMajor;
+
+    @ExcelProperty(value = "通知书号码")
+    private String noticeNumber;
+
+    @ExcelProperty(value = "是否住校")
+    private String boardingFlag;
+
+    @ExcelProperty(value = "预交学费")
+    private String prepaidTuitionFlag;
+
+    @ExcelProperty(value = "备注(时间、经办人)")
+    private String remarkInfo;
+}

+ 28 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/mapper/ZsjyEnrollmentMajorMapper.java

@@ -0,0 +1,28 @@
+package org.dromara.zsjy.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.zsjy.domain.ZsjyEnrollmentMajor;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo;
+
+import java.util.List;
+
+/**
+ * 招生报名开放专业配置 Mapper
+ */
+public interface ZsjyEnrollmentMajorMapper extends BaseMapperPlus<ZsjyEnrollmentMajor, ZsjyEnrollmentDeptVo> {
+
+    List<ZsjyEnrollmentDeptVo> selectCollegeList();
+
+    List<ZsjyEnrollmentDeptVo> selectMajorConfigByCollegeId(@Param("collegeDeptId") Long collegeDeptId);
+
+    List<ZsjyEnrollmentDeptVo> selectOpenColleges();
+
+    List<ZsjyEnrollmentDeptVo> selectOpenMajorsByCollegeId(@Param("collegeDeptId") Long collegeDeptId);
+
+    int countValidCollege(@Param("collegeDeptId") Long collegeDeptId);
+
+    int countValidMajorUnderCollege(@Param("collegeDeptId") Long collegeDeptId, @Param("majorDeptId") Long majorDeptId);
+
+    int physicalDeleteByCollegeDeptId(@Param("collegeDeptId") Long collegeDeptId);
+}

+ 22 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/IZsjyEnrollmentMajorService.java

@@ -0,0 +1,22 @@
+package org.dromara.zsjy.service;
+
+import org.dromara.zsjy.domain.bo.ZsjyEnrollmentMajorSaveBo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo;
+
+import java.util.List;
+
+/**
+ * 招生报名开放专业配置 Service
+ */
+public interface IZsjyEnrollmentMajorService {
+
+    List<ZsjyEnrollmentDeptVo> selectCollegeList();
+
+    List<ZsjyEnrollmentDeptVo> selectMajorConfigByCollegeId(Long collegeDeptId);
+
+    Boolean saveCollegeConfig(ZsjyEnrollmentMajorSaveBo bo);
+
+    List<ZsjyEnrollmentDeptVo> selectOpenColleges();
+
+    List<ZsjyEnrollmentDeptVo> selectOpenMajorsByCollegeId(Long collegeDeptId);
+}

+ 9 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/IZsjyEnrollmentProcessService.java

@@ -1,6 +1,7 @@
 package org.dromara.zsjy.service;
 
 import org.dromara.zsjy.domain.vo.ZsjyEnrollmentProcessVo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentRegisterExportVo;
 import org.dromara.zsjy.domain.bo.ZsjyEnrollmentProcessBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
@@ -139,4 +140,12 @@ public interface IZsjyEnrollmentProcessService {
      * @return 待我审核分页列表
      */
     TableDataInfo<ZsjyEnrollmentProcessVo> queryMyAuditList(PageQuery pageQuery);
+
+    /**
+     * 查询新生录取名册导出数据(与列表筛选条件一致)
+     *
+     * @param bo 查询条件
+     * @return 录取名册导出列表
+     */
+    List<ZsjyEnrollmentRegisterExportVo> queryRegisterExportList(ZsjyEnrollmentProcessBo bo);
 }

+ 120 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyEnrollmentMajorServiceImpl.java

@@ -0,0 +1,120 @@
+package org.dromara.zsjy.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.IdUtil;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.zsjy.domain.ZsjyEnrollmentMajor;
+import org.dromara.zsjy.domain.bo.ZsjyEnrollmentMajorSaveBo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo;
+import org.dromara.zsjy.mapper.ZsjyEnrollmentMajorMapper;
+import org.dromara.zsjy.service.IZsjyEnrollmentMajorService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 招生报名开放专业配置 Service 实现
+ */
+@RequiredArgsConstructor
+@Service
+public class ZsjyEnrollmentMajorServiceImpl implements IZsjyEnrollmentMajorService {
+
+    private final ZsjyEnrollmentMajorMapper enrollmentMajorMapper;
+
+    @Override
+    public List<ZsjyEnrollmentDeptVo> selectCollegeList() {
+        return enrollmentMajorMapper.selectCollegeList();
+    }
+
+    @Override
+    public List<ZsjyEnrollmentDeptVo> selectMajorConfigByCollegeId(Long collegeDeptId) {
+        if (collegeDeptId == null) {
+            throw new ServiceException("学院ID不能为空");
+        }
+        if (enrollmentMajorMapper.countValidCollege(collegeDeptId) == 0) {
+            throw new ServiceException("学院不存在或已停用");
+        }
+        return enrollmentMajorMapper.selectMajorConfigByCollegeId(collegeDeptId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean saveCollegeConfig(ZsjyEnrollmentMajorSaveBo bo) {
+        Long collegeDeptId = bo.getCollegeDeptId();
+        if (enrollmentMajorMapper.countValidCollege(collegeDeptId) == 0) {
+            throw new ServiceException("学院不存在或已停用");
+        }
+
+        List<Long> majorDeptIds = bo.getMajorDeptIds() == null ? List.of() : bo.getMajorDeptIds();
+        for (Long majorDeptId : majorDeptIds) {
+            if (enrollmentMajorMapper.countValidMajorUnderCollege(collegeDeptId, majorDeptId) == 0) {
+                throw new ServiceException("存在不属于该学院的专业,无法保存");
+            }
+        }
+
+        List<ZsjyEnrollmentDeptVo> allMajors = enrollmentMajorMapper.selectMajorConfigByCollegeId(collegeDeptId);
+        Map<Long, ZsjyEnrollmentDeptVo> majorMap = new HashMap<>();
+        for (ZsjyEnrollmentDeptVo major : allMajors) {
+            majorMap.put(major.getMajorDeptId(), major);
+        }
+
+        String collegeName = resolveCollegeName(collegeDeptId);
+
+        enrollmentMajorMapper.physicalDeleteByCollegeDeptId(collegeDeptId);
+
+        if (CollUtil.isEmpty(majorDeptIds)) {
+            return true;
+        }
+
+        List<ZsjyEnrollmentMajor> insertList = new ArrayList<>();
+        int sort = 0;
+        for (Long majorDeptId : majorDeptIds) {
+            ZsjyEnrollmentDeptVo major = majorMap.get(majorDeptId);
+            if (major == null) {
+                continue;
+            }
+            ZsjyEnrollmentMajor entity = new ZsjyEnrollmentMajor();
+            entity.setId(IdUtil.getSnowflakeNextId());
+            entity.setCollegeDeptId(collegeDeptId);
+            entity.setMajorDeptId(majorDeptId);
+            entity.setCollegeName(collegeName);
+            entity.setMajorName(major.getMajorName());
+            entity.setMajorCode(major.getMajorCode());
+            entity.setSortOrder(++sort);
+            entity.setStatus("0");
+            entity.setDeleted(0);
+            insertList.add(entity);
+        }
+
+        if (CollUtil.isNotEmpty(insertList)) {
+            enrollmentMajorMapper.insertBatch(insertList);
+        }
+        return true;
+    }
+
+    @Override
+    public List<ZsjyEnrollmentDeptVo> selectOpenColleges() {
+        return enrollmentMajorMapper.selectOpenColleges();
+    }
+
+    @Override
+    public List<ZsjyEnrollmentDeptVo> selectOpenMajorsByCollegeId(Long collegeDeptId) {
+        if (collegeDeptId == null) {
+            throw new ServiceException("学院ID不能为空");
+        }
+        return enrollmentMajorMapper.selectOpenMajorsByCollegeId(collegeDeptId);
+    }
+
+    private String resolveCollegeName(Long collegeDeptId) {
+        return enrollmentMajorMapper.selectCollegeList().stream()
+            .filter(item -> collegeDeptId.equals(item.getDeptId()))
+            .map(ZsjyEnrollmentDeptVo::getDeptName)
+            .findFirst()
+            .orElse("");
+    }
+}

+ 55 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyEnrollmentProcessServiceImpl.java

@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.dromara.zsjy.domain.bo.ZsjyEnrollmentProcessBo;
 import org.dromara.zsjy.domain.vo.ZsjyEnrollmentProcessVo;
+import org.dromara.zsjy.domain.vo.ZsjyEnrollmentRegisterExportVo;
 import org.dromara.zsjy.domain.ZsjyEnrollmentProcess;
 import org.dromara.zsjy.mapper.ZsjyEnrollmentProcessMapper;
 import org.dromara.zsjy.service.IZsjyEnrollmentProcessService;
@@ -22,6 +23,8 @@ import java.util.List;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Set;
+import java.util.ArrayList;
+import java.text.SimpleDateFormat;
 
 /**
  * 招生录取流程状态Service业务层处理
@@ -430,4 +433,56 @@ public class ZsjyEnrollmentProcessServiceImpl implements IZsjyEnrollmentProcessS
         Page<ZsjyEnrollmentProcessVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
         return TableDataInfo.build(result);
     }
+
+    @Override
+    public List<ZsjyEnrollmentRegisterExportVo> queryRegisterExportList(ZsjyEnrollmentProcessBo bo) {
+        List<ZsjyEnrollmentProcessVo> sourceList = baseMapper.selectAllProcessWithInfoAudit(bo);
+        List<ZsjyEnrollmentRegisterExportVo> exportList = new ArrayList<>();
+        if (sourceList == null || sourceList.isEmpty()) {
+            return exportList;
+        }
+        int index = 1;
+        for (ZsjyEnrollmentProcessVo item : sourceList) {
+            ZsjyEnrollmentRegisterExportVo exportVo = new ZsjyEnrollmentRegisterExportVo();
+            exportVo.setSerialNumber(index++);
+            exportVo.setStudentName(StringUtils.blankToDefault(item.getStudentName(), ""));
+            exportVo.setAdmittedMajor(StringUtils.blankToDefault(item.getMajorName(), ""));
+            exportVo.setNoticeNumber(StringUtils.blankToDefault(item.getNoticeNumber(), ""));
+            exportVo.setBoardingFlag(formatBoardingFlag(item.getIsBoarding()));
+            exportVo.setPrepaidTuitionFlag(formatPrepaidTuitionFlag(item.getPaymentStatus()));
+            exportVo.setRemarkInfo(buildRegisterRemark(item));
+            exportList.add(exportVo);
+        }
+        return exportList;
+    }
+
+    private String formatBoardingFlag(Integer isBoarding) {
+        return isBoarding != null && isBoarding == 1 ? "√" : "";
+    }
+
+    private String formatPrepaidTuitionFlag(String paymentStatus) {
+        return "1".equals(paymentStatus) ? "√" : "×";
+    }
+
+    private String buildRegisterRemark(ZsjyEnrollmentProcessVo item) {
+        if (StringUtils.isNotBlank(item.getRemark())) {
+            return item.getRemark();
+        }
+        StringBuilder remark = new StringBuilder();
+        Date remarkTime = item.getPaymentConfirmTime() != null ? item.getPaymentConfirmTime()
+            : (item.getAdmissionTime() != null ? item.getAdmissionTime() : item.getFinalAuditTime());
+        if (remarkTime != null) {
+            remark.append(new SimpleDateFormat("M.d").format(remarkTime));
+        }
+        String handler = StringUtils.blankToDefault(item.getAdmissionUserName(),
+            StringUtils.blankToDefault(item.getFinalAuditUserName(),
+                StringUtils.blankToDefault(item.getInfoAuditUserName(), item.getCurrentHandlerName())));
+        if (StringUtils.isNotBlank(handler)) {
+            if (!remark.isEmpty()) {
+                remark.append(" ");
+            }
+            remark.append(handler);
+        }
+        return remark.toString();
+    }
 }

+ 88 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/zsjy/ZsjyEnrollmentMajorMapper.xml

@@ -0,0 +1,88 @@
+<?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.zsjy.mapper.ZsjyEnrollmentMajorMapper">
+
+    <select id="selectCollegeList" resultType="org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo">
+        SELECT d.dept_id   AS deptId,
+               d.dept_name AS deptName,
+               d.dept_category AS deptCategory,
+               d.order_num AS orderNum
+        FROM sys_dept d
+        WHERE d.del_flag = '0'
+          AND d.status = '0'
+          AND d.parent_id = 101
+        ORDER BY d.order_num
+    </select>
+
+    <select id="selectMajorConfigByCollegeId" resultType="org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo">
+        SELECT m.dept_id       AS majorDeptId,
+               m.dept_name     AS majorName,
+               m.dept_category AS majorCode,
+               m.parent_id     AS collegeDeptId,
+               CASE WHEN em.id IS NOT NULL THEN 1 ELSE 0 END AS selected
+        FROM sys_dept m
+        LEFT JOIN zsjy_enrollment_major em
+            ON em.major_dept_id = m.dept_id AND em.deleted = 0 AND em.status = '0'
+        WHERE m.parent_id = #{collegeDeptId}
+          AND m.del_flag = '0'
+          AND m.status = '0'
+          AND m.dept_category LIKE 'zy-%'
+        ORDER BY m.order_num
+    </select>
+
+    <select id="selectOpenColleges" resultType="org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo">
+        SELECT DISTINCT c.dept_id       AS deptId,
+                        c.dept_name     AS deptName,
+                        c.dept_category AS deptCategory,
+                        c.order_num     AS orderNum
+        FROM sys_dept c
+        INNER JOIN sys_dept m ON m.parent_id = c.dept_id
+        INNER JOIN zsjy_enrollment_major em ON em.major_dept_id = m.dept_id
+        WHERE c.parent_id = 101
+          AND c.del_flag = '0'
+          AND c.status = '0'
+          AND m.del_flag = '0'
+          AND m.status = '0'
+          AND em.deleted = 0
+          AND em.status = '0'
+        ORDER BY c.order_num
+    </select>
+
+    <select id="selectOpenMajorsByCollegeId" resultType="org.dromara.zsjy.domain.vo.ZsjyEnrollmentDeptVo">
+        SELECT m.dept_id       AS deptId,
+               m.dept_name     AS deptName,
+               m.dept_category AS deptCategory,
+               em.sort_order   AS orderNum
+        FROM sys_dept m
+        INNER JOIN zsjy_enrollment_major em ON em.major_dept_id = m.dept_id
+        WHERE m.parent_id = #{collegeDeptId}
+          AND m.del_flag = '0'
+          AND m.status = '0'
+          AND em.deleted = 0
+          AND em.status = '0'
+        ORDER BY em.sort_order, m.order_num
+    </select>
+
+    <select id="countValidCollege" resultType="int">
+        SELECT COUNT(1)
+        FROM sys_dept d
+        WHERE d.dept_id = #{collegeDeptId}
+          AND d.parent_id = 101
+          AND d.del_flag = '0'
+          AND d.status = '0'
+    </select>
+
+    <select id="countValidMajorUnderCollege" resultType="int">
+        SELECT COUNT(1)
+        FROM sys_dept m
+        WHERE m.dept_id = #{majorDeptId}
+          AND m.parent_id = #{collegeDeptId}
+          AND m.del_flag = '0'
+          AND m.status = '0'
+          AND m.dept_category LIKE 'zy-%'
+    </select>
+
+    <delete id="physicalDeleteByCollegeDeptId">
+        DELETE FROM zsjy_enrollment_major WHERE college_dept_id = #{collegeDeptId}
+    </delete>
+</mapper>

+ 2 - 1
backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/zsjy/ZsjyEnrollmentProcessMapper.xml

@@ -78,6 +78,7 @@
         <!-- 额外字段,用于合并enrollment_info表的数据 -->
         <result property="gender" column="gender"/>
         <result property="majorName" column="majorName"/>
+        <result property="isBoarding" column="isBoarding"/>
         <result property="province" column="province"/>
     </resultMap>
 
@@ -143,7 +144,7 @@
             COALESCE(p.create_time, ei.create_time) as create_time,
             COALESCE(p.update_by, ei.update_by) as update_by,
             COALESCE(p.update_time, ei.update_time) as update_time,
-            ei.gender, ei.applied_major as majorName, ei.exam_region as province
+            ei.gender, ei.applied_major as majorName, ei.is_boarding as isBoarding, ei.exam_region as province
         FROM zsjy_enrollment_info ei
         LEFT JOIN zsjy_enrollment_process p ON ei.id = p.enrollment_id AND p.deleted = '0'
         WHERE ei.deleted = '0'

+ 3 - 3
mobile/src/pages.json

@@ -208,7 +208,7 @@
 			}
 		},
 		{
-			"path": "pages/xgss/dmdl/dmdl",
+			"path": "pages/xgss/ssdm/ssdm",
 			"style": {
 				"navigationBarTitleText": "宿舍点名",
 				"navigationBarBackgroundColor": "#0051d9",
@@ -216,14 +216,14 @@
 			}
 		},
 		{
-			"path": "pages/xgss/dmdl/dmdlCheck",
+			"path": "pages/xgss/ssdm/ssdmCheck",
 			"style": {
 				"navigationBarTitleText": "宿舍点名",
 				"navigationStyle": "custom"
 			}
 		},
 		{
-			"path": "pages/xgss/dmdl/dmdlDetail",
+			"path": "pages/xgss/ssdm/ssdmDetail",
 			"style": {
 				"navigationBarTitleText": "点名详情",
 				"navigationBarBackgroundColor": "#0051d9",

+ 73 - 63
mobile/src/pages/enrollment/index.vue

@@ -57,7 +57,7 @@
                         ></u-picker>
                     </u-form-item>
 
-                    <u-form-item label="出生日期" prop="birthDate">
+                    <u-form-item label="出生日期" prop="birthDate" required>
                         <u-input
                             v-model="formData.birthDate"
                             placeholder="选择出生日期"
@@ -71,15 +71,15 @@
                         <u-input v-model="formData.phone1" placeholder="请输入联系电话" />
                     </u-form-item>
 
-                    <u-form-item label="备用电话" prop="phone2">
+                    <u-form-item label="备用电话" prop="phone2" required>
                         <u-input v-model="formData.phone2" placeholder="请输入备用电话" />
                     </u-form-item>
 
-                    <u-form-item label="民族" prop="nation">
+                    <u-form-item label="民族" prop="nation" required>
                         <u-input v-model="formData.nation" placeholder="请输入民族" />
                     </u-form-item>
 
-                    <u-form-item label="是否住宿" prop="isBoarding">
+                    <u-form-item label="是否住宿" prop="isBoarding" required>
                         <view
                             @click="showBoardingPicker"
                             @tap="showBoardingPicker"
@@ -102,11 +102,11 @@
                         ></u-picker>
                     </u-form-item>
 
-                    <u-form-item label="健康状况" prop="healthStatus">
+                    <u-form-item label="健康状况" prop="healthStatus" required>
                         <u-input v-model="formData.healthStatus" placeholder="请输入健康状况" />
                     </u-form-item>
 
-                    <u-form-item label="家庭住址" prop="homeAddress">
+                    <u-form-item label="家庭住址" prop="homeAddress" required>
                         <u-input
                             v-model="formData.homeAddress"
                             type="textarea"
@@ -123,19 +123,19 @@
                         <text class="title-text">学业信息</text>
                     </view>
 
-                    <u-form-item label="毕业中学" prop="middleSchool">
+                    <u-form-item label="毕业中学" prop="middleSchool" required>
                         <u-input v-model="formData.middleSchool" placeholder="请输入毕业中学全称" />
                     </u-form-item>
 
-                    <u-form-item label="准考证号" prop="examNumber">
+                    <u-form-item label="准考证号" prop="examNumber" required>
                         <u-input v-model="formData.examNumber" placeholder="请输入准考证号" />
                     </u-form-item>
 
-                    <u-form-item label="中考地区" prop="examRegion">
+                    <u-form-item label="中考地区" prop="examRegion" required>
                         <u-input v-model="formData.examRegion" placeholder="请输入中考地区" />
                     </u-form-item>
 
-                    <u-form-item label="中考满分" prop="totalScoreFull">
+                    <u-form-item label="中考满分" prop="totalScoreFull" required>
                         <u-number-box
                             v-model="formData.totalScoreFull"
                             :min="0"
@@ -144,7 +144,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="中考成绩" prop="totalScore">
+                    <u-form-item label="中考成绩" prop="totalScore" required>
                         <u-number-box
                             v-model="formData.totalScore"
                             :min="0"
@@ -153,7 +153,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="语文成绩" prop="chineseScore">
+                    <u-form-item label="语文成绩" prop="chineseScore" required>
                         <u-number-box
                             v-model="formData.chineseScore"
                             :min="0"
@@ -162,7 +162,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="数学成绩" prop="mathScore">
+                    <u-form-item label="数学成绩" prop="mathScore" required>
                         <u-number-box
                             v-model="formData.mathScore"
                             :min="0"
@@ -171,7 +171,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="英语成绩" prop="englishScore">
+                    <u-form-item label="英语成绩" prop="englishScore" required>
                         <u-number-box
                             v-model="formData.englishScore"
                             :min="0"
@@ -180,7 +180,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="科学成绩" prop="scienceScore">
+                    <u-form-item label="科学成绩" prop="scienceScore" required>
                         <u-number-box
                             v-model="formData.scienceScore"
                             :min="0"
@@ -189,7 +189,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="社会成绩" prop="socialScore">
+                    <u-form-item label="社会成绩" prop="socialScore" required>
                         <u-number-box
                             v-model="formData.socialScore"
                             :min="0"
@@ -198,7 +198,7 @@
                         ></u-number-box>
                     </u-form-item>
 
-                    <u-form-item label="体育成绩" prop="peScore">
+                    <u-form-item label="体育成绩" prop="peScore" required>
                         <u-number-box
                             v-model="formData.peScore"
                             :min="0"
@@ -238,7 +238,7 @@
                         ></u-picker>
                     </u-form-item>
 
-                    <u-form-item label="学院代码" prop="collegeId">
+                    <u-form-item label="学院代码" prop="collegeId" required>
                         <u-input
                             v-model="formData.collegeId"
                             placeholder="学院代码将自动填入"
@@ -265,7 +265,7 @@
                         ></u-picker>
                     </u-form-item>
 
-                    <u-form-item label="专业代码" prop="majorCode">
+                    <u-form-item label="专业代码" prop="majorCode" required>
                         <u-input
                             v-model="formData.majorCode"
                             placeholder="专业代码将自动填入"
@@ -273,7 +273,7 @@
                         />
                     </u-form-item>
 
-                    <u-form-item label="校服尺码" prop="uniformSize">
+                    <u-form-item label="校服尺码" prop="uniformSize" required>
                         <view
                             @click="showUniformSizePicker"
                             @tap="showUniformSizePicker"
@@ -444,6 +444,28 @@ import { getDict } from '@/common/dict.js';
 import { request } from '@/common/request.js';
 import { getCollegeList, getMajorsByCollegeId } from '@/api/enrollment.js';
 
+const idCardPattern = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
+const phonePattern = /^1[3-9]\d{9}$/;
+
+const requiredText = (label) => [{ required: true, message: `${label}不能为空`, trigger: ['blur', 'change'] }];
+
+const requiredNumber = (label, min = 0, max) => [
+    {
+        validator: (rule, value, callback) => {
+            if (value === null || value === undefined || value === '') {
+                callback(new Error(`${label}不能为空`));
+            } else if (Number(value) < min) {
+                callback(new Error(`${label}不能小于${min}`));
+            } else if (max !== undefined && Number(value) > max) {
+                callback(new Error(`${label}不能大于${max}`));
+            } else {
+                callback();
+            }
+        },
+        trigger: ['blur', 'change'],
+    },
+];
+
 export default {
     name: 'EnrollmentIndex',
     data() {
@@ -535,28 +557,43 @@ export default {
             majorPickerColumns: [],
             uniformSizePickerColumns: [],
 
-            // 表单验证规则
+            // 表单验证规则(特长爱好、获奖情况除外)
             rules: {
-                name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
+                name: requiredText('姓名'),
                 idCard: [
                     { required: true, message: '身份证号不能为空', trigger: 'blur' },
-                    {
-                        pattern:
-                            /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
-                        message: '身份证号格式不正确',
-                        trigger: 'blur',
-                    },
+                    { pattern: idCardPattern, message: '身份证号格式不正确', trigger: 'blur' },
                 ],
-                gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
+                gender: requiredText('性别'),
+                birthDate: requiredText('出生日期'),
                 phone1: [
                     { required: true, message: '联系电话不能为空', trigger: 'blur' },
-                    { pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' },
+                    { pattern: phonePattern, message: '手机号码格式不正确', trigger: 'blur' },
                 ],
                 phone2: [
-                    { pattern: /^$|^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' },
+                    { required: true, message: '备用电话不能为空', trigger: 'blur' },
+                    { pattern: phonePattern, message: '手机号码格式不正确', trigger: 'blur' },
                 ],
-                collegeName: [{ required: true, message: '所属学院不能为空', trigger: 'change' }],
-                appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
+                nation: requiredText('民族'),
+                isBoarding: requiredText('是否住宿'),
+                healthStatus: requiredText('健康状况'),
+                homeAddress: requiredText('家庭住址'),
+                middleSchool: requiredText('毕业中学'),
+                examNumber: requiredText('准考证号'),
+                examRegion: requiredText('中考地区'),
+                totalScoreFull: requiredNumber('中考满分', 100, 1000),
+                totalScore: requiredNumber('中考成绩', 0),
+                chineseScore: requiredNumber('语文成绩', 0),
+                mathScore: requiredNumber('数学成绩', 0),
+                englishScore: requiredNumber('英语成绩', 0),
+                scienceScore: requiredNumber('科学成绩', 0),
+                socialScore: requiredNumber('社会成绩', 0),
+                peScore: requiredNumber('体育成绩', 0),
+                collegeName: requiredText('所属学院'),
+                collegeId: requiredText('学院代码'),
+                appliedMajor: requiredText('报考专业'),
+                majorCode: requiredText('专业代码'),
+                uniformSize: requiredText('校服尺码'),
             },
         };
     },
@@ -565,21 +602,10 @@ export default {
         try {
             console.log('🚀 开始页面初始化...');
 
-            // 先初始化兜底数据,确保页面能正常显示
-            this.initDefaultDictData();
-            this.initDefaultCollegeData();
-
-            console.log('✅ 兜底数据初始化完成:', {
-                genderOptions: this.genderOptions.length,
-                collegeOptions: this.collegeOptions.length,
-                majorOptions: this.majorOptions.length,
-            });
-
-            // 并行加载数据,即使部分失败也不影响页面显示
+            // 并行加载数据
             const results = await Promise.allSettled([
                 this.initDictData(),
                 this.loadCollegeOptions(),
-                this.initMajorOptions(),
             ]);
 
             console.log('📋 异步数据加载结果:', results);
@@ -606,9 +632,7 @@ export default {
             console.log('✅ 页面初始化完成,准备就绪');
         } catch (error) {
             console.error('❌ 页面初始化失败:', error);
-            // 确保使用兜底数据
             this.initDefaultDictData();
-            this.initDefaultCollegeData();
             this.$forceUpdate();
         }
     },
@@ -798,15 +822,8 @@ export default {
                 }
             } catch (error) {
                 console.error('学院数据加载失败:', error);
-                // 设置兜底学院数据
-                this.collegeOptions = [
-                    { deptId: 1, deptName: '机械工程学院', deptCategory: 'JX' },
-                    { deptId: 2, deptName: '电气工程学院', deptCategory: 'DQ' },
-                    { deptId: 3, deptName: '信息工程学院', deptCategory: 'XX' },
-                    { deptId: 4, deptName: '经济管理学院', deptCategory: 'JG' },
-                    { deptId: 5, deptName: '建筑工程学院', deptCategory: 'JZ' },
-                ];
-                console.log('✅ 已使用默认学院数据(兜底方案)');
+                this.collegeOptions = [];
+                uni.showToast({ title: '学院数据加载失败', icon: 'none' });
             }
 
             // 更新学院picker的columns数据
@@ -1174,13 +1191,6 @@ export default {
                 this.majorOptions = [];
             }
 
-            // 如果API加载失败或无数据,使用兜底专业数据
-            if (!this.majorOptions || this.majorOptions.length === 0) {
-                const defaultMajors = this.getDefaultMajorsByCollegeId(collegeId);
-                this.majorOptions = defaultMajors;
-                console.log('✅ 已使用默认专业数据(兜底方案):', defaultMajors);
-            }
-
             // 更新专业picker的columns数据
             this.majorPickerColumns = [this.majorOptions.map((item) => item.label)];
             console.log('✅ 专业picker columns更新完成:', {

+ 1 - 1
mobile/src/pages/index/index.vue

@@ -1220,7 +1220,7 @@ export default {
         // 跳转到宿舍点名页面
         gotoDormRollCall() {
             uni.navigateTo({
-                url: '/pages/xgss/dmdl/dmdl',
+                url: '/pages/xgss/ssdm/ssdm',
             });
         },
 

+ 30 - 53
web/src/views/zsjy/enrollment/apply.vue

@@ -39,7 +39,7 @@
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="出生日期" prop="birthDate">
+              <el-form-item label="出生日期" prop="birthDate" required>
                 <el-date-picker v-model="applyForm.birthDate" type="date" placeholder="选择出生日期" value-format="YYYY-MM-DD" style="width: 100%" />
               </el-form-item>
             </el-col>
@@ -49,33 +49,33 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="备用电话" prop="phone2">
+              <el-form-item label="备用电话" prop="phone2" required>
                 <el-input v-model="applyForm.phone2" placeholder="请输入备用电话" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="民族" prop="nation">
+              <el-form-item label="民族" prop="nation" required>
                 <el-input v-model="applyForm.nation" placeholder="请输入民族" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="是否住宿" prop="isBoarding">
+              <el-form-item label="是否住宿" prop="isBoarding" required>
                 <el-select v-model="applyForm.isBoarding" placeholder="请选择是否住宿" style="width: 100%">
                   <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="健康状况" prop="healthStatus">
+              <el-form-item label="健康状况" prop="healthStatus" required>
                 <el-input v-model="applyForm.healthStatus" placeholder="请输入健康状况" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-form-item label="家庭住址" prop="homeAddress">
+              <el-form-item label="家庭住址" prop="homeAddress" required>
                 <el-input v-model="applyForm.homeAddress" placeholder="请输入详细的家庭住址" />
               </el-form-item>
             </el-col>
@@ -90,63 +90,63 @@
           </h3>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="毕业中学" prop="middleSchool">
+              <el-form-item label="毕业中学" prop="middleSchool" required>
                 <el-input v-model="applyForm.middleSchool" placeholder="请输入毕业中学全称" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="准考证号" prop="examNumber">
+              <el-form-item label="准考证号" prop="examNumber" required>
                 <el-input v-model="applyForm.examNumber" placeholder="请输入准考证号" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="中考地区" prop="examRegion">
+              <el-form-item label="中考地区" prop="examRegion" required>
                 <el-input v-model="applyForm.examRegion" placeholder="请输入中考地区" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="中考满分" prop="totalScoreFull">
+              <el-form-item label="中考满分" prop="totalScoreFull" required>
                 <el-input-number v-model="applyForm.totalScoreFull" placeholder="请输入中考满分" :min="100" :max="1000" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="中考成绩" prop="totalScore">
+              <el-form-item label="中考成绩" prop="totalScore" required>
                 <el-input-number v-model="applyForm.totalScore" placeholder="请输入中考成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="语文成绩" prop="chineseScore">
+              <el-form-item label="语文成绩" prop="chineseScore" required>
                 <el-input-number v-model="applyForm.chineseScore" placeholder="请输入语文成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="数学成绩" prop="mathScore">
+              <el-form-item label="数学成绩" prop="mathScore" required>
                 <el-input-number v-model="applyForm.mathScore" placeholder="请输入数学成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="英语成绩" prop="englishScore">
+              <el-form-item label="英语成绩" prop="englishScore" required>
                 <el-input-number v-model="applyForm.englishScore" placeholder="请输入英语成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="科学成绩" prop="scienceScore">
+              <el-form-item label="科学成绩" prop="scienceScore" required>
                 <el-input-number v-model="applyForm.scienceScore" placeholder="请输入科学成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="社会成绩" prop="socialScore">
+              <el-form-item label="社会成绩" prop="socialScore" required>
                 <el-input-number v-model="applyForm.socialScore" placeholder="请输入社会成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="体育成绩" prop="peScore">
+              <el-form-item label="体育成绩" prop="peScore" required>
                 <el-input-number v-model="applyForm.peScore" placeholder="请输入体育成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
@@ -168,7 +168,7 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="学院代码" prop="collegeId">
+              <el-form-item label="学院代码" prop="collegeId" required>
                 <el-input v-model="applyForm.collegeId" placeholder="学院代码将自动填入" readonly />
               </el-form-item>
             </el-col>
@@ -182,16 +182,16 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="专业代码" prop="majorCode">
+              <el-form-item label="专业代码" prop="majorCode" required>
                 <el-input v-model="applyForm.majorCode" placeholder="专业代码将自动填入" readonly />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="校服尺码" prop="uniformSize">
+              <el-form-item label="校服尺码" prop="uniformSize" required>
                 <el-select v-model="applyForm.uniformSize" placeholder="请选择校服尺码" style="width: 100%">
-                  <el-option v-for="dict in zsjy_uniform_size" :key="dict.value" :label="dict.label" :value="dict.value" />
+                  <el-option v-for="dict in uniformSizeOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -282,11 +282,13 @@
 </template>
 
 <script setup name="EnrollmentApply" lang="ts">
-import { reactive, ref, toRefs, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick } from 'vue';
+import { reactive, ref, toRefs, getCurrentInstance, ComponentInternalInstance, onMounted, nextTick, computed } from 'vue';
 import { ElForm } from 'element-plus';
 import { submitEnrollmentApplication, queryMyEnrollmentStatus } from '@/api/zsjy/enrollment';
 import type { EnrollmentInfoForm } from '@/api/zsjy/enrollment/types';
-import { listColleges, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classMsg/college';
+import { listOpenColleges, listOpenMajorsByCollegeId } from '@/api/zsjy/enrollmentMajor';
+import { enrollmentFormRules } from './enrollmentFormRules';
+import { resolveUniformSizeOptions } from './uniformSizeOptions';
 import { User, School, Star, Document, Check, Refresh } from '@element-plus/icons-vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -294,6 +296,8 @@ const { zsjy_gender, sys_yes_no, zsjy_major, zsjy_uniform_size } = toRefs<any>(
   (proxy as any)?.useDict('zsjy_gender', 'sys_yes_no', 'zsjy_major', 'zsjy_uniform_size')
 );
 
+const uniformSizeOptions = computed(() => resolveUniformSizeOptions(zsjy_uniform_size.value));
+
 const applyFormRef = ref<InstanceType<typeof ElForm>>();
 const submitLoading = ref(false);
 const queryLoading = ref(false);
@@ -343,33 +347,7 @@ const queryForm = reactive({
   examNumber: ''
 });
 
-const rules = {
-  name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
-  idCard: [
-    { required: true, message: '身份证号不能为空', trigger: 'blur' },
-    {
-      pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
-      message: '身份证号格式不正确',
-      trigger: 'blur'
-    }
-  ],
-  gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
-  phone1: [
-    { required: true, message: '联系电话不能为空', trigger: 'blur' },
-    { pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
-  ],
-  phone2: [{ pattern: /^$|^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }],
-  collegeName: [{ required: true, message: '所属学院不能为空', trigger: 'change' }],
-  appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
-  totalScoreFull: [{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }],
-  totalScore: [{ type: 'number', min: 0, message: '中考成绩不能小于0', trigger: 'blur' }],
-  chineseScore: [{ type: 'number', min: 0, message: '语文成绩不能小于0', trigger: 'blur' }],
-  mathScore: [{ type: 'number', min: 0, message: '数学成绩不能小于0', trigger: 'blur' }],
-  englishScore: [{ type: 'number', min: 0, message: '英语成绩不能小于0', trigger: 'blur' }],
-  scienceScore: [{ type: 'number', min: 0, message: '科学成绩不能小于0', trigger: 'blur' }],
-  socialScore: [{ type: 'number', min: 0, message: '社会成绩不能小于0', trigger: 'blur' }],
-  peScore: [{ type: 'number', min: 0, message: '体育成绩不能小于0', trigger: 'blur' }]
-};
+const rules = enrollmentFormRules;
 
 /** 身份证号变化处理 */
 const onIdCardChange = () => {
@@ -438,7 +416,7 @@ const onCollegeChange = async (selectedCollegeId: number) => {
 /** 根据学院ID加载专业 */
 const loadMajorsByCollegeId = async (collegeId: number) => {
   try {
-    const response = await listMajorsByCollegeId(collegeId);
+    const response = await listOpenMajorsByCollegeId(collegeId);
     if (response.data) {
       majorOptions.value = response.data.map((major: any) => ({
         value: major.deptId.toString(),
@@ -566,7 +544,7 @@ const queryStatus = async () => {
 /** 获取学院选项 */
 const getCollegeOptions = async () => {
   try {
-    const response = await listColleges();
+    const response = await listOpenColleges();
     if (response.data) {
       collegeOptions.value = response.data;
       console.log('学院数据加载成功:', collegeOptions.value);
@@ -579,7 +557,6 @@ const getCollegeOptions = async () => {
 
 onMounted(() => {
   getCollegeOptions();
-  getMajorOptions();
 });
 </script>
 

+ 58 - 0
web/src/views/zsjy/enrollment/enrollmentFormRules.ts

@@ -0,0 +1,58 @@
+import type { FormRules } from 'element-plus';
+
+const idCardPattern = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
+const phonePattern = /^1[3-9]\d{9}$/;
+
+const requiredText = (label: string, trigger: 'blur' | 'change' = 'blur') => [
+  { required: true, message: `${label}不能为空`, trigger }
+];
+
+const requiredNumber = (label: string, min: number, max?: number) => {
+  const rules: FormRules[string] = [
+    { required: true, message: `${label}不能为空`, trigger: 'blur' },
+    { type: 'number', min, message: `${label}不能小于${min}`, trigger: 'blur' }
+  ];
+  if (max !== undefined) {
+    rules.push({ type: 'number', max, message: `${label}不能大于${max}`, trigger: 'blur' });
+  }
+  return rules;
+};
+
+/** 招生报名信息表单校验(特长爱好、获奖情况除外) */
+export const enrollmentFormRules: FormRules = {
+  name: requiredText('姓名'),
+  idCard: [
+    { required: true, message: '身份证号不能为空', trigger: 'blur' },
+    { pattern: idCardPattern, message: '身份证号格式不正确', trigger: 'blur' }
+  ],
+  gender: requiredText('性别', 'change'),
+  birthDate: requiredText('出生日期', 'change'),
+  phone1: [
+    { required: true, message: '联系电话不能为空', trigger: 'blur' },
+    { pattern: phonePattern, message: '手机号码格式不正确', trigger: 'blur' }
+  ],
+  phone2: [
+    { required: true, message: '备用电话不能为空', trigger: 'blur' },
+    { pattern: phonePattern, message: '手机号码格式不正确', trigger: 'blur' }
+  ],
+  nation: requiredText('民族'),
+  isBoarding: requiredText('是否住宿', 'change'),
+  healthStatus: requiredText('健康状况'),
+  homeAddress: requiredText('家庭住址'),
+  middleSchool: requiredText('毕业中学'),
+  examNumber: requiredText('准考证号'),
+  examRegion: requiredText('中考地区'),
+  totalScoreFull: requiredNumber('中考满分', 100, 1000),
+  totalScore: requiredNumber('中考成绩', 0),
+  chineseScore: requiredNumber('语文成绩', 0),
+  mathScore: requiredNumber('数学成绩', 0),
+  englishScore: requiredNumber('英语成绩', 0),
+  scienceScore: requiredNumber('科学成绩', 0),
+  socialScore: requiredNumber('社会成绩', 0),
+  peScore: requiredNumber('体育成绩', 0),
+  collegeName: requiredText('所属学院', 'change'),
+  collegeId: requiredText('学院代码'),
+  appliedMajor: requiredText('报考专业', 'change'),
+  majorCode: requiredText('专业代码'),
+  uniformSize: requiredText('校服尺码', 'change')
+};

+ 147 - 0
web/src/views/zsjy/enrollment/enrollmentPrint.ts

@@ -0,0 +1,147 @@
+export interface EnrollmentPrintField {
+  label: string;
+  value?: string | number | null;
+  /** 占满一行(如地址、特长等) */
+  fullWidth?: boolean;
+}
+
+export interface EnrollmentPrintSection {
+  title: string;
+  fields: EnrollmentPrintField[];
+}
+
+const formatValue = (value?: string | number | null) => {
+  if (value === undefined || value === null || value === '') {
+    return '-';
+  }
+  return String(value);
+};
+
+const escapeHtml = (text: string) =>
+  text
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#39;');
+
+const buildSectionHtml = (section: EnrollmentPrintSection) => {
+  const fieldsHtml = section.fields
+    .map((field) => {
+      const cssClass = field.fullWidth ? 'field full' : 'field';
+      return `<div class="${cssClass}"><span class="label">${escapeHtml(field.label)}:</span><span class="value">${escapeHtml(formatValue(field.value))}</span></div>`;
+    })
+    .join('');
+
+  return `<section class="section"><h2>${escapeHtml(section.title)}</h2><div class="fields">${fieldsHtml}</div></section>`;
+};
+
+/** 打印招生报名信息 */
+export const printEnrollmentInfo = (documentTitle: string, sections: EnrollmentPrintSection[], printTime?: string) => {
+  const printWindow = window.open('', '_blank');
+  if (!printWindow) {
+    throw new Error('无法打开打印窗口,请检查浏览器是否拦截弹窗');
+  }
+
+  const sectionsHtml = sections.map(buildSectionHtml).join('');
+  const timeText = printTime || new Date().toLocaleString('zh-CN');
+
+  const html = `<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="utf-8" />
+  <title>${escapeHtml(documentTitle)}</title>
+  <style>
+    * { box-sizing: border-box; }
+    body {
+      margin: 0;
+      padding: 24px 32px;
+      font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
+      color: #303133;
+      font-size: 14px;
+      line-height: 1.6;
+    }
+    .print-header {
+      text-align: center;
+      margin-bottom: 24px;
+      padding-bottom: 12px;
+      border-bottom: 2px solid #409eff;
+    }
+    .print-title {
+      margin: 0 0 8px;
+      font-size: 22px;
+      font-weight: 700;
+    }
+    .print-meta {
+      margin: 0;
+      color: #909399;
+      font-size: 13px;
+    }
+    .section {
+      margin-bottom: 20px;
+      page-break-inside: avoid;
+    }
+    .section h2 {
+      margin: 0 0 12px;
+      padding-left: 10px;
+      font-size: 16px;
+      border-left: 4px solid #409eff;
+    }
+    .fields {
+      display: flex;
+      flex-wrap: wrap;
+      border: 1px solid #ebeef5;
+      border-radius: 4px;
+      overflow: hidden;
+    }
+    .field {
+      width: 33.33%;
+      display: flex;
+      padding: 10px 12px;
+      border-bottom: 1px solid #ebeef5;
+      border-right: 1px solid #ebeef5;
+      min-height: 42px;
+    }
+    .field:nth-child(3n) {
+      border-right: none;
+    }
+    .field.full {
+      width: 100%;
+      border-right: none;
+    }
+    .label {
+      flex: 0 0 auto;
+      color: #606266;
+      font-weight: 600;
+      white-space: nowrap;
+    }
+    .value {
+      flex: 1;
+      color: #303133;
+      word-break: break-all;
+    }
+    @media print {
+      body { padding: 12px 16px; }
+      @page { margin: 12mm; }
+    }
+  </style>
+</head>
+<body>
+  <div class="print-header">
+    <h1 class="print-title">${escapeHtml(documentTitle)}</h1>
+    <p class="print-meta">打印时间:${escapeHtml(timeText)}</p>
+  </div>
+  ${sectionsHtml}
+  <script>
+    window.onload = function () {
+      window.print();
+      window.onafterprint = function () { window.close(); };
+    };
+  <\/script>
+</body>
+</html>`;
+
+  printWindow.document.open();
+  printWindow.document.write(html);
+  printWindow.document.close();
+};

+ 140 - 149
web/src/views/zsjy/enrollment/index.vue

@@ -189,12 +189,12 @@
           <h3 class="form-section-title">基本信息</h3>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="姓名" prop="name">
+              <el-form-item label="姓名" prop="name" required>
                 <el-input v-model="form.name" placeholder="请输入姓名" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="身份证号" prop="idCard">
+              <el-form-item label="身份证号" prop="idCard" required>
                 <el-input
                   v-model="form.idCard"
                   placeholder="请输入身份证号"
@@ -205,7 +205,7 @@
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="性别" prop="gender">
+              <el-form-item label="性别" prop="gender" required>
                 <el-select
                   v-model="form.gender"
                   placeholder="请选择性别"
@@ -220,43 +220,43 @@
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="出生日期" prop="birthDate">
+              <el-form-item label="出生日期" prop="birthDate" required>
                 <el-date-picker v-model="form.birthDate" type="date" placeholder="选择出生日期" value-format="YYYY-MM-DD" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="联系电话" prop="phone1">
+              <el-form-item label="联系电话" prop="phone1" required>
                 <el-input v-model="form.phone1" placeholder="请输入联系电话" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="备用电话" prop="phone2">
+              <el-form-item label="备用电话" prop="phone2" required>
                 <el-input v-model="form.phone2" placeholder="请输入备用电话" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="民族" prop="nation">
+              <el-form-item label="民族" prop="nation" required>
                 <el-input v-model="form.nation" placeholder="请输入民族" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="是否住宿" prop="isBoarding">
+              <el-form-item label="是否住宿" prop="isBoarding" required>
                 <el-select v-model="form.isBoarding" placeholder="请选择是否住宿" style="width: 100%">
                   <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="健康状况" prop="healthStatus">
+              <el-form-item label="健康状况" prop="healthStatus" required>
                 <el-input v-model="form.healthStatus" placeholder="请输入健康状况" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-form-item label="家庭住址" prop="homeAddress">
+              <el-form-item label="家庭住址" prop="homeAddress" required>
                 <el-input v-model="form.homeAddress" placeholder="请输入家庭住址" />
               </el-form-item>
             </el-col>
@@ -268,63 +268,63 @@
           <h3 class="form-section-title">学业信息</h3>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="毕业中学" prop="middleSchool">
+              <el-form-item label="毕业中学" prop="middleSchool" required>
                 <el-input v-model="form.middleSchool" placeholder="请输入毕业中学" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="准考证号" prop="examNumber">
+              <el-form-item label="准考证号" prop="examNumber" required>
                 <el-input v-model="form.examNumber" placeholder="请输入准考证号" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="中考地区" prop="examRegion">
+              <el-form-item label="中考地区" prop="examRegion" required>
                 <el-input v-model="form.examRegion" placeholder="请输入中考地区" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="中考满分" prop="totalScoreFull">
+              <el-form-item label="中考满分" prop="totalScoreFull" required>
                 <el-input-number v-model="form.totalScoreFull" placeholder="请输入中考满分" :min="100" :max="1000" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="中考成绩" prop="totalScore">
+              <el-form-item label="中考成绩" prop="totalScore" required>
                 <el-input-number v-model="form.totalScore" placeholder="请输入中考成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="语文成绩" prop="chineseScore">
+              <el-form-item label="语文成绩" prop="chineseScore" required>
                 <el-input-number v-model="form.chineseScore" placeholder="请输入语文成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="数学成绩" prop="mathScore">
+              <el-form-item label="数学成绩" prop="mathScore" required>
                 <el-input-number v-model="form.mathScore" placeholder="请输入数学成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="英语成绩" prop="englishScore">
+              <el-form-item label="英语成绩" prop="englishScore" required>
                 <el-input-number v-model="form.englishScore" placeholder="请输入英语成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="科学成绩" prop="scienceScore">
+              <el-form-item label="科学成绩" prop="scienceScore" required>
                 <el-input-number v-model="form.scienceScore" placeholder="请输入科学成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="8">
-              <el-form-item label="社会成绩" prop="socialScore">
+              <el-form-item label="社会成绩" prop="socialScore" required>
                 <el-input-number v-model="form.socialScore" placeholder="请输入社会成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
             <el-col :span="8">
-              <el-form-item label="体育成绩" prop="peScore">
+              <el-form-item label="体育成绩" prop="peScore" required>
                 <el-input-number v-model="form.peScore" placeholder="请输入体育成绩" :min="0" style="width: 100%" />
               </el-form-item>
             </el-col>
@@ -336,7 +336,7 @@
           <h3 class="form-section-title">报考信息</h3>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="所属学院" prop="collegeName">
+              <el-form-item label="所属学院" prop="collegeName" required>
                 <el-select
                   v-model="(form as EnrollmentInfoFormExtended).selectCollegeId"
                   placeholder="请选择所属学院"
@@ -348,30 +348,30 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="学院代码" prop="collegeId">
+              <el-form-item label="学院代码" prop="collegeId" required>
                 <el-input v-model="form.collegeId" placeholder="学院代码将自动填入" readonly />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="报考专业" prop="appliedMajor">
+              <el-form-item label="报考专业" prop="appliedMajor" required>
                 <el-select v-model="form.appliedMajor" placeholder="请选择报考专业" style="width: 100%" @change="onMajorChange">
                   <el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
                 </el-select>
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="专业代码" prop="majorCode">
+              <el-form-item label="专业代码" prop="majorCode" required>
                 <el-input v-model="form.majorCode" placeholder="专业代码将自动填入" readonly />
               </el-form-item>
             </el-col>
           </el-row>
           <el-row :gutter="20">
             <el-col :span="12">
-              <el-form-item label="校服尺码" prop="uniformSize">
+              <el-form-item label="校服尺码" prop="uniformSize" required>
                 <el-select v-model="form.uniformSize" placeholder="请选择校服尺码" style="width: 100%">
-                  <el-option v-for="dict in zsjy_uniform_size" :key="dict.value" :label="dict.label" :value="dict.value" />
+                  <el-option v-for="dict in uniformSizeOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
                 </el-select>
               </el-form-item>
             </el-col>
@@ -605,6 +605,7 @@
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="cancelView">关 闭</el-button>
+          <el-button type="success" @click="printViewForm">打 印</el-button>
           <el-button type="primary" @click="showRawData">查看原始数据</el-button>
         </div>
       </template>
@@ -824,7 +825,10 @@ import { createProcessFromEnrollment } from '@/api/zsjy/enrollmentProcess';
 import { EnrollmentInfoVO, EnrollmentInfoQuery, EnrollmentInfoForm } from '@/api/zsjy/enrollment/types';
 import { ComponentInternalInstance, getCurrentInstance, reactive, ref, toRefs, onMounted, watch, computed, nextTick } from 'vue';
 import { FormInstance } from 'element-plus';
-import { listDept } from '@/api/system/dept';
+import { listOpenColleges, listOpenMajorsByCollegeId } from '@/api/zsjy/enrollmentMajor';
+import { enrollmentFormRules } from './enrollmentFormRules';
+import { getUniformSizeLabel as resolveUniformSizeLabel, resolveUniformSizeOptions } from './uniformSizeOptions';
+import { printEnrollmentInfo } from './enrollmentPrint';
 import { DeptVO } from '@/api/system/dept/types';
 
 // 状态常量
@@ -1040,39 +1044,16 @@ const data = reactive<PageData<EnrollmentInfoForm, EnrollmentInfoQuery>>({
     majorCode: undefined,
     phone1: undefined,
     examRegion: undefined,
-    enrollmentStatus: ENROLLMENT_STATUS.AUDITING, // 默认只查询"信息审核中"状态的学生
+    enrollmentStatus: undefined,
     params: {}
   },
-  rules: {
-    name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
-    idCard: [
-      { required: true, message: '身份证号不能为空', trigger: 'blur' },
-      {
-        pattern: /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
-        message: '身份证号格式不正确',
-        trigger: 'blur'
-      }
-    ],
-    gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
-    phone1: [
-      { required: true, message: '联系电话不能为空', trigger: 'blur' },
-      { pattern: /^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
-    ],
-    phone2: [{ pattern: /^$|^1[3-9]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }],
-    appliedMajor: [{ required: true, message: '报考专业不能为空', trigger: 'change' }],
-    totalScoreFull: [{ type: 'number', min: 100, max: 1000, message: '中考满分应在100-1000之间', trigger: 'blur' }],
-    totalScore: [{ type: 'number', min: 0, message: '中考成绩不能小于0', trigger: 'blur' }],
-    chineseScore: [{ type: 'number', min: 0, message: '语文成绩不能小于0', trigger: 'blur' }],
-    mathScore: [{ type: 'number', min: 0, message: '数学成绩不能小于0', trigger: 'blur' }],
-    englishScore: [{ type: 'number', min: 0, message: '英语成绩不能小于0', trigger: 'blur' }],
-    scienceScore: [{ type: 'number', min: 0, message: '科学成绩不能小于0', trigger: 'blur' }],
-    socialScore: [{ type: 'number', min: 0, message: '社会成绩不能小于0', trigger: 'blur' }],
-    peScore: [{ type: 'number', min: 0, message: '体育成绩不能小于0', trigger: 'blur' }]
-  }
+  rules: enrollmentFormRules
 });
 
 const { queryParams, form, rules } = toRefs(data);
 
+const uniformSizeOptions = computed(() => resolveUniformSizeOptions(zsjy_uniform_size.value));
+
 /** 身份证号变化处理 */
 const onIdCardChange = () => {
   const idCard = form.value.idCard;
@@ -1267,12 +1248,7 @@ const getCollegeNameEnhanced = (student: any) => {
 };
 
 /** 获取校服尺码显示名称 */
-const getUniformSizeLabel = (value: string | undefined) => {
-  if (!value) return '-';
-  // 使用字典数据查找校服尺码
-  const size = zsjy_uniform_size.value?.find((item: any) => item.value === value);
-  return size ? size.label : value;
-};
+const getUniformSizeLabel = (value: string | undefined) => resolveUniformSizeLabel(value, zsjy_uniform_size.value);
 
 /** 获取是否住宿显示文本 */
 const getBoardingDisplay = (value: any) => {
@@ -1360,8 +1336,6 @@ const handleQuery = () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value?.resetFields();
-  // 确保重置后状态过滤条件依然生效
-  queryParams.value.enrollmentStatus = ENROLLMENT_STATUS.AUDITING;
   handleQuery();
 };
 
@@ -1733,6 +1707,82 @@ const cancelView = () => {
   viewDialog.visible = false;
 };
 
+/** 打印查看的报名信息 */
+const printViewForm = () => {
+  const form = viewForm;
+  const display = (value: unknown) => (value === undefined || value === null || value === '' ? '-' : String(value));
+  const genderText =
+    form.gender === 'M' ? '男' : form.gender === 'F' ? '女' : getGenderDisplay(form.gender, form.idCard);
+  const statusText = form.enrollmentStatus
+    ? proxy?.selectDictLabel(zsjy_enrollment_status.value, form.enrollmentStatus) || form.enrollmentStatus
+    : '-';
+  const applicationTimeText = form.applicationTime
+    ? proxy?.parseTime(form.applicationTime, '{y}-{m}-{d} {h}:{i}:{s}')
+    : '-';
+
+  try {
+    printEnrollmentInfo(
+      `招生报名信息${form.name ? ' - ' + form.name : ''}`,
+      [
+        {
+          title: '基本信息',
+          fields: [
+            { label: '姓名', value: display(form.name) },
+            { label: '身份证号', value: display(form.idCard) },
+            { label: '性别', value: genderText },
+            { label: '出生日期', value: display(form.birthDate) },
+            { label: '联系电话', value: display(form.phone1) },
+            { label: '备用电话', value: display(form.phone2) },
+            { label: '民族', value: display(form.nation) },
+            { label: '是否住宿', value: getBoardingDisplay(form.isBoarding) },
+            { label: '健康状况', value: display(form.healthStatus) },
+            { label: '家庭住址', value: display(form.homeAddress), fullWidth: true }
+          ]
+        },
+        {
+          title: '学业信息',
+          fields: [
+            { label: '毕业中学', value: display(form.middleSchool) },
+            { label: '准考证号', value: display(form.examNumber) },
+            { label: '中考地区', value: display(form.examRegion) },
+            { label: '中考满分', value: display(form.totalScoreFull) },
+            { label: '中考成绩', value: display(form.totalScore) },
+            { label: '语文成绩', value: display(form.chineseScore) },
+            { label: '数学成绩', value: display(form.mathScore) },
+            { label: '英语成绩', value: display(form.englishScore) },
+            { label: '科学成绩', value: display(form.scienceScore) },
+            { label: '社会成绩', value: display(form.socialScore) },
+            { label: '体育成绩', value: display(form.peScore) }
+          ]
+        },
+        {
+          title: '报考信息',
+          fields: [
+            { label: '所属学院', value: display(form.collegeName) },
+            { label: '学院代码', value: display(form.collegeId) },
+            { label: '报考专业', value: display(form.appliedMajor) },
+            { label: '专业代码', value: display(form.majorCode) },
+            { label: '校服尺码', value: getUniformSizeLabel(form.uniformSize) },
+            { label: '报名状态', value: statusText },
+            { label: '报名时间', value: applicationTimeText }
+          ]
+        },
+        {
+          title: '其他信息',
+          fields: [
+            { label: '特长爱好', value: display(form.specialties), fullWidth: true },
+            { label: '获奖情况', value: display(form.awards), fullWidth: true }
+          ]
+        }
+      ],
+      proxy?.parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}')
+    );
+  } catch (error) {
+    console.error('打印报名信息失败:', error);
+    proxy?.$modal.msgError('无法打开打印窗口,请检查浏览器是否拦截弹窗');
+  }
+};
+
 /** 显示原始数据 */
 const showRawData = () => {
   // 显示所有表单数据
@@ -1833,93 +1883,40 @@ const collegeOptions = ref<DeptVO[]>([]);
 const majorOptionsByCollege = ref<{ [key: string]: DeptVO[] }>({});
 
 // 添加获取部门数据的方法
-/** 获取学院和专业数据 */
+/** 获取学院和专业数据(仅开放报名的专业) */
 const getCollegeAndMajorData = async () => {
   try {
-    console.log('开始加载学院和专业数据...');
-
-    // 强制刷新字典数据
-    console.log('加载学院字典数据...');
-    try {
-      const dictResponse = await proxy?.getDicts('zsjy_college');
-      console.log('学院字典加载结果:', dictResponse);
-    } catch (dictError) {
-      console.error('加载字典数据出错:', dictError);
-    }
-
-    // 获取所有部门数据
-    console.log('加载部门数据...');
-    const res = await listDept();
+    console.log('开始加载开放报名的学院和专业数据...');
 
+    const res = await listOpenColleges();
     if (res.code === 200 && res.data) {
-      // 找出学院 - 类别编码以xy开头的部门
-      const colleges = res.data.filter((dept: DeptVO) => {
-        return dept.deptCategory && dept.deptCategory.startsWith('xy');
-      });
+      const colleges = res.data.map((item: any) => ({
+        deptId: item.deptId,
+        deptName: item.deptName,
+        deptCategory: item.deptCategory,
+        parentId: item.parentId
+      })) as DeptVO[];
 
       collegeOptions.value = colleges;
-      console.log(
-        '获取到的学院数据:',
-        colleges.map((c) => ({
-          deptId: c.deptId,
-          deptName: c.deptName,
-          deptCategory: c.deptCategory
-        }))
+      majorOptionsByCollege.value = {};
+
+      await Promise.all(
+        colleges.map(async (college) => {
+          const majorRes = await listOpenMajorsByCollegeId(college.deptId);
+          const majors = (majorRes.data || []).map((major: any) => ({
+            deptId: major.deptId,
+            deptName: major.deptName,
+            deptCategory: major.deptCategory,
+            parentId: college.deptId
+          })) as DeptVO[];
+          majorOptionsByCollege.value[college.deptId] = majors;
+        })
       );
 
-      // 如果没有找到学院数据,尝试其他可能的过滤条件
-      if (colleges.length === 0) {
-        console.log('未找到xy开头的学院,尝试其他可能的部门...');
-
-        // 尝试查找所有非叶子节点部门
-        const possibleColleges = res.data.filter((dept: DeptVO) => {
-          return !dept.isLeaf;
-        });
-
-        if (possibleColleges.length > 0) {
-          collegeOptions.value = possibleColleges;
-          console.log(
-            '找到可能的学院:',
-            possibleColleges.map((c) => ({
-              deptId: c.deptId,
-              deptName: c.deptName,
-              deptCategory: c.deptCategory
-            }))
-          );
-        }
-      }
-
-      // 找出专业 - 类别编码以zy开头的部门
-      const allMajors = res.data.filter((dept: DeptVO) => {
-        return dept.deptCategory && dept.deptCategory.startsWith('zy');
-      });
-
-      console.log('获取到的所有专业数据:', allMajors.length);
-
-      // 按照上下级关系组织专业
-      colleges.forEach((college: DeptVO) => {
-        // 找出该学院下的所有专业
-        const majors = allMajors.filter(
-          (dept: DeptVO) =>
-            // 使用上下级关系或其他关联关系确定专业归属哪个学院
-            dept.parentId === college.deptId
-        );
-
-        majorOptionsByCollege.value[college.deptId] = majors;
-        if (majors.length > 0) {
-          console.log(`学院 ${college.deptName} 的专业数量: ${majors.length}`);
-        }
-      });
-
-      // 如果当前有学生信息且缺少学院名称,尝试找到匹配
       if (viewForm.collegeId && !viewForm.collegeName) {
-        // 查找学院名称
         const college = collegeOptions.value.find((c) => c.deptCategory === viewForm.collegeId);
         if (college) {
-          console.log(`根据学院代码 ${viewForm.collegeId} 找到学院名称: ${college.deptName}`);
           viewForm.collegeName = college.deptName;
-        } else {
-          console.warn(`未找到匹配学院代码 ${viewForm.collegeId} 的学院名称`);
         }
       }
 
@@ -1927,20 +1924,14 @@ const getCollegeAndMajorData = async () => {
         success: true,
         collegeCount: collegeOptions.value.length
       };
-    } else {
-      console.error('部门数据返回异常:', res);
-      return {
-        success: false,
-        error: '部门数据返回异常'
-      };
     }
+
+    console.error('开放学院数据返回异常:', res);
+    return { success: false, error: '开放学院数据返回异常' };
   } catch (error) {
-    console.error('获取部门数据出错:', error);
+    console.error('获取开放学院和专业数据出错:', error);
     proxy?.$modal.msgError('获取学院和专业数据失败');
-    return {
-      success: false,
-      error: error
-    };
+    return { success: false, error };
   }
 };
 

+ 28 - 0
web/src/views/zsjy/enrollment/uniformSizeOptions.ts

@@ -0,0 +1,28 @@
+/** 招生报名校服尺码标准选项(与移动端兜底数据一致) */
+export const DEFAULT_UNIFORM_SIZE_OPTIONS: DictDataOption[] = [
+  { value: '150/84A', label: '150/84A(身高150cm)' },
+  { value: '155/86A', label: '155/86A(身高155cm)' },
+  { value: '160/88A', label: '160/88A(身高160cm)' },
+  { value: '165/90A', label: '165/90A(身高165cm)' },
+  { value: '170/92A', label: '170/92A(身高170cm)' },
+  { value: '175/94A', label: '175/94A(身高175cm)' },
+  { value: '180/96A', label: '180/96A(身高180cm)' },
+  { value: '185/98A', label: '185/98A(身高185cm)' },
+  { value: '190/100A', label: '190/100A(身高190cm)' }
+];
+
+/** 优先使用字典数据,字典为空时使用标准尺码兜底 */
+export const resolveUniformSizeOptions = (dictOptions?: DictDataOption[] | null): DictDataOption[] => {
+  if (dictOptions && dictOptions.length > 0) {
+    return dictOptions;
+  }
+  return DEFAULT_UNIFORM_SIZE_OPTIONS;
+};
+
+/** 获取校服尺码显示名称 */
+export const getUniformSizeLabel = (value: string | undefined, dictOptions?: DictDataOption[] | null): string => {
+  if (!value) return '-';
+  const options = resolveUniformSizeOptions(dictOptions);
+  const size = options.find((item) => item.value === value);
+  return size ? size.label : value;
+};

+ 227 - 0
web/src/views/zsjy/enrollmentMajor/index.vue

@@ -0,0 +1,227 @@
+<template>
+  <div class="p-2 page-container">
+    <div class="flex h-full">
+      <div class="w-72 mr-4">
+        <el-card shadow="never" class="h-full">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium">二级学院</span>
+              <el-button type="primary" link icon="Plus" @click="goAddMajor">新增专业</el-button>
+            </div>
+          </template>
+          <el-tree
+            v-loading="collegeLoading"
+            :data="collegeTreeData"
+            :props="treeProps"
+            node-key="deptId"
+            highlight-current
+            :current-node-key="activeCollegeId"
+            @node-click="handleCollegeClick"
+          >
+            <template #default="{ node, data }">
+              <span class="text-sm">{{ node.label }}</span>
+              <span v-if="data.deptCategory" class="text-xs text-gray-400 ml-2">({{ data.deptCategory }})</span>
+            </template>
+          </el-tree>
+        </el-card>
+      </div>
+
+      <div class="flex-1">
+        <el-card shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium">{{ activeCollegeName || '招生报名专业管理' }}</span>
+              <el-button type="primary" :loading="saving" @click="handleSave" v-hasPermi="['zsjy:enrollmentMajor:save']">
+                保存本学院配置
+              </el-button>
+            </div>
+          </template>
+
+          <div v-loading="loading" class="major-config-panel">
+            <el-empty v-if="!activeCollegeId" description="请在左侧选择学院" />
+            <template v-else>
+              <div class="toolbar">
+                <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选</el-checkbox>
+                <span class="selected-count">已选 {{ checkedMajorIds.length }} / {{ majorList.length }}</span>
+              </div>
+
+              <el-empty v-if="majorList.length === 0" description="该学院暂无专业,请前往专业信息维护" />
+
+              <el-checkbox-group v-else v-model="checkedMajorIds" class="major-checkbox-group" @change="handleCheckedChange">
+                <el-checkbox v-for="item in majorList" :key="item.majorDeptId" :label="String(item.majorDeptId)" class="major-checkbox-item">
+                  {{ item.majorName }}
+                  <span class="major-code">({{ item.majorCode }})</span>
+                </el-checkbox>
+              </el-checkbox-group>
+            </template>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="EnrollmentMajor">
+import { ref, computed, onMounted, getCurrentInstance } from 'vue';
+import router from '@/router';
+import {
+  listEnrollmentMajorColleges,
+  listEnrollmentMajorConfig,
+  saveEnrollmentMajorConfig,
+  EnrollmentDeptVO
+} from '@/api/zsjy/enrollmentMajor';
+
+const { proxy } = getCurrentInstance() as any;
+
+const collegeList = ref<EnrollmentDeptVO[]>([]);
+const majorList = ref<EnrollmentDeptVO[]>([]);
+const activeCollegeId = ref('');
+const checkedMajorIds = ref<string[]>([]);
+const collegeLoading = ref(false);
+const loading = ref(false);
+const saving = ref(false);
+const checkAll = ref(false);
+const isIndeterminate = ref(false);
+
+const treeProps = {
+  label: 'deptName',
+  children: 'children'
+};
+
+const collegeTreeData = computed(() => collegeList.value);
+
+const activeCollegeName = computed(() => {
+  const college = collegeList.value.find((item) => String(item.deptId) === activeCollegeId.value);
+  return college?.deptName || '';
+});
+
+const syncCheckAllState = () => {
+  const total = majorList.value.length;
+  const checked = checkedMajorIds.value.length;
+  checkAll.value = total > 0 && checked === total;
+  isIndeterminate.value = checked > 0 && checked < total;
+};
+
+const loadColleges = async () => {
+  collegeLoading.value = true;
+  try {
+    const res = await listEnrollmentMajorColleges();
+    collegeList.value = res.data || [];
+    if (collegeList.value.length === 0) {
+      proxy?.$modal.msgWarning('未加载到学院数据,请确认后端已更新并分配菜单权限');
+      return;
+    }
+    activeCollegeId.value = String(collegeList.value[0].deptId);
+    await loadMajorConfig(activeCollegeId.value);
+  } catch (error) {
+    console.error('加载学院失败', error);
+    proxy?.$modal.msgError('加载学院数据失败,请确认后端服务已重启');
+  } finally {
+    collegeLoading.value = false;
+  }
+};
+
+const loadMajorConfig = async (collegeDeptId: string) => {
+  if (!collegeDeptId) return;
+  loading.value = true;
+  try {
+    const res = await listEnrollmentMajorConfig(collegeDeptId);
+    majorList.value = res.data || [];
+    checkedMajorIds.value = majorList.value
+      .filter((item) => item.selected === true || item.selected === 1)
+      .map((item) => String(item.majorDeptId));
+    syncCheckAllState();
+  } catch (error) {
+    console.error('加载专业配置失败', error);
+    proxy?.$modal.msgError('加载专业配置失败');
+    majorList.value = [];
+    checkedMajorIds.value = [];
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleCollegeClick = (data: EnrollmentDeptVO) => {
+  activeCollegeId.value = String(data.deptId);
+  loadMajorConfig(activeCollegeId.value);
+};
+
+const handleCheckAllChange = (val: boolean) => {
+  checkedMajorIds.value = val ? majorList.value.map((item) => String(item.majorDeptId)) : [];
+  isIndeterminate.value = false;
+};
+
+const handleCheckedChange = () => {
+  syncCheckAllState();
+};
+
+const handleSave = async () => {
+  if (!activeCollegeId.value) return;
+  saving.value = true;
+  try {
+    await saveEnrollmentMajorConfig({
+      collegeDeptId: activeCollegeId.value,
+      majorDeptIds: checkedMajorIds.value
+    });
+    proxy?.$modal.msgSuccess('保存成功');
+    await loadMajorConfig(activeCollegeId.value);
+  } catch (error) {
+    console.error('保存失败', error);
+  } finally {
+    saving.value = false;
+  }
+};
+
+const resolveMajorManagePath = () => {
+  const matched = router.getRoutes().find((route) => {
+    const component = route.meta?.component as string | undefined;
+    return (component && component.includes('majormsg/index')) || route.path.endsWith('/majormsg');
+  });
+  return matched?.path || '/student/classmsg/majormsg';
+};
+
+const goAddMajor = () => {
+  const path = resolveMajorManagePath();
+  router.push(path).catch(() => {
+    proxy?.$modal.msgError('未找到专业信息页面,请联系管理员配置菜单');
+  });
+};
+
+onMounted(() => {
+  loadColleges();
+});
+</script>
+
+<style scoped>
+.major-config-panel {
+  min-height: 360px;
+}
+
+.toolbar {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.selected-count {
+  color: #909399;
+  font-size: 13px;
+}
+
+.major-checkbox-group {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  gap: 8px 16px;
+}
+
+.major-checkbox-item {
+  margin-right: 0;
+}
+
+.major-code {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 4px;
+}
+</style>

+ 7 - 3
web/src/views/zsjy/enrollmentStatistics/index.vue

@@ -170,7 +170,6 @@
 import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
 import {
   listEnrollmentProcess,
-  exportEnrollmentProcess,
   EnrollmentProcessQuery,
   EnrollmentProcessVO,
   getEnrollmentProcess
@@ -498,9 +497,14 @@ const resetQuery = () => {
   handleQuery();
 };
 
-// 处理导出
+// 处理导出(新生录取名册格式)
 const handleExport = () => {
-  exportEnrollmentProcess(queryParams);
+  const year = new Date().getFullYear();
+  proxy?.download(
+    '/zsjy/enrollment/process/exportRegister',
+    { ...queryParams },
+    `${year}年新生录取名册_${new Date().getTime()}.xlsx`
+  );
 };
 
 // 查看详情