wuzhen 更新假期住宿申请
This commit is contained in:
1 parent
1d67506451
commit
3518ea697e
35 files changed
+2173
-98
No files matched your search
+83
-27
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
@@ -19,6 +18,7 @@ import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayAuditBo;
|
||||
import org.dromara.xg.domain.vo.StudentOptionVo;
|
||||
import org.dromara.xg.domain.bo.StudentOptionBo;
|
||||
import org.dromara.xg.service.IXgHolidayStayService;
|
||||
@@ -48,7 +48,7 @@ public class XgHolidayStayController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出假期留宿管理列表
|
||||
* 导出假期留宿管理列表(可按 applyStatus 筛选)
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:export")
|
||||
@Log(title = "假期留宿管理", businessType = BusinessType.EXPORT)
|
||||
@@ -58,10 +58,88 @@ public class XgHolidayStayController extends BaseController {
|
||||
ExcelUtil.exportExcel(list, "假期留宿管理", XgHolidayStayVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生选择器选项列表(支持按学号、姓名检索)
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:list")
|
||||
@GetMapping("/studentOptions")
|
||||
public TableDataInfo<StudentOptionVo> getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) {
|
||||
return xgHolidayStayService.getStudentOptions(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单人留宿有效数据
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:list")
|
||||
@GetMapping("/singlePersonValidStay")
|
||||
public R<List<XgHolidayStayVo>> getSinglePersonValidStay() {
|
||||
return R.ok(xgHolidayStayService.querySinglePersonValidStay());
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生本人提交留宿申请
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:apply")
|
||||
@Log(title = "假期留宿申请", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/apply")
|
||||
public R<Void> apply(@RequestBody XgHolidayStayBo bo) {
|
||||
return toAjax(xgHolidayStayService.studentApply(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前学生的申请列表
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:apply")
|
||||
@GetMapping("/myList")
|
||||
public TableDataInfo<XgHolidayStayVo> myList(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
return xgHolidayStayService.queryMyApplyPage(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 班主任本班申请列表
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:list1")
|
||||
@GetMapping("/teacherList")
|
||||
public TableDataInfo<XgHolidayStayVo> teacherList(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
return xgHolidayStayService.queryTeacherPage(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 班主任审批通过
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:audit")
|
||||
@Log(title = "假期留宿审批通过", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/approve")
|
||||
public R<Void> approve(@Validated @RequestBody XgHolidayStayAuditBo auditBo) {
|
||||
return toAjax(xgHolidayStayService.approve(auditBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 班主任驳回
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:audit")
|
||||
@Log(title = "假期留宿驳回", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/reject")
|
||||
public R<Void> reject(@Validated @RequestBody XgHolidayStayAuditBo auditBo) {
|
||||
return toAjax(xgHolidayStayService.reject(auditBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 班主任撤销已通过申请
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:revoke")
|
||||
@Log(title = "假期留宿撤销", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/revoke")
|
||||
public R<Void> revoke(@Validated(XgHolidayStayAuditBo.Revoke.class) @RequestBody XgHolidayStayAuditBo auditBo) {
|
||||
return toAjax(xgHolidayStayService.revoke(auditBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿管理详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:query")
|
||||
@GetMapping("/{id}")
|
||||
@@ -70,7 +148,7 @@ public class XgHolidayStayController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增假期留宿管理
|
||||
* 新增假期留宿管理(宿管登记,默认已通过)
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:add")
|
||||
@Log(title = "假期留宿管理", businessType = BusinessType.INSERT)
|
||||
@@ -93,8 +171,6 @@ public class XgHolidayStayController extends BaseController {
|
||||
|
||||
/**
|
||||
* 删除假期留宿管理
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:remove")
|
||||
@Log(title = "假期留宿管理", businessType = BusinessType.DELETE)
|
||||
@@ -102,24 +178,4 @@ public class XgHolidayStayController extends BaseController {
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable("ids") Long[] ids) {
|
||||
return toAjax(xgHolidayStayService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生选择器选项列表(支持按学号、姓名检索)
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:list")
|
||||
@GetMapping("/studentOptions")
|
||||
public TableDataInfo<StudentOptionVo> getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) {
|
||||
return xgHolidayStayService.getStudentOptions(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单人留宿有效数据
|
||||
* 获取所有有效的假期留宿数据,筛选出在同一时间段内该宿舍只有一个学生留宿的记录
|
||||
*/
|
||||
// @SaCheckPermission("holidayStay:holidayStay:list")
|
||||
@GetMapping("/singlePersonValidStay")
|
||||
public R<List<XgHolidayStayVo>> getSinglePersonValidStay() {
|
||||
List<XgHolidayStayVo> result = xgHolidayStayService.querySinglePersonValidStay();
|
||||
return R.ok(result);
|
||||
}
|
||||
}
|
||||
+10
@@ -110,6 +110,16 @@ public class XgDormHygieneCheck extends TenantEntity {
|
||||
*/
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级)
|
||||
*/
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 宿舍扣分(0-100分)
|
||||
*/
|
||||
|
||||
@@ -199,5 +199,55 @@ public class XgHolidayStay extends TenantEntity {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
private String applyStatus;
|
||||
|
||||
/**
|
||||
* 提交申请时间
|
||||
*/
|
||||
private Date applyTime;
|
||||
|
||||
/**
|
||||
* 审批人用户ID
|
||||
*/
|
||||
private Long auditorId;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
private String auditorName;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
private Date auditTime;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String auditOpinion;
|
||||
|
||||
/**
|
||||
* 撤销人用户ID
|
||||
*/
|
||||
private Long revokeBy;
|
||||
|
||||
/**
|
||||
* 撤销人姓名
|
||||
*/
|
||||
private String revokeByName;
|
||||
|
||||
/**
|
||||
* 撤销时间
|
||||
*/
|
||||
private Date revokeTime;
|
||||
|
||||
/**
|
||||
* 撤销原因
|
||||
*/
|
||||
private String revokeReason;
|
||||
|
||||
|
||||
}
|
||||
+12
@@ -117,6 +117,18 @@ public class XgDormHygieneCheckBo extends BaseEntity {
|
||||
*/
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级)
|
||||
*/
|
||||
@NotNull(message = "归属班级不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
@NotBlank(message = "归属班级名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
|
||||
+12
@@ -118,6 +118,18 @@ public class XgDormHygieneCheckCompleteBo {
|
||||
*/
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级)
|
||||
*/
|
||||
@NotNull(message = "归属班级不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
@NotBlank(message = "归属班级名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.dromara.xg.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 假期留宿审批/撤销参数
|
||||
*/
|
||||
@Data
|
||||
public class XgHolidayStayAuditBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull(message = "申请ID不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 审批意见(驳回建议填写)
|
||||
*/
|
||||
private String auditOpinion;
|
||||
|
||||
/**
|
||||
* 撤销原因(撤销必填)
|
||||
*/
|
||||
@NotBlank(message = "撤销原因不能为空", groups = { Revoke.class })
|
||||
private String revokeReason;
|
||||
|
||||
public interface Revoke {
|
||||
}
|
||||
}
|
||||
+18
@@ -209,5 +209,23 @@ public class XgHolidayStayBo extends BaseEntity {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
private String applyStatus;
|
||||
|
||||
/**
|
||||
* 提交申请时间
|
||||
*/
|
||||
private Date applyTime;
|
||||
|
||||
private Long auditorId;
|
||||
private String auditorName;
|
||||
private Date auditTime;
|
||||
private String auditOpinion;
|
||||
private Long revokeBy;
|
||||
private String revokeByName;
|
||||
private Date revokeTime;
|
||||
private String revokeReason;
|
||||
|
||||
}
|
||||
+11
@@ -123,6 +123,17 @@ public class XgDormHygieneCheckSimplifiedVo implements Serializable {
|
||||
@ExcelProperty(value = "校区名称")
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属)
|
||||
*/
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
@ExcelProperty(value = "归属班级")
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
|
||||
+12
@@ -135,6 +135,18 @@ public class XgDormHygieneCheckVo implements Serializable {
|
||||
@ExcelProperty(value = "校区名称")
|
||||
private String campusName;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属)
|
||||
*/
|
||||
@ExcelProperty(value = "归属班级ID")
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
@ExcelProperty(value = "归属班级")
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
|
||||
+35
@@ -236,4 +236,39 @@ public class XgHolidayStayVo implements Serializable {
|
||||
@ExcelProperty(value = "备注信息")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
@ExcelProperty(value = "申请状态", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "xg_holiday_stay_apply_status")
|
||||
private String applyStatus;
|
||||
|
||||
@ExcelProperty(value = "提交申请时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date applyTime;
|
||||
|
||||
private Long auditorId;
|
||||
|
||||
@ExcelProperty(value = "审批人")
|
||||
private String auditorName;
|
||||
|
||||
@ExcelProperty(value = "审批时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date auditTime;
|
||||
|
||||
@ExcelProperty(value = "审批意见")
|
||||
private String auditOpinion;
|
||||
|
||||
private Long revokeBy;
|
||||
|
||||
@ExcelProperty(value = "撤销人")
|
||||
private String revokeByName;
|
||||
|
||||
@ExcelProperty(value = "撤销时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date revokeTime;
|
||||
|
||||
@ExcelProperty(value = "撤销原因")
|
||||
private String revokeReason;
|
||||
|
||||
}
|
||||
+7
-4
@@ -165,14 +165,17 @@ public interface XgDormBedStudentMapper extends BaseMapperPlus<XgDormBedStudent,
|
||||
b.bed_name,
|
||||
b.bed_code,
|
||||
|
||||
-- 班级信息
|
||||
c.class_name,
|
||||
-- 班级信息(优先班级表,回退学生表冗余字段)
|
||||
COALESCE(c.id, s.class_id) as class_id,
|
||||
COALESCE(c.class_name, s.class_name) as class_name,
|
||||
|
||||
-- 学院信息
|
||||
d.dept_name as college_name,
|
||||
COALESCE(c.college_id, s.college_id) as college_id,
|
||||
COALESCE(d.dept_name, s.college_name) as college_name,
|
||||
|
||||
-- 专业信息(通过班级关联)
|
||||
major.dept_name as major_name
|
||||
COALESCE(c.major_id, s.major_id) as major_id,
|
||||
COALESCE(major.dept_name, s.major_name) as major_name
|
||||
|
||||
FROM xg_dorm_bed_student bs
|
||||
INNER JOIN xg_dorm_bed b ON bs.bed_id = b.id AND b.deleted = '0'
|
||||
|
||||
+31
@@ -2,6 +2,7 @@ package org.dromara.xg.service;
|
||||
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayAuditBo;
|
||||
import org.dromara.xg.domain.vo.StudentOptionVo;
|
||||
import org.dromara.xg.domain.bo.StudentOptionBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
@@ -85,4 +86,34 @@ public interface IXgHolidayStayService {
|
||||
*/
|
||||
List<XgHolidayStayVo> querySinglePersonValidStay();
|
||||
|
||||
/**
|
||||
* 学生本人提交留宿申请(状态=待审)
|
||||
*/
|
||||
Boolean studentApply(XgHolidayStayBo bo);
|
||||
|
||||
/**
|
||||
* 当前学生的申请列表
|
||||
*/
|
||||
TableDataInfo<XgHolidayStayVo> queryMyApplyPage(XgHolidayStayBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 班主任本班申请列表(含待审)
|
||||
*/
|
||||
TableDataInfo<XgHolidayStayVo> queryTeacherPage(XgHolidayStayBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 班主任审批通过
|
||||
*/
|
||||
Boolean approve(XgHolidayStayAuditBo auditBo);
|
||||
|
||||
/**
|
||||
* 班主任驳回
|
||||
*/
|
||||
Boolean reject(XgHolidayStayAuditBo auditBo);
|
||||
|
||||
/**
|
||||
* 班主任撤销已通过申请
|
||||
*/
|
||||
Boolean revoke(XgHolidayStayAuditBo auditBo);
|
||||
|
||||
}
|
||||
+33
-17
@@ -2500,9 +2500,12 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
|
||||
studentInfo.put("studentNo", detailMap.get("student_no"));
|
||||
studentInfo.put("gender", detailMap.get("gender"));
|
||||
|
||||
// 学院、专业、班级信息
|
||||
// 学院、专业、班级信息(含 ID,供卫生检查归属班级等使用)
|
||||
studentInfo.put("collegeId", detailMap.get("college_id"));
|
||||
studentInfo.put("collegeName", detailMap.get("college_name"));
|
||||
studentInfo.put("majorId", detailMap.get("major_id"));
|
||||
studentInfo.put("majorName", detailMap.get("major_name"));
|
||||
studentInfo.put("classId", detailMap.get("class_id"));
|
||||
studentInfo.put("className", detailMap.get("class_name"));
|
||||
|
||||
// 床位信息
|
||||
@@ -2561,33 +2564,35 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 组装返回结果
|
||||
// 组装返回结果(兼容 Map 键为下划线或驼峰)
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
// 校区信息
|
||||
result.put("campusId", dormInfo.get("campus_id"));
|
||||
result.put("campusName", dormInfo.get("campus_name"));
|
||||
result.put("campusId", mapVal(dormInfo, "campusId", "campus_id"));
|
||||
result.put("campusName", mapVal(dormInfo, "campusName", "campus_name"));
|
||||
|
||||
// 宿舍区信息
|
||||
result.put("areaId", dormInfo.get("area_id"));
|
||||
result.put("areaName", dormInfo.get("area_name"));
|
||||
result.put("areaId", mapVal(dormInfo, "areaId", "area_id"));
|
||||
result.put("areaName", mapVal(dormInfo, "areaName", "area_name"));
|
||||
|
||||
// 楼栋信息
|
||||
result.put("buildingId", dormInfo.get("building_id"));
|
||||
result.put("buildingName", dormInfo.get("building_name"));
|
||||
result.put("buildingCode", dormInfo.get("building_code"));
|
||||
result.put("buildingId", mapVal(dormInfo, "buildingId", "building_id"));
|
||||
result.put("buildingName", mapVal(dormInfo, "buildingName", "building_name"));
|
||||
result.put("buildingCode", mapVal(dormInfo, "buildingCode", "building_code"));
|
||||
|
||||
// 房间信息
|
||||
result.put("roomId", dormInfo.get("room_id"));
|
||||
result.put("roomName", dormInfo.get("room_name"));
|
||||
result.put("roomNo", dormInfo.get("room_no"));
|
||||
result.put("roomNumber", dormInfo.get("room_no")); // 兼容字段
|
||||
result.put("roomId", mapVal(dormInfo, "roomId", "room_id"));
|
||||
result.put("roomName", mapVal(dormInfo, "roomName", "room_name"));
|
||||
Object roomNo = mapVal(dormInfo, "roomNo", "room_no");
|
||||
result.put("roomNo", roomNo);
|
||||
result.put("roomNumber", roomNo); // 兼容字段
|
||||
|
||||
// 床位信息
|
||||
result.put("bedId", dormInfo.get("bed_id"));
|
||||
result.put("bedName", dormInfo.get("bed_name"));
|
||||
result.put("bedNo", dormInfo.get("bed_no"));
|
||||
result.put("bedNumber", dormInfo.get("bed_no")); // 兼容字段
|
||||
result.put("bedId", mapVal(dormInfo, "bedId", "bed_id"));
|
||||
result.put("bedName", mapVal(dormInfo, "bedName", "bed_name"));
|
||||
Object bedNo = mapVal(dormInfo, "bedNo", "bed_no");
|
||||
result.put("bedNo", bedNo);
|
||||
result.put("bedNumber", bedNo); // 兼容字段
|
||||
|
||||
log.info("获取学生当前宿舍信息成功,学生ID: {}, 宿舍信息: {}校区-{}楼-{}房间-{}床位",
|
||||
studentId,
|
||||
@@ -2604,6 +2609,17 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object mapVal(Map<String, Object> map, String camelKey, String snakeKey) {
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
Object val = map.get(camelKey);
|
||||
if (val == null) {
|
||||
val = map.get(snakeKey);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据床位ID获取该床位的学生信息
|
||||
* 包含学生基本信息、学院、专业、班级等信息
|
||||
|
||||
+224
@@ -11,12 +11,21 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayAuditBo;
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.XgHolidayStay;
|
||||
import org.dromara.xg.domain.bo.StudentOptionBo;
|
||||
import org.dromara.xg.domain.vo.StudentOptionVo;
|
||||
import org.dromara.xg.domain.vo.XgStudentMsgVo;
|
||||
import org.dromara.xg.domain.vo.XgClassMsgVo;
|
||||
import org.dromara.xg.domain.bo.XgClassMsgBo;
|
||||
import org.dromara.xg.mapper.XgHolidayStayMapper;
|
||||
import org.dromara.xg.service.IXgHolidayStayService;
|
||||
import org.dromara.xg.service.IXgStudentMsgService;
|
||||
import org.dromara.xg.service.IXgClassMsgService;
|
||||
import org.dromara.xg.service.IXgDormBedStudentService;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -44,6 +53,9 @@ import org.springframework.beans.BeanUtils;
|
||||
public class XgHolidayStayServiceImpl implements IXgHolidayStayService {
|
||||
|
||||
private final XgHolidayStayMapper baseMapper;
|
||||
private final IXgStudentMsgService xgStudentMsgService;
|
||||
private final IXgClassMsgService xgClassMsgService;
|
||||
private final IXgDormBedStudentService xgDormBedStudentService;
|
||||
|
||||
/**
|
||||
* 查询假期留宿管理
|
||||
@@ -154,6 +166,9 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService {
|
||||
// 添加创建人过滤条件 - 实现班主任权限控制,只能查看自己创建的数据
|
||||
lqw.eq(bo.getCreateBy() != null, XgHolidayStay::getCreateBy, bo.getCreateBy());
|
||||
|
||||
// 申请审批状态
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getApplyStatus()), XgHolidayStay::getApplyStatus, bo.getApplyStatus());
|
||||
|
||||
// 添加状态过滤逻辑
|
||||
addStatusFilter(lqw, bo);
|
||||
|
||||
@@ -201,6 +216,10 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService {
|
||||
@Override
|
||||
public Boolean insertByBo(XgHolidayStayBo bo) {
|
||||
XgHolidayStay add = MapstructUtils.convert(bo, XgHolidayStay.class);
|
||||
// 宿管/班主任直接登记:默认已通过;学生申请走 studentApply
|
||||
if (StringUtils.isBlank(add.getApplyStatus())) {
|
||||
add.setApplyStatus("2");
|
||||
}
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
@@ -755,4 +774,209 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService {
|
||||
return originalStay;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean studentApply(XgHolidayStayBo bo) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
XgStudentMsgVo student = xgStudentMsgService.queryByUserId(userId);
|
||||
if (student == null || student.getId() == null) {
|
||||
throw new ServiceException("未找到当前登录学生信息,无法申请留宿");
|
||||
}
|
||||
fillStudentSnapshot(bo, student);
|
||||
fillDormSnapshot(bo, student.getId());
|
||||
if (bo.getBedId() == null || bo.getRoomId() == null) {
|
||||
throw new ServiceException("未查询到当前在住宿舍/床位信息,请先完成床位分配后再申请留宿");
|
||||
}
|
||||
bo.setApplyStatus("1");
|
||||
bo.setApplyTime(new Date());
|
||||
bo.setAuditorId(null);
|
||||
bo.setAuditorName(null);
|
||||
bo.setAuditTime(null);
|
||||
bo.setAuditOpinion(null);
|
||||
bo.setRevokeBy(null);
|
||||
bo.setRevokeByName(null);
|
||||
bo.setRevokeTime(null);
|
||||
bo.setRevokeReason(null);
|
||||
return insertByBo(bo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<XgHolidayStayVo> queryMyApplyPage(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
XgStudentMsgVo student = xgStudentMsgService.queryByUserId(userId);
|
||||
if (student == null || student.getId() == null) {
|
||||
return TableDataInfo.build(new Page<>());
|
||||
}
|
||||
if (bo == null) {
|
||||
bo = new XgHolidayStayBo();
|
||||
}
|
||||
bo.setStudentId(student.getId());
|
||||
return queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<XgHolidayStayVo> queryTeacherPage(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
List<Long> classIds = listMyClassIdsAsTeacher();
|
||||
if (classIds.isEmpty()) {
|
||||
return TableDataInfo.build(new Page<>());
|
||||
}
|
||||
if (bo == null) {
|
||||
bo = new XgHolidayStayBo();
|
||||
}
|
||||
// 指定班级时必须是本班
|
||||
if (bo.getClassId() != null && !classIds.contains(bo.getClassId())) {
|
||||
return TableDataInfo.build(new Page<>());
|
||||
}
|
||||
LambdaQueryWrapper<XgHolidayStay> lqw = buildQueryWrapper(bo);
|
||||
if (bo.getClassId() == null) {
|
||||
lqw.in(XgHolidayStay::getClassId, classIds);
|
||||
}
|
||||
lqw.orderByDesc(XgHolidayStay::getApplyTime).orderByDesc(XgHolidayStay::getId);
|
||||
Page<XgHolidayStayVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean approve(XgHolidayStayAuditBo auditBo) {
|
||||
XgHolidayStay entity = requireTeacherPendingRecord(auditBo.getId());
|
||||
entity.setApplyStatus("2");
|
||||
entity.setAuditorId(LoginHelper.getUserId());
|
||||
entity.setAuditorName(LoginHelper.getNickname());
|
||||
entity.setAuditTime(new Date());
|
||||
entity.setAuditOpinion(auditBo.getAuditOpinion());
|
||||
return baseMapper.updateById(entity) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean reject(XgHolidayStayAuditBo auditBo) {
|
||||
XgHolidayStay entity = requireTeacherPendingRecord(auditBo.getId());
|
||||
entity.setApplyStatus("3");
|
||||
entity.setAuditorId(LoginHelper.getUserId());
|
||||
entity.setAuditorName(LoginHelper.getNickname());
|
||||
entity.setAuditTime(new Date());
|
||||
entity.setAuditOpinion(auditBo.getAuditOpinion());
|
||||
return baseMapper.updateById(entity) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean revoke(XgHolidayStayAuditBo auditBo) {
|
||||
if (StringUtils.isBlank(auditBo.getRevokeReason())) {
|
||||
throw new ServiceException("撤销原因不能为空");
|
||||
}
|
||||
XgHolidayStay entity = requireTeacherOwnedRecord(auditBo.getId());
|
||||
if (!"2".equals(entity.getApplyStatus())) {
|
||||
throw new ServiceException("仅已通过的申请可撤销");
|
||||
}
|
||||
entity.setApplyStatus("4");
|
||||
entity.setRevokeBy(LoginHelper.getUserId());
|
||||
entity.setRevokeByName(LoginHelper.getNickname());
|
||||
entity.setRevokeTime(new Date());
|
||||
entity.setRevokeReason(auditBo.getRevokeReason());
|
||||
return baseMapper.updateById(entity) > 0;
|
||||
}
|
||||
|
||||
private void fillStudentSnapshot(XgHolidayStayBo bo, XgStudentMsgVo student) {
|
||||
bo.setStudentId(student.getId());
|
||||
bo.setStudentName(student.getName());
|
||||
bo.setStudentNo(student.getStudentNo());
|
||||
if (StringUtils.isNotBlank(student.getClassId())) {
|
||||
try {
|
||||
bo.setClassId(Long.valueOf(student.getClassId()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
bo.setClassName(student.getClassName());
|
||||
if (StringUtils.isNotBlank(student.getMajorId())) {
|
||||
try {
|
||||
bo.setMajorId(Long.valueOf(student.getMajorId()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
bo.setMajorName(student.getMajorName());
|
||||
if (StringUtils.isNotBlank(student.getCollegeId())) {
|
||||
try {
|
||||
bo.setCollegeId(Long.valueOf(student.getCollegeId()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
bo.setCollegeName(student.getCollegeName());
|
||||
if (StringUtils.isBlank(bo.getStudentPhone())) {
|
||||
bo.setStudentPhone(student.getPhone());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void fillDormSnapshot(XgHolidayStayBo bo, Long studentId) {
|
||||
Object dormObj = xgDormBedStudentService.getCurrentDormInfo(studentId);
|
||||
if (!(dormObj instanceof Map)) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> dorm = (Map<String, Object>) dormObj;
|
||||
bo.setCampusId(toLong(dorm.get("campusId")));
|
||||
bo.setCampusName(toStr(dorm.get("campusName")));
|
||||
bo.setAreaId(toLong(dorm.get("areaId")));
|
||||
bo.setAreaName(toStr(dorm.get("areaName")));
|
||||
bo.setBuildingId(toLong(dorm.get("buildingId")));
|
||||
bo.setBuildingName(toStr(dorm.get("buildingName")));
|
||||
bo.setRoomId(toLong(dorm.get("roomId")));
|
||||
bo.setRoomName(toStr(dorm.get("roomName")));
|
||||
bo.setBedId(toLong(dorm.get("bedId")));
|
||||
Object bedName = dorm.get("bedName");
|
||||
if (bedName == null) {
|
||||
bedName = dorm.get("bedNo");
|
||||
}
|
||||
bo.setBedName(toStr(bedName));
|
||||
}
|
||||
|
||||
private List<Long> listMyClassIdsAsTeacher() {
|
||||
XgClassMsgBo classBo = new XgClassMsgBo();
|
||||
classBo.setClassTeacherId(LoginHelper.getUserId());
|
||||
classBo.setClassStatus("1");
|
||||
List<XgClassMsgVo> classes = xgClassMsgService.queryList(classBo);
|
||||
if (classes == null || classes.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return classes.stream().map(XgClassMsgVo::getId).filter(id -> id != null).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private XgHolidayStay requireTeacherPendingRecord(Long id) {
|
||||
XgHolidayStay entity = requireTeacherOwnedRecord(id);
|
||||
if (!"1".equals(entity.getApplyStatus())) {
|
||||
throw new ServiceException("仅待审申请可审批");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private XgHolidayStay requireTeacherOwnedRecord(Long id) {
|
||||
XgHolidayStay entity = baseMapper.selectById(id);
|
||||
if (entity == null || (entity.getDeleted() != null && entity.getDeleted() != 0L)) {
|
||||
throw new ServiceException("申请记录不存在");
|
||||
}
|
||||
List<Long> classIds = listMyClassIdsAsTeacher();
|
||||
if (entity.getClassId() == null || !classIds.contains(entity.getClassId())) {
|
||||
throw new ServiceException("无权操作非本班学生的留宿申请");
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
private Long toLong(Object val) {
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
if (val instanceof Number) {
|
||||
return ((Number) val).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(val.toString());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String toStr(Object val) {
|
||||
return val == null ? null : val.toString();
|
||||
}
|
||||
}
|
||||
+6
@@ -107,6 +107,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="campusName != null and campusName != ''">
|
||||
AND dhc.campus_name LIKE CONCAT('%', #{campusName}, '%')
|
||||
</if>
|
||||
<if test="classId != null">
|
||||
AND dhc.class_id = #{classId}
|
||||
</if>
|
||||
<if test="className != null and className != ''">
|
||||
AND dhc.class_name LIKE CONCAT('%', #{className}, '%')
|
||||
</if>
|
||||
<if test="hygieneGrade != null and hygieneGrade != ''">
|
||||
AND dhc.hygiene_grade = #{hygieneGrade}
|
||||
</if>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- [宿舍管理员授权API](./宿舍管理员授权API.md)
|
||||
- [学生管理员授权关系API](./学生管理员授权关系API.md)
|
||||
- [学生销假管理API](./学生销假管理API.md)
|
||||
- [假期留宿管理API](./假期留宿管理API.md) - **重要修复:单人留宿计算逻辑**(2025-01-03)
|
||||
- [假期留宿管理API](./假期留宿管理API.md) - **申请/班主任一级审批/撤销/导出**(2026-08-04)
|
||||
- [均化处理最高分值API](./均化处理最高分值API.md) - **新增**:支持考核申请均化处理功能(2025-01-03)
|
||||
|
||||
### 1. 用户认证登录
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
- 单项分数必须在对应类型的 min-max 范围内
|
||||
- 合计扣分上限 100 分
|
||||
- 历史单条数据迁移:以主表 `total_score` 作为唯一明细的 score
|
||||
- **归属班级(classId/className)必填**:选项仅来自当前寝室在住学生的班级(去重);默认选中第一项;用于宿舍扣分归属统计
|
||||
- **班级选项数据来源**:`GET /dormBedStudent/dormBedStudent/getRoomStudents` 须返回 `classId`、`className`(以及 collegeId/majorId);前端按 `classId` 去重生成下拉,缺 `classId` 时会出现「当前寝室无在住学生班级」
|
||||
|
||||
## API接口规范
|
||||
|
||||
@@ -59,6 +61,8 @@
|
||||
"areaName": "东区",
|
||||
"campusId": 1,
|
||||
"campusName": "主校区",
|
||||
"classId": 10001,
|
||||
"className": "计算机应用技术2301班",
|
||||
"totalScore": 85.5,
|
||||
"deductionItems": [
|
||||
{
|
||||
@@ -315,6 +319,7 @@ public interface DormHygieneStudentScoreMapper extends BaseMapperPlus<DormHygien
|
||||
- `checkDate`: 必填,日期时间格式
|
||||
- `checkerId`: 必填,关联用户表
|
||||
- `roomId`: 必填,关联房间表
|
||||
- `classId` / `className`: **必填**,宿舍扣分归属班级;选项须为当前寝室在住学生班级
|
||||
- `totalScore`: 可选,0-100分(由 deductionItems 合计自动计算,只读)
|
||||
- `deductionItems`: 可选,扣分明细数组;每项 score 默认 minScore,可在类型 min-max 内调整;合计不超过100
|
||||
- `hygieneGrade`: 可选,A/B/C/D
|
||||
|
||||
@@ -4376,6 +4376,8 @@ CREATE TABLE `xg_dorm_hygiene_check` (
|
||||
`area_name` varchar(100) DEFAULT NULL COMMENT '宿舍区名称',
|
||||
`campus_id` bigint(20) DEFAULT NULL COMMENT '校区ID,关联xg_dorm_campus.id',
|
||||
`campus_name` varchar(100) DEFAULT NULL COMMENT '校区名称',
|
||||
`class_id` bigint(20) DEFAULT NULL COMMENT '归属班级ID(宿舍扣分归属)',
|
||||
`class_name` varchar(100) DEFAULT NULL COMMENT '归属班级名称',
|
||||
`total_score` decimal(5,2) DEFAULT NULL COMMENT '宿舍扣分(0-100分)',
|
||||
`hygiene_grade` char(1) DEFAULT NULL COMMENT '卫生等级(A优秀 B良好 C合格 D不合格)',
|
||||
`attachment_ids` text COMMENT '附件OSS ID列表,逗号分隔',
|
||||
@@ -4388,6 +4390,7 @@ CREATE TABLE `xg_dorm_hygiene_check` (
|
||||
KEY `idx_tenant_id` (`tenant_id`),
|
||||
KEY `idx_deleted` (`deleted`),
|
||||
KEY `idx_check_date` (`check_date`),
|
||||
KEY `idx_class_id` (`class_id`),
|
||||
KEY `idx_checker_id` (`checker_id`),
|
||||
KEY `idx_room_id` (`room_id`),
|
||||
KEY `idx_building_id` (`building_id`),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
allowBuilds:
|
||||
'@parcel/watcher': set this to true or false
|
||||
core-js: set this to true or false
|
||||
fsevents: set this to true or false
|
||||
puppeteer: set this to true or false
|
||||
@@ -0,0 +1,79 @@
|
||||
import { request } from '@/common/request.js';
|
||||
|
||||
/** 学生提交留宿申请 */
|
||||
export const applyHolidayStay = (data) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/apply',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 当前学生申请列表 */
|
||||
export const listMyHolidayStay = (query) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/myList',
|
||||
method: 'GET',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/** 班主任本班申请列表 */
|
||||
export const listTeacherHolidayStay = (query) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/teacherList',
|
||||
method: 'GET',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/** 审批通过 */
|
||||
export const approveHolidayStay = (data) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/approve',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 驳回 */
|
||||
export const rejectHolidayStay = (data) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/reject',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 撤销已通过申请 */
|
||||
export const revokeHolidayStay = (data) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/revoke',
|
||||
method: 'POST',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 详情 */
|
||||
export const getHolidayStay = (id) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/' + id,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/** 获取学生当前在住宿舍信息 */
|
||||
export const getCurrentDormInfo = (studentId) => {
|
||||
return request({
|
||||
url: '/dormBedStudent/dormBedStudent/currentDormInfo/' + studentId,
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
/** 当前登录学生信息 */
|
||||
export const getCurrentStudentInfo = () => {
|
||||
return request({
|
||||
url: '/studentMsg/studentMsg/currentUserInfo',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
@@ -207,6 +207,30 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/jqzs/myList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的留宿申请",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/jqzs/apply",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请假期留宿",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/jqzs/teacherList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "留宿审批",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/ssdm/ssdm",
|
||||
"style": {
|
||||
|
||||
@@ -111,6 +111,26 @@
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['holidayStay:holidayStay:apply'])"
|
||||
title="假期留宿申请"
|
||||
@click="gotoHolidayStayApply"
|
||||
>
|
||||
<template #icon>
|
||||
<u-icon name="home" size="24" color="#3c9cff" />
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['holidayStay:holidayStay:list1'])"
|
||||
title="假期留宿审批"
|
||||
@click="gotoHolidayStayAudit"
|
||||
>
|
||||
<template #icon>
|
||||
<u-icon name="checkmark-circle" size="24" color="#3c9cff" />
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<!-- <ServiceItem
|
||||
v-if="hasPermi(['student:psychology:list'])"
|
||||
title="心理健康"
|
||||
@@ -1183,6 +1203,20 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到假期留宿申请(学生)
|
||||
gotoHolidayStayApply() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xgss/jqzs/myList',
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到假期留宿审批(班主任)
|
||||
gotoHolidayStayAudit() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xgss/jqzs/teacherList',
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到实习管理页面
|
||||
gotoInternshipManage() {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<u-navbar title="申请假期留宿" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<view class="form-wrap">
|
||||
<view class="dorm-card">
|
||||
<view class="dorm-title">当前宿舍(自动带出)</view>
|
||||
<view v-if="dormLoading" class="dorm-text">加载中...</view>
|
||||
<view v-else-if="dormText" class="dorm-text">{{ dormText }}</view>
|
||||
<view v-else class="dorm-text dorm-warn">未查询到在住床位,请先完成宿舍床位分配后再申请</view>
|
||||
</view>
|
||||
|
||||
<u-form :model="form" ref="formRef" label-width="90">
|
||||
<u-form-item label="假期类型" prop="holidayType" required>
|
||||
<view @click="openHolidayPicker" style="width: 100%">
|
||||
<u-input
|
||||
v-model="holidayTypeLabel"
|
||||
placeholder="请选择假期类型"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="开始时间" prop="applyStartDate" required>
|
||||
<view @click="openDate('start')" style="width: 100%">
|
||||
<u-input
|
||||
v-model="form.applyStartDate"
|
||||
placeholder="请选择开始时间"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="结束时间" prop="applyEndDate" required>
|
||||
<view @click="openDate('end')" style="width: 100%">
|
||||
<u-input
|
||||
v-model="form.applyEndDate"
|
||||
placeholder="请选择结束时间"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="原因类型" prop="stayReasonType">
|
||||
<view @click="openReasonPicker" style="width: 100%">
|
||||
<u-input
|
||||
v-model="stayReasonTypeLabel"
|
||||
placeholder="请选择原因类型"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="留宿原因" prop="stayReason" required>
|
||||
<u-textarea
|
||||
v-model="form.stayReason"
|
||||
placeholder="请填写留宿原因"
|
||||
maxlength="500"
|
||||
count
|
||||
></u-textarea>
|
||||
</u-form-item>
|
||||
<u-form-item label="联系电话" prop="studentPhone">
|
||||
<u-input v-model="form.studentPhone" placeholder="可自动带出,可修改" />
|
||||
</u-form-item>
|
||||
<u-form-item label="紧急联系人" prop="emergencyContact">
|
||||
<u-input v-model="form.emergencyContact" placeholder="请输入紧急联系人" />
|
||||
</u-form-item>
|
||||
<u-form-item label="紧急电话" prop="emergencyPhone">
|
||||
<u-input v-model="form.emergencyPhone" placeholder="请输入紧急联系电话" />
|
||||
</u-form-item>
|
||||
<u-form-item label="家长同意" prop="parentConsent">
|
||||
<u-radio-group v-model="form.parentConsent" class="radio-group">
|
||||
<u-radio label="1" name="1" class="radio-item">是</u-radio>
|
||||
<u-radio label="0" name="0" class="radio-item">否</u-radio>
|
||||
</u-radio-group>
|
||||
</u-form-item>
|
||||
<u-form-item label="备注" prop="remark">
|
||||
<u-textarea v-model="form.remark" placeholder="选填" maxlength="200"></u-textarea>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
|
||||
<u-button type="primary" :loading="submitting" text="提交申请" @click="submit"></u-button>
|
||||
<view class="tip">提交后由班主任一级审批;宿舍信息将按当前入住床位自动带出。</view>
|
||||
</view>
|
||||
|
||||
<u-picker
|
||||
:show="showHolidayPicker"
|
||||
:columns="holidayColumns"
|
||||
keyName="label"
|
||||
@confirm="onHolidayConfirm"
|
||||
@cancel="showHolidayPicker = false"
|
||||
@close="showHolidayPicker = false"
|
||||
></u-picker>
|
||||
|
||||
<u-picker
|
||||
:show="showReasonPicker"
|
||||
:columns="reasonColumns"
|
||||
keyName="label"
|
||||
@confirm="onReasonConfirm"
|
||||
@cancel="showReasonPicker = false"
|
||||
@close="showReasonPicker = false"
|
||||
></u-picker>
|
||||
|
||||
<u-datetime-picker
|
||||
:show="showDatePicker"
|
||||
v-model="dateValue"
|
||||
mode="datetime"
|
||||
@confirm="onDateConfirm"
|
||||
@cancel="showDatePicker = false"
|
||||
@close="showDatePicker = false"
|
||||
></u-datetime-picker>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { applyHolidayStay } from '@/api/holidayStay.js';
|
||||
import { getDict } from '@/api/system/dict/data.js';
|
||||
import { request } from '@/common/request.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
submitting: false,
|
||||
showHolidayPicker: false,
|
||||
showReasonPicker: false,
|
||||
showDatePicker: false,
|
||||
dateField: 'start',
|
||||
dateValue: Number(new Date()),
|
||||
holidayTypeDict: [],
|
||||
stayReasonTypeDict: [],
|
||||
holidayTypeLabel: '',
|
||||
stayReasonTypeLabel: '',
|
||||
dormLoading: false,
|
||||
dormText: '',
|
||||
hasDorm: false,
|
||||
studentId: null,
|
||||
form: {
|
||||
holidayType: '',
|
||||
applyStartDate: '',
|
||||
applyEndDate: '',
|
||||
stayReasonType: 'other',
|
||||
stayReason: '',
|
||||
studentPhone: '',
|
||||
emergencyContact: '',
|
||||
emergencyPhone: '',
|
||||
parentConsent: '1',
|
||||
remark: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
holidayColumns() {
|
||||
return [this.holidayTypeDict || []];
|
||||
},
|
||||
reasonColumns() {
|
||||
return [this.stayReasonTypeDict || []];
|
||||
}
|
||||
},
|
||||
async onLoad() {
|
||||
try {
|
||||
this.holidayTypeDict = (await getDict('xg_holiday_type')) || [];
|
||||
this.stayReasonTypeDict = (await getDict('xg_stay_reason_type')) || [];
|
||||
const reason = this.stayReasonTypeDict.find((d) => String(d.value) === 'other');
|
||||
if (reason) {
|
||||
this.stayReasonTypeLabel = reason.label;
|
||||
this.form.stayReasonType = reason.value;
|
||||
}
|
||||
const stuRes = await request({
|
||||
url: '/studentMsg/studentMsg/currentUserInfo',
|
||||
method: 'GET'
|
||||
});
|
||||
const stu = stuRes.data || stuRes;
|
||||
if (stu && stu.phone) {
|
||||
this.form.studentPhone = stu.phone;
|
||||
}
|
||||
if (stu && stu.id) {
|
||||
this.studentId = stu.id;
|
||||
await this.loadDormInfo(stu.id);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
async loadDormInfo(studentId) {
|
||||
this.dormLoading = true;
|
||||
this.hasDorm = false;
|
||||
this.dormText = '';
|
||||
try {
|
||||
const res = await request({
|
||||
url: `/dormBedStudent/dormBedStudent/currentDormInfo/${studentId}`,
|
||||
method: 'GET'
|
||||
});
|
||||
const dorm = res.data || res;
|
||||
if (dorm && (dorm.roomId || dorm.bedId || dorm.buildingName || dorm.roomName)) {
|
||||
const parts = [
|
||||
dorm.campusName,
|
||||
dorm.buildingName,
|
||||
dorm.roomName || dorm.roomNo,
|
||||
dorm.bedName || dorm.bedNo
|
||||
].filter(Boolean);
|
||||
this.dormText = parts.join(' / ');
|
||||
this.hasDorm = !!(dorm.roomId && dorm.bedId);
|
||||
}
|
||||
} catch (e) {
|
||||
this.dormText = '';
|
||||
this.hasDorm = false;
|
||||
} finally {
|
||||
this.dormLoading = false;
|
||||
}
|
||||
},
|
||||
openHolidayPicker() {
|
||||
if (!this.holidayTypeDict.length) {
|
||||
uni.showToast({ title: '假期类型加载中,请稍后', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.showHolidayPicker = true;
|
||||
},
|
||||
openReasonPicker() {
|
||||
if (!this.stayReasonTypeDict.length) {
|
||||
uni.showToast({ title: '原因类型加载中,请稍后', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.showReasonPicker = true;
|
||||
},
|
||||
openDate(field) {
|
||||
this.dateField = field;
|
||||
const current = field === 'start' ? this.form.applyStartDate : this.form.applyEndDate;
|
||||
this.dateValue = current ? new Date(current.replace(/-/g, '/')).getTime() : Number(new Date());
|
||||
this.showDatePicker = true;
|
||||
},
|
||||
onHolidayConfirm(e) {
|
||||
const item = (e.value && e.value[0]) || null;
|
||||
if (item) {
|
||||
this.form.holidayType = item.value;
|
||||
this.holidayTypeLabel = item.label;
|
||||
}
|
||||
this.showHolidayPicker = false;
|
||||
},
|
||||
onReasonConfirm(e) {
|
||||
const item = (e.value && e.value[0]) || null;
|
||||
if (item) {
|
||||
this.form.stayReasonType = item.value;
|
||||
this.stayReasonTypeLabel = item.label;
|
||||
}
|
||||
this.showReasonPicker = false;
|
||||
},
|
||||
onDateConfirm(e) {
|
||||
const ts = e && e.value !== undefined ? e.value : e;
|
||||
const d = new Date(Number(ts));
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
this.showDatePicker = false;
|
||||
return;
|
||||
}
|
||||
const pad = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
const str =
|
||||
d.getFullYear() +
|
||||
'-' +
|
||||
pad(d.getMonth() + 1) +
|
||||
'-' +
|
||||
pad(d.getDate()) +
|
||||
' ' +
|
||||
pad(d.getHours()) +
|
||||
':' +
|
||||
pad(d.getMinutes()) +
|
||||
':' +
|
||||
pad(d.getSeconds());
|
||||
if (this.dateField === 'start') {
|
||||
this.form.applyStartDate = str;
|
||||
} else {
|
||||
this.form.applyEndDate = str;
|
||||
}
|
||||
this.showDatePicker = false;
|
||||
},
|
||||
async submit() {
|
||||
if (!this.hasDorm) {
|
||||
return uni.showToast({ title: '未分配宿舍床位,无法申请', icon: 'none' });
|
||||
}
|
||||
if (!this.form.holidayType) {
|
||||
return uni.showToast({ title: '请选择假期类型', icon: 'none' });
|
||||
}
|
||||
if (!this.form.applyStartDate || !this.form.applyEndDate) {
|
||||
return uni.showToast({ title: '请选择留宿时间', icon: 'none' });
|
||||
}
|
||||
if (!this.form.stayReason || !this.form.stayReason.trim()) {
|
||||
return uni.showToast({ title: '请填写留宿原因', icon: 'none' });
|
||||
}
|
||||
this.submitting = true;
|
||||
try {
|
||||
await applyHolidayStay({ ...this.form });
|
||||
uni.showToast({ title: '提交成功', icon: 'success' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 500);
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.msg) || '提交失败', icon: 'none' });
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
.form-wrap {
|
||||
margin: 24rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.tip {
|
||||
margin-top: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.dorm-card {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 20rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.dorm-title {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.dorm-text {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
.dorm-warn {
|
||||
color: #e6a23c;
|
||||
}
|
||||
.radio-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.radio-item {
|
||||
margin-right: 32rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<u-navbar title="我的留宿申请" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<view class="filter-bar">
|
||||
<u-tabs
|
||||
:list="statusTabs"
|
||||
:current="tabIndex"
|
||||
@change="onTabChange"
|
||||
line-color="#3c9cff"
|
||||
></u-tabs>
|
||||
</view>
|
||||
|
||||
<u-loading-page :loading="loading && list.length === 0" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<view v-if="!loading || list.length > 0" class="content">
|
||||
<u-empty v-if="list.length === 0" text="暂无申请记录" mode="data"></u-empty>
|
||||
<view v-else class="card-list">
|
||||
<view v-for="(item, index) in list" :key="item.id || index" class="card">
|
||||
<view class="card-header">
|
||||
<text class="title">{{ holidayTypeLabel(item.holidayType) }}</text>
|
||||
<u-tag
|
||||
:text="applyStatusLabel(item.applyStatus)"
|
||||
:type="applyStatusType(item.applyStatus)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="row"><text class="label">宿舍:</text><text>{{ item.buildingName }}{{ item.roomName }} {{ item.bedName }}</text></view>
|
||||
<view class="row"><text class="label">时间:</text><text>{{ formatDate(item.applyStartDate) }} ~ {{ formatDate(item.applyEndDate) }}</text></view>
|
||||
<view class="row"><text class="label">原因:</text><text>{{ item.stayReason || '-' }}</text></view>
|
||||
<view class="row" v-if="item.auditOpinion"><text class="label">意见:</text><text>{{ item.auditOpinion }}</text></view>
|
||||
<view class="row" v-if="item.revokeReason"><text class="label">撤销:</text><text>{{ item.revokeReason }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="load-more" v-if="list.length > 0">
|
||||
<u-loadmore :status="loadStatus" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="fab">
|
||||
<u-button type="primary" shape="circle" text="申请留宿" @click="gotoApply"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listMyHolidayStay } from '@/api/holidayStay.js';
|
||||
import { getDict } from '@/api/system/dict/data.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
loadStatus: 'loadmore',
|
||||
tabIndex: 0,
|
||||
applyStatus: '',
|
||||
statusTabs: [
|
||||
{ name: '全部', value: '' },
|
||||
{ name: '待审', value: '1' },
|
||||
{ name: '已通过', value: '2' },
|
||||
{ name: '已驳回', value: '3' },
|
||||
{ name: '已撤销', value: '4' }
|
||||
],
|
||||
holidayTypeDict: [],
|
||||
applyStatusDict: []
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.refresh();
|
||||
},
|
||||
onReachBottom() {
|
||||
this.loadMore();
|
||||
},
|
||||
async onLoad() {
|
||||
try {
|
||||
this.holidayTypeDict = (await getDict('xg_holiday_type')) || [];
|
||||
this.applyStatusDict = (await getDict('xg_holiday_stay_apply_status')) || [];
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) });
|
||||
},
|
||||
gotoApply() {
|
||||
uni.navigateTo({ url: '/pages/xgss/jqzs/apply' });
|
||||
},
|
||||
onTabChange(item) {
|
||||
const idx = typeof item === 'number' ? item : item.index;
|
||||
this.tabIndex = idx;
|
||||
this.applyStatus = this.statusTabs[idx].value;
|
||||
this.refresh();
|
||||
},
|
||||
async refresh() {
|
||||
this.pageNum = 1;
|
||||
this.list = [];
|
||||
await this.fetchList();
|
||||
},
|
||||
async loadMore() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.pageNum += 1;
|
||||
await this.fetchList(true);
|
||||
},
|
||||
async fetchList(append = false) {
|
||||
this.loading = true;
|
||||
this.loadStatus = 'loading';
|
||||
try {
|
||||
const res = await listMyHolidayStay({
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
applyStatus: this.applyStatus || undefined
|
||||
});
|
||||
const rows = res.rows || [];
|
||||
this.total = res.total || 0;
|
||||
this.list = append ? this.list.concat(rows) : rows;
|
||||
this.loadStatus = this.list.length >= this.total ? 'nomore' : 'loadmore';
|
||||
} catch (e) {
|
||||
this.loadStatus = 'loadmore';
|
||||
uni.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
holidayTypeLabel(val) {
|
||||
const hit = this.holidayTypeDict.find((d) => String(d.value) === String(val));
|
||||
return (hit && hit.label) || val || '假期留宿';
|
||||
},
|
||||
applyStatusLabel(val) {
|
||||
const hit = this.applyStatusDict.find((d) => String(d.value) === String(val));
|
||||
return (hit && hit.label) || val || '-';
|
||||
},
|
||||
applyStatusType(val) {
|
||||
const map = { '0': 'info', '1': 'warning', '2': 'success', '3': 'error', '4': 'info' };
|
||||
return map[String(val)] || 'info';
|
||||
},
|
||||
formatDate(v) {
|
||||
if (!v) return '-';
|
||||
return String(v).replace('T', ' ').slice(0, 16);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
.filter-bar {
|
||||
background: #fff;
|
||||
}
|
||||
.content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
.card-list .card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.row {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-top: 8rpx;
|
||||
display: flex;
|
||||
}
|
||||
.label {
|
||||
color: #909399;
|
||||
min-width: 90rpx;
|
||||
}
|
||||
.fab {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 40rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<u-navbar title="留宿审批" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<view class="filter-bar">
|
||||
<u-tabs
|
||||
:list="statusTabs"
|
||||
:current="tabIndex"
|
||||
@change="onTabChange"
|
||||
line-color="#3c9cff"
|
||||
></u-tabs>
|
||||
</view>
|
||||
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="studentName"
|
||||
placeholder="学生姓名"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
@search="refresh"
|
||||
@custom="refresh"
|
||||
@clear="refresh"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<u-loading-page :loading="loading && list.length === 0" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<view v-if="!loading || list.length > 0" class="content">
|
||||
<u-empty v-if="list.length === 0" text="暂无本班申请" mode="data"></u-empty>
|
||||
<view v-else class="card-list">
|
||||
<view v-for="(item, index) in list" :key="item.id || index" class="card">
|
||||
<view class="card-header">
|
||||
<view>
|
||||
<text class="name">{{ item.studentName }}</text>
|
||||
<text class="no">{{ item.studentNo }}</text>
|
||||
</view>
|
||||
<u-tag
|
||||
:text="applyStatusLabel(item.applyStatus)"
|
||||
:type="applyStatusType(item.applyStatus)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="row">班级:{{ item.className || '-' }}</view>
|
||||
<view class="row">宿舍:{{ item.buildingName }}{{ item.roomName }} {{ item.bedName }}</view>
|
||||
<view class="row">时间:{{ formatDate(item.applyStartDate) }} ~ {{ formatDate(item.applyEndDate) }}</view>
|
||||
<view class="row">原因:{{ item.stayReason || '-' }}</view>
|
||||
<view class="actions" v-if="item.applyStatus === '1'">
|
||||
<u-button size="mini" type="success" text="通过" @click="doApprove(item)"></u-button>
|
||||
<u-button size="mini" type="warning" text="驳回" @click="openReject(item)"></u-button>
|
||||
</view>
|
||||
<view class="actions" v-else-if="item.applyStatus === '2'">
|
||||
<u-button size="mini" type="error" text="撤销" @click="openRevoke(item)"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<u-loadmore v-if="list.length > 0" :status="loadStatus" />
|
||||
</view>
|
||||
|
||||
<u-modal
|
||||
:show="modal.show"
|
||||
:title="modal.title"
|
||||
showCancelButton
|
||||
@confirm="submitModal"
|
||||
@cancel="modal.show = false"
|
||||
>
|
||||
<view class="modal-body">
|
||||
<u-textarea v-model="modal.opinion" :placeholder="modal.placeholder" maxlength="200" count></u-textarea>
|
||||
</view>
|
||||
</u-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listTeacherHolidayStay,
|
||||
approveHolidayStay,
|
||||
rejectHolidayStay,
|
||||
revokeHolidayStay
|
||||
} from '@/api/holidayStay.js';
|
||||
import { getDict } from '@/api/system/dict/data.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
loadStatus: 'loadmore',
|
||||
tabIndex: 1,
|
||||
applyStatus: '1',
|
||||
studentName: '',
|
||||
statusTabs: [
|
||||
{ name: '全部', value: '' },
|
||||
{ name: '待审', value: '1' },
|
||||
{ name: '已通过', value: '2' },
|
||||
{ name: '已驳回', value: '3' },
|
||||
{ name: '已撤销', value: '4' }
|
||||
],
|
||||
applyStatusDict: [],
|
||||
modal: {
|
||||
show: false,
|
||||
title: '',
|
||||
mode: '',
|
||||
id: null,
|
||||
opinion: '',
|
||||
placeholder: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.refresh();
|
||||
},
|
||||
onReachBottom() {
|
||||
this.loadMore();
|
||||
},
|
||||
async onLoad() {
|
||||
try {
|
||||
this.applyStatusDict = (await getDict('xg_holiday_stay_apply_status')) || [];
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) });
|
||||
},
|
||||
onTabChange(item) {
|
||||
const idx = typeof item === 'number' ? item : item.index;
|
||||
this.tabIndex = idx;
|
||||
this.applyStatus = this.statusTabs[idx].value;
|
||||
this.refresh();
|
||||
},
|
||||
async refresh() {
|
||||
this.pageNum = 1;
|
||||
this.list = [];
|
||||
await this.fetchList();
|
||||
},
|
||||
async loadMore() {
|
||||
if (this.loadStatus !== 'loadmore') return;
|
||||
this.pageNum += 1;
|
||||
await this.fetchList(true);
|
||||
},
|
||||
async fetchList(append = false) {
|
||||
this.loading = true;
|
||||
this.loadStatus = 'loading';
|
||||
try {
|
||||
const res = await listTeacherHolidayStay({
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
applyStatus: this.applyStatus || undefined,
|
||||
studentName: this.studentName || undefined
|
||||
});
|
||||
const rows = res.rows || [];
|
||||
this.total = res.total || 0;
|
||||
this.list = append ? this.list.concat(rows) : rows;
|
||||
this.loadStatus = this.list.length >= this.total ? 'nomore' : 'loadmore';
|
||||
} catch (e) {
|
||||
this.loadStatus = 'loadmore';
|
||||
uni.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async doApprove(item) {
|
||||
try {
|
||||
await approveHolidayStay({ id: item.id });
|
||||
uni.showToast({ title: '已通过', icon: 'success' });
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.msg) || '操作失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
openReject(item) {
|
||||
this.modal = {
|
||||
show: true,
|
||||
title: '驳回申请',
|
||||
mode: 'reject',
|
||||
id: item.id,
|
||||
opinion: '',
|
||||
placeholder: '请填写驳回意见(选填)'
|
||||
};
|
||||
},
|
||||
openRevoke(item) {
|
||||
this.modal = {
|
||||
show: true,
|
||||
title: '撤销留宿',
|
||||
mode: 'revoke',
|
||||
id: item.id,
|
||||
opinion: '',
|
||||
placeholder: '请填写撤销原因(必填)'
|
||||
};
|
||||
},
|
||||
async submitModal() {
|
||||
if (this.modal.mode === 'revoke' && !String(this.modal.opinion || '').trim()) {
|
||||
uni.showToast({ title: '请填写撤销原因', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (this.modal.mode === 'reject') {
|
||||
await rejectHolidayStay({ id: this.modal.id, auditOpinion: this.modal.opinion });
|
||||
uni.showToast({ title: '已驳回', icon: 'success' });
|
||||
} else {
|
||||
await revokeHolidayStay({ id: this.modal.id, revokeReason: this.modal.opinion });
|
||||
uni.showToast({ title: '已撤销', icon: 'success' });
|
||||
}
|
||||
this.modal.show = false;
|
||||
this.refresh();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (e && e.msg) || '操作失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
applyStatusLabel(val) {
|
||||
const hit = this.applyStatusDict.find((d) => String(d.value) === String(val));
|
||||
return (hit && hit.label) || val || '-';
|
||||
},
|
||||
applyStatusType(val) {
|
||||
const map = { '0': 'info', '1': 'warning', '2': 'success', '3': 'error', '4': 'info' };
|
||||
return map[String(val)] || 'info';
|
||||
},
|
||||
formatDate(v) {
|
||||
if (!v) return '-';
|
||||
return String(v).replace('T', ' ').slice(0, 16);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
.filter-bar,
|
||||
.search-box {
|
||||
background: #fff;
|
||||
}
|
||||
.search-box {
|
||||
padding: 16rpx 24rpx;
|
||||
}
|
||||
.content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.no {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
.row {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
.actions {
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.modal-body {
|
||||
padding: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -12,8 +12,9 @@
|
||||
**主要功能模块:**
|
||||
|
||||
#### 1.1 宿舍整体情况
|
||||
- 违纪类型选择(从API加载)
|
||||
- 宿舍扣分输入(0-100分)
|
||||
- 扣分项多选(违纪类型)
|
||||
- **归属班级选择(必填)**:选项为当前寝室在住学生班级(去重),默认选中第一项
|
||||
- 宿舍扣分(由扣分项合计,只读,0-100分)
|
||||
- 卫生等级选择(字典:`xg_dorm_hygiene_grade`)
|
||||
- 检查附件上传(支持图片,最多15个)
|
||||
|
||||
|
||||
@@ -83,6 +83,32 @@
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 归属班级(本寝室在住学生班级) -->
|
||||
<u-form-item label="归属班级" prop="classId" required>
|
||||
<view
|
||||
@click="!isViewMode && classOptions.length && (showClassPicker = true)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<u-input
|
||||
:value="classDisplay"
|
||||
:placeholder="classOptions.length ? '请选择归属班级' : '当前寝室无在住学生班级'"
|
||||
readonly
|
||||
border="none"
|
||||
:suffix-icon="isViewMode || !classOptions.length ? '' : 'arrow-down'"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-picker
|
||||
v-if="!isViewMode"
|
||||
:show="showClassPicker && classOptions.length > 0"
|
||||
:columns="classColumns"
|
||||
keyName="text"
|
||||
@confirm="handleClassConfirm"
|
||||
@cancel="showClassPicker = false"
|
||||
></u-picker>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 宿舍扣分 -->
|
||||
<u-form-item label="宿舍扣分" prop="totalScore">
|
||||
<u-input
|
||||
@@ -634,6 +660,8 @@ export default {
|
||||
formData: {
|
||||
disciplineTypeId: null,
|
||||
disciplineTypeName: '',
|
||||
classId: null,
|
||||
className: '',
|
||||
totalScore: 0,
|
||||
hygieneGrade: 'A',
|
||||
checkRemarks: '',
|
||||
@@ -643,9 +671,12 @@ export default {
|
||||
deductionItems: [],
|
||||
showDeductionPopup: false,
|
||||
tempSelectedTypeIds: [],
|
||||
classOptions: [],
|
||||
showClassPicker: false,
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
classId: [{ required: true, message: '请选择归属班级', trigger: 'change' }],
|
||||
hygieneGrade: [{ required: true, message: '请选择卫生等级', trigger: 'change' }],
|
||||
},
|
||||
|
||||
@@ -718,6 +749,19 @@ export default {
|
||||
return this.deductionItems.map((item) => item.disciplineTypeName).join('、');
|
||||
},
|
||||
|
||||
classColumns() {
|
||||
return [
|
||||
this.classOptions.map((item) => ({
|
||||
text: item.className,
|
||||
value: item.classId,
|
||||
})),
|
||||
];
|
||||
},
|
||||
|
||||
classDisplay() {
|
||||
return this.formData.className || '';
|
||||
},
|
||||
|
||||
// 卫生等级列
|
||||
hygieneGradeColumns() {
|
||||
return [
|
||||
@@ -919,6 +963,7 @@ export default {
|
||||
async loadRoomStudents() {
|
||||
if (!this.roomInfo || !this.roomInfo.id) {
|
||||
this.roomStudents = [];
|
||||
this.syncClassOptionsFromStudents([]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -946,21 +991,74 @@ export default {
|
||||
showAttendancePicker: false,
|
||||
showGradePicker: false,
|
||||
}));
|
||||
this.syncClassOptionsFromStudents(this.roomStudents);
|
||||
|
||||
// 加载学生请假信息
|
||||
await this.loadStudentLeaves();
|
||||
} else {
|
||||
this.roomStudents = [];
|
||||
this.syncClassOptionsFromStudents([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载房间学生信息失败:', error);
|
||||
uni.showToast({ title: '加载学生信息失败', icon: 'none' });
|
||||
this.roomStudents = [];
|
||||
this.syncClassOptionsFromStudents([]);
|
||||
} finally {
|
||||
this.studentsLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/** 从本房学生提取班级选项,默认选中第一项 */
|
||||
syncClassOptionsFromStudents(students, preferClassId) {
|
||||
const map = new Map();
|
||||
(students || []).forEach((student) => {
|
||||
if (
|
||||
student.classId == null ||
|
||||
student.classId === '' ||
|
||||
student.classId === 0 ||
|
||||
student.classId === '0'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const key = String(student.classId);
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
classId: student.classId,
|
||||
className: student.className || key,
|
||||
});
|
||||
}
|
||||
});
|
||||
this.classOptions = Array.from(map.values());
|
||||
|
||||
if (!this.classOptions.length) {
|
||||
this.formData.classId = null;
|
||||
this.formData.className = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const preferred =
|
||||
preferClassId !== undefined && preferClassId !== null
|
||||
? preferClassId
|
||||
: this.formData.classId;
|
||||
const matched =
|
||||
preferred != null
|
||||
? this.classOptions.find((item) => String(item.classId) === String(preferred))
|
||||
: null;
|
||||
const selected = matched || this.classOptions[0];
|
||||
this.formData.classId = selected.classId;
|
||||
this.formData.className = selected.className;
|
||||
},
|
||||
|
||||
handleClassConfirm(e) {
|
||||
const selected = e.value && e.value[0];
|
||||
if (selected) {
|
||||
this.formData.classId = selected.value;
|
||||
this.formData.className = selected.text;
|
||||
}
|
||||
this.showClassPicker = false;
|
||||
},
|
||||
|
||||
// 加载学生请假信息
|
||||
async loadStudentLeaves() {
|
||||
if (!this.checkInfo || !this.checkInfo.checkDate || this.roomStudents.length === 0)
|
||||
@@ -1008,6 +1106,8 @@ export default {
|
||||
// 1️⃣ 回显表单基础数据
|
||||
this.formData.disciplineTypeId = this.existingData.disciplineTypeId || null;
|
||||
this.formData.disciplineTypeName = this.existingData.disciplineTypeName || '';
|
||||
this.formData.classId = this.existingData.classId || null;
|
||||
this.formData.className = this.existingData.className || '';
|
||||
this.formData.totalScore = this.existingData.totalScore || 0;
|
||||
this.formData.hygieneGrade = this.existingData.hygieneGrade || 'A';
|
||||
this.formData.checkRemarks = this.existingData.checkRemarks || '';
|
||||
@@ -1060,6 +1160,7 @@ export default {
|
||||
studentId: normalizeStudentId(studentScore.studentId),
|
||||
studentName: studentScore.studentName,
|
||||
studentNo: studentScore.studentNo,
|
||||
classId: studentScore.classId,
|
||||
className: studentScore.className,
|
||||
majorName: studentScore.majorName,
|
||||
collegeName: studentScore.collegeName,
|
||||
@@ -1091,6 +1192,7 @@ export default {
|
||||
}
|
||||
|
||||
this.roomStudents = students;
|
||||
this.syncClassOptionsFromStudents(students, this.formData.classId);
|
||||
|
||||
console.log('✅ 学生评分数据映射完成');
|
||||
|
||||
@@ -2372,6 +2474,20 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.formData.classId) {
|
||||
uni.showToast({
|
||||
title: '请选择归属班级(取自当前寝室在住学生)',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const matchedClass = this.classOptions.find(
|
||||
(item) => String(item.classId) === String(this.formData.classId),
|
||||
);
|
||||
if (matchedClass) {
|
||||
this.formData.className = matchedClass.className;
|
||||
}
|
||||
|
||||
this.submitLoading = true;
|
||||
|
||||
this.syncStudentScoreFromViolations();
|
||||
@@ -2387,8 +2503,11 @@ export default {
|
||||
studentId: student.studentId,
|
||||
studentName: student.studentName,
|
||||
studentNo: student.studentNo,
|
||||
classId: student.classId,
|
||||
className: student.className,
|
||||
majorId: student.majorId,
|
||||
majorName: student.majorName,
|
||||
collegeId: student.collegeId,
|
||||
collegeName: student.collegeName,
|
||||
bedId: student.bedId,
|
||||
bedCode: student.bedCode,
|
||||
|
||||
@@ -84,6 +84,16 @@ export interface DormHygieneCheckVO {
|
||||
*/
|
||||
campusName: string;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属)
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
@@ -227,6 +237,16 @@ export interface DormHygieneCheckForm extends BaseEntity {
|
||||
*/
|
||||
campusName?: string;
|
||||
|
||||
/**
|
||||
* 归属班级ID(宿舍扣分归属)
|
||||
*/
|
||||
classId?: string | number;
|
||||
|
||||
/**
|
||||
* 归属班级名称
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 宿舍总分(0-100分)
|
||||
*/
|
||||
|
||||
@@ -142,3 +142,57 @@ export const getBedOptions = (roomId?: string | number): AxiosPromise<any[]> =>
|
||||
params: roomId ? { roomId } : undefined
|
||||
});
|
||||
};
|
||||
|
||||
/** 学生提交留宿申请 */
|
||||
export const applyHolidayStay = (data: HolidayStayForm) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/apply',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 当前学生申请列表 */
|
||||
export const listMyHolidayStay = (query?: HolidayStayQuery): AxiosPromise<HolidayStayVO[]> => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/myList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/** 班主任本班申请列表 */
|
||||
export const listTeacherHolidayStay = (query?: HolidayStayQuery): AxiosPromise<HolidayStayVO[]> => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/teacherList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/** 班主任审批通过 */
|
||||
export const approveHolidayStay = (data: { id: string | number; auditOpinion?: string }) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/approve',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 班主任驳回 */
|
||||
export const rejectHolidayStay = (data: { id: string | number; auditOpinion?: string }) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/reject',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/** 班主任撤销已通过申请 */
|
||||
export const revokeHolidayStay = (data: { id: string | number; revokeReason: string }) => {
|
||||
return request({
|
||||
url: '/holidayStay/holidayStay/revoke',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
};
|
||||
@@ -173,6 +173,56 @@ export interface HolidayStayVO {
|
||||
* 备注信息
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
applyStatus?: string;
|
||||
|
||||
/**
|
||||
* 提交申请时间
|
||||
*/
|
||||
applyTime?: string;
|
||||
|
||||
/**
|
||||
* 审批人用户ID
|
||||
*/
|
||||
auditorId?: string | number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
auditorName?: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
auditTime?: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
auditOpinion?: string;
|
||||
|
||||
/**
|
||||
* 撤销人用户ID
|
||||
*/
|
||||
revokeBy?: string | number;
|
||||
|
||||
/**
|
||||
* 撤销人姓名
|
||||
*/
|
||||
revokeByName?: string;
|
||||
|
||||
/**
|
||||
* 撤销时间
|
||||
*/
|
||||
revokeTime?: string;
|
||||
|
||||
/**
|
||||
* 撤销原因
|
||||
*/
|
||||
revokeReason?: string;
|
||||
}
|
||||
|
||||
export interface HolidayStayForm extends BaseEntity {
|
||||
@@ -350,6 +400,11 @@ export interface HolidayStayForm extends BaseEntity {
|
||||
* 备注信息
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
applyStatus?: string;
|
||||
}
|
||||
|
||||
export interface HolidayStayQuery extends PageQuery {
|
||||
@@ -523,6 +578,11 @@ export interface HolidayStayQuery extends PageQuery {
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销)
|
||||
*/
|
||||
applyStatus?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
|
||||
+92
@@ -57,6 +57,25 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="归属班级" prop="classId">
|
||||
<el-select
|
||||
v-model="hygieneForm.classId"
|
||||
:placeholder="classOptions.length ? '请选择归属班级' : '当前寝室无在住学生班级'"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
:disabled="!classOptions.length"
|
||||
@change="handleClassChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in classOptions"
|
||||
:key="String(item.classId)"
|
||||
:label="item.className"
|
||||
:value="item.classId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="宿舍扣分" prop="totalScore">
|
||||
<el-input-number
|
||||
@@ -407,6 +426,8 @@ const hygieneForm = reactive({
|
||||
campusName: '',
|
||||
disciplineTypeId: null as number | null,
|
||||
disciplineTypeName: '',
|
||||
classId: undefined as string | number | undefined,
|
||||
className: '',
|
||||
totalScore: 0,
|
||||
hygieneGrade: 'A',
|
||||
checkRemarks: '',
|
||||
@@ -418,6 +439,7 @@ const hygieneRules = reactive({
|
||||
checkTitle: [{ required: true, message: '检查标题不能为空', trigger: 'blur' }],
|
||||
checkDate: [{ required: true, message: '检查日期不能为空', trigger: 'change' }],
|
||||
checkerName: [{ required: true, message: '检查员不能为空', trigger: 'blur' }],
|
||||
classId: [{ required: true, message: '请选择归属班级', trigger: 'change' }],
|
||||
totalScore: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: (error?: string | Error) => void) => {
|
||||
@@ -440,6 +462,8 @@ const hygieneRules = reactive({
|
||||
|
||||
// 学生评分数据
|
||||
const studentScores = ref<any[]>([]);
|
||||
/** 本寝室在住学生班级(去重) */
|
||||
const classOptions = ref<Array<{ classId: string | number; className: string }>>([]);
|
||||
|
||||
// 违纪记录数据(使用普通对象,避免 Map 变更无法触发 Vue 响应式更新)
|
||||
const violationRecords = ref<Record<string, any[]>>({});
|
||||
@@ -488,6 +512,41 @@ const handleDeductionTotalChange = (total: number) => {
|
||||
hygieneForm.totalScore = total;
|
||||
};
|
||||
|
||||
/** 从本房学生提取班级选项,并默认选中第一项(已选且仍有效则保留) */
|
||||
const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | number | null) => {
|
||||
const map = new Map<string, { classId: string | number; className: string }>();
|
||||
students.forEach((student) => {
|
||||
if (student.classId == null || student.classId === '' || student.classId === 0 || student.classId === '0') {
|
||||
return;
|
||||
}
|
||||
const key = String(student.classId);
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
classId: student.classId,
|
||||
className: student.className || key
|
||||
});
|
||||
}
|
||||
});
|
||||
classOptions.value = Array.from(map.values());
|
||||
|
||||
if (!classOptions.value.length) {
|
||||
hygieneForm.classId = undefined;
|
||||
hygieneForm.className = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const preferred = preferClassId ?? hygieneForm.classId;
|
||||
const matched = preferred != null ? classOptions.value.find((item) => String(item.classId) === String(preferred)) : undefined;
|
||||
const selected = matched || classOptions.value[0];
|
||||
hygieneForm.classId = selected.classId;
|
||||
hygieneForm.className = selected.className;
|
||||
};
|
||||
|
||||
const handleClassChange = (classId: string | number) => {
|
||||
const matched = classOptions.value.find((item) => String(item.classId) === String(classId));
|
||||
hygieneForm.className = matched?.className || '';
|
||||
};
|
||||
|
||||
// 上传配置
|
||||
const uploadAction = computed(() => {
|
||||
return (import.meta as any).env.VITE_APP_BASE_API + '/resource/oss/upload';
|
||||
@@ -623,14 +682,38 @@ const loadStudentScores = async () => {
|
||||
|
||||
studentScores.value = scores;
|
||||
console.log('学生评分数据加载成功:', scores.length, '条');
|
||||
|
||||
// 班级选项优先用评分中的班级;若缺失则从当前寝室在住学生补全
|
||||
let classSource = scores;
|
||||
const hasClass = scores.some(
|
||||
(s: any) => s.classId != null && s.classId !== '' && s.classId !== 0 && s.classId !== '0'
|
||||
);
|
||||
if (!hasClass && hygieneForm.roomId) {
|
||||
try {
|
||||
const roomRes = await request({
|
||||
url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
||||
method: 'get',
|
||||
params: { roomId: hygieneForm.roomId }
|
||||
});
|
||||
if (roomRes.code === 200 && roomRes.data) {
|
||||
classSource = roomRes.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('加载寝室班级选项失败', error);
|
||||
}
|
||||
}
|
||||
syncClassOptionsFromStudents(classSource, hygieneForm.classId);
|
||||
|
||||
await loadStudentLeaves();
|
||||
} else {
|
||||
studentScores.value = [];
|
||||
syncClassOptionsFromStudents([], hygieneForm.classId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生评分数据失败:', error);
|
||||
ElMessage.error('加载学生评分数据失败');
|
||||
studentScores.value = [];
|
||||
syncClassOptionsFromStudents([], hygieneForm.classId);
|
||||
} finally {
|
||||
studentsLoading.value = false;
|
||||
}
|
||||
@@ -1179,6 +1262,12 @@ const submitForm = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hygieneForm.classId) {
|
||||
ElMessage.error('请选择归属班级(取自当前寝室在住学生)');
|
||||
return;
|
||||
}
|
||||
handleClassChange(hygieneForm.classId);
|
||||
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 提交前同步个人扣分为违纪合计
|
||||
@@ -1194,8 +1283,11 @@ const submitForm = async () => {
|
||||
studentId: student.studentId,
|
||||
studentName: student.studentName,
|
||||
studentNo: student.studentNo,
|
||||
classId: student.classId,
|
||||
className: student.className,
|
||||
majorId: student.majorId,
|
||||
majorName: student.majorName,
|
||||
collegeId: student.collegeId,
|
||||
collegeName: student.collegeName,
|
||||
bedId: student.bedId,
|
||||
bedCode: student.bedCode,
|
||||
|
||||
+77
@@ -22,6 +22,25 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="归属班级" prop="classId">
|
||||
<el-select
|
||||
v-model="hygieneForm.classId"
|
||||
:placeholder="classOptions.length ? '请选择归属班级' : '当前寝室无在住学生班级'"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
:disabled="!classOptions.length"
|
||||
@change="handleClassChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in classOptions"
|
||||
:key="String(item.classId)"
|
||||
:label="item.className"
|
||||
:value="item.classId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="宿舍扣分" prop="totalScore">
|
||||
<el-input-number
|
||||
@@ -336,6 +355,8 @@ const deductionItems = ref<HygieneDeductionItem[]>([]);
|
||||
const hygieneForm = reactive({
|
||||
disciplineTypeId: null as number | null,
|
||||
disciplineTypeName: '',
|
||||
classId: undefined as string | number | undefined,
|
||||
className: '',
|
||||
totalScore: 0,
|
||||
hygieneGrade: 'A',
|
||||
checkRemarks: '',
|
||||
@@ -344,6 +365,7 @@ const hygieneForm = reactive({
|
||||
|
||||
// 表单验证规则
|
||||
const hygieneRules = reactive({
|
||||
classId: [{ required: true, message: '请选择归属班级', trigger: 'change' }],
|
||||
totalScore: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: (error?: string | Error) => void) => {
|
||||
@@ -366,6 +388,8 @@ const hygieneRules = reactive({
|
||||
|
||||
// 学生相关数据
|
||||
const roomStudents = ref<any[]>([]);
|
||||
/** 本寝室在住学生班级(去重) */
|
||||
const classOptions = ref<Array<{ classId: string | number; className: string }>>([]);
|
||||
const violationRecords = ref<Map<string | number, any[]>>(new Map());
|
||||
const studentLeaves = ref<Map<string | number, any>>(new Map());
|
||||
|
||||
@@ -444,10 +468,48 @@ const handleDeductionTotalChange = (total: number) => {
|
||||
hygieneForm.totalScore = total;
|
||||
};
|
||||
|
||||
/** 从本房学生提取班级选项,并默认选中第一项(已选且仍有效则保留) */
|
||||
const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | number | null) => {
|
||||
const map = new Map<string, { classId: string | number; className: string }>();
|
||||
students.forEach((student) => {
|
||||
const classId = student.classId;
|
||||
const hasClassId = classId != null && classId !== '' && classId !== 0 && classId !== '0';
|
||||
if (!hasClassId) {
|
||||
return;
|
||||
}
|
||||
const key = String(classId);
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
classId,
|
||||
className: (student.className || '').toString().trim() || key
|
||||
});
|
||||
}
|
||||
});
|
||||
classOptions.value = Array.from(map.values());
|
||||
|
||||
if (!classOptions.value.length) {
|
||||
hygieneForm.classId = undefined;
|
||||
hygieneForm.className = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const preferred = preferClassId ?? hygieneForm.classId;
|
||||
const matched = preferred != null ? classOptions.value.find((item) => String(item.classId) === String(preferred)) : undefined;
|
||||
const selected = matched || classOptions.value[0];
|
||||
hygieneForm.classId = selected.classId;
|
||||
hygieneForm.className = selected.className;
|
||||
};
|
||||
|
||||
const handleClassChange = (classId: string | number) => {
|
||||
const matched = classOptions.value.find((item) => String(item.classId) === String(classId));
|
||||
hygieneForm.className = matched?.className || '';
|
||||
};
|
||||
|
||||
// 加载房间学生信息
|
||||
const loadRoomStudents = async () => {
|
||||
if (!props.roomInfo?.id) {
|
||||
roomStudents.value = [];
|
||||
syncClassOptionsFromStudents([]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -467,16 +529,19 @@ const loadRoomStudents = async () => {
|
||||
attendance_status: '1',
|
||||
student_grade: 'A'
|
||||
}));
|
||||
syncClassOptionsFromStudents(roomStudents.value);
|
||||
|
||||
// 加载学生请假信息
|
||||
await loadStudentLeaves();
|
||||
} else {
|
||||
roomStudents.value = [];
|
||||
syncClassOptionsFromStudents([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载房间学生信息失败:', error);
|
||||
ElMessage.error('加载学生信息失败');
|
||||
roomStudents.value = [];
|
||||
syncClassOptionsFromStudents([]);
|
||||
} finally {
|
||||
studentsLoading.value = false;
|
||||
}
|
||||
@@ -878,6 +943,12 @@ const submitForm = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hygieneForm.classId) {
|
||||
ElMessage.error('请选择归属班级(取自当前寝室在住学生)');
|
||||
return;
|
||||
}
|
||||
handleClassChange(hygieneForm.classId);
|
||||
|
||||
buttonLoading.value = true;
|
||||
|
||||
syncStudentScoreFromViolations();
|
||||
@@ -891,8 +962,11 @@ const submitForm = async () => {
|
||||
studentId: student.studentId,
|
||||
studentName: student.studentName,
|
||||
studentNo: student.studentNo,
|
||||
classId: student.classId,
|
||||
className: student.className,
|
||||
majorId: student.majorId,
|
||||
majorName: student.majorName,
|
||||
collegeId: student.collegeId,
|
||||
collegeName: student.collegeName,
|
||||
bedId: student.bedId,
|
||||
bedCode: student.bedCode,
|
||||
@@ -999,12 +1073,15 @@ const resetForm = () => {
|
||||
Object.assign(hygieneForm, {
|
||||
disciplineTypeId: null,
|
||||
disciplineTypeName: '',
|
||||
classId: undefined,
|
||||
className: '',
|
||||
totalScore: 0,
|
||||
hygieneGrade: 'A',
|
||||
checkRemarks: '',
|
||||
attachmentIds: ''
|
||||
});
|
||||
deductionItems.value = [];
|
||||
classOptions.value = [];
|
||||
|
||||
roomStudents.value = [];
|
||||
violationRecords.value.clear();
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="房间名称" align="center" prop="roomName" />
|
||||
<el-table-column label="归属班级" align="center" prop="className" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="宿舍楼名称" align="center" prop="buildingName" />
|
||||
<el-table-column label="校区名称" align="center" prop="campusName" />
|
||||
<el-table-column label="扣分项" align="center" prop="disciplineTypeName" min-width="160" show-overflow-tooltip />
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请状态" prop="applyStatus">
|
||||
<el-select v-model="queryParams.applyStatus" placeholder="请选择申请状态" clearable>
|
||||
<el-option v-for="dict in xg_holiday_stay_apply_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in xg_holiday_stay_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
@@ -223,6 +228,11 @@
|
||||
<el-table-column label="扩展字段2" align="center" prop="extField2" v-if="false" />
|
||||
<el-table-column label="扩展字段3" align="center" prop="extField3" v-if="false" />
|
||||
<el-table-column label="备注信息" align="center" prop="remark" />
|
||||
<el-table-column label="申请状态" align="center" prop="applyStatus" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_holiday_stay_apply_status" :value="scope.row.applyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small">
|
||||
@@ -801,8 +811,8 @@ import {
|
||||
} from '@/api/xg/xgss/holidayStay/holidayStay/types-sg';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno, xg_holiday_stay_status } = toRefs<any>(
|
||||
proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno', 'xg_holiday_stay_status')
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno, xg_holiday_stay_status, xg_holiday_stay_apply_status } = toRefs<any>(
|
||||
proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno', 'xg_holiday_stay_status', 'xg_holiday_stay_apply_status')
|
||||
);
|
||||
|
||||
// 用户store - 用于获取当前登录用户信息,实现权限控制
|
||||
@@ -920,6 +930,7 @@ const data = reactive<PageData<HolidayStayForm, HolidayStayQuery>>({
|
||||
extField1: undefined,
|
||||
extField2: undefined,
|
||||
extField3: undefined,
|
||||
applyStatus: undefined,
|
||||
status: 'valid', // 默认展示有效状态的数据
|
||||
params: {}
|
||||
},
|
||||
@@ -1210,15 +1221,22 @@ const handleDelete = async (row?: HolidayStayVO) => {
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
/** 导出按钮操作:未选申请状态时默认导出已通过名单 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'holidayStay/holidayStay/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`holidayStay_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
const exportParams: any = { ...queryParams.value };
|
||||
if (!exportParams.applyStatus) {
|
||||
exportParams.applyStatus = '2';
|
||||
}
|
||||
if (exportParams.status === 'valid' || exportParams.status === 'invalid') {
|
||||
const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
exportParams.params = {
|
||||
...exportParams.params,
|
||||
statusType: exportParams.status,
|
||||
currentTime
|
||||
};
|
||||
delete exportParams.status;
|
||||
}
|
||||
proxy?.download('holidayStay/holidayStay/export', exportParams, `holidayStay_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
/** 远程搜索学生 */
|
||||
|
||||
@@ -13,8 +13,13 @@
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-form-item label="申请状态" prop="applyStatus">
|
||||
<el-select v-model="queryParams.applyStatus" placeholder="请选择申请状态" clearable>
|
||||
<el-option v-for="dict in xg_holiday_stay_apply_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择有效期" clearable>
|
||||
<el-option label="有效" value="valid" />
|
||||
<el-option label="失效" value="invalid" />
|
||||
</el-select>
|
||||
@@ -157,9 +162,11 @@
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col> -->
|
||||
<!-- <el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['holidayStay:holidayStay:export']">导出</el-button>
|
||||
</el-col> -->
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['holidayStay:holidayStay:export']"
|
||||
>导出留宿名单</el-button
|
||||
>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -221,15 +228,47 @@
|
||||
<el-table-column label="扩展字段2" align="center" prop="extField2" v-if="false" />
|
||||
<el-table-column label="扩展字段3" align="center" prop="extField3" v-if="false" />
|
||||
<el-table-column label="备注信息" align="center" prop="remark" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<el-table-column label="申请状态" align="center" prop="applyStatus" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_holiday_stay_apply_status" :value="scope.row.applyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="有效期" align="center" prop="status" width="90">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row)" size="small">
|
||||
{{ getStatusText(scope.row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="220">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="通过" placement="top" v-if="scope.row.applyStatus === '1'">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
@click="openAuditDialog(scope.row, 'approve')"
|
||||
v-hasPermi="['holidayStay:holidayStay:audit']"
|
||||
>通过</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="驳回" placement="top" v-if="scope.row.applyStatus === '1'">
|
||||
<el-button
|
||||
link
|
||||
type="warning"
|
||||
@click="openAuditDialog(scope.row, 'reject')"
|
||||
v-hasPermi="['holidayStay:holidayStay:audit']"
|
||||
>驳回</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="撤销" placement="top" v-if="scope.row.applyStatus === '2'">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="openAuditDialog(scope.row, 'revoke')"
|
||||
v-hasPermi="['holidayStay:holidayStay:revoke']"
|
||||
>撤销</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['holidayStay:holidayStay:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
@@ -553,6 +592,26 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批 / 撤销 -->
|
||||
<el-dialog :title="auditDialog.title" v-model="auditDialog.visible" width="480px" append-to-body>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item :label="auditDialog.mode === 'revoke' ? '撤销原因' : '审批意见'" :required="auditDialog.mode === 'revoke'">
|
||||
<el-input
|
||||
v-model="auditDialog.opinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="auditDialog.mode === 'revoke' ? '请输入撤销原因(必填)' : '请输入审批意见(选填)'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="auditDialog.loading" type="primary" @click="submitAudit">确 定</el-button>
|
||||
<el-button @click="auditDialog.visible = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -565,9 +624,8 @@ import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus
|
||||
import { getToken } from '@/utils/auth';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import request from '@/utils/request';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {
|
||||
listHolidayStay,
|
||||
listTeacherHolidayStay,
|
||||
getHolidayStay,
|
||||
delHolidayStay,
|
||||
addHolidayStay,
|
||||
@@ -577,7 +635,10 @@ import {
|
||||
getAreaOptions,
|
||||
getBuildingOptions,
|
||||
getRoomOptions,
|
||||
getBedOptions
|
||||
getBedOptions,
|
||||
approveHolidayStay,
|
||||
rejectHolidayStay,
|
||||
revokeHolidayStay
|
||||
} from '@/api/xg/xgss/holidayStay/holidayStay';
|
||||
import {
|
||||
HolidayStayVO,
|
||||
@@ -593,10 +654,9 @@ import {
|
||||
} from '@/api/xg/xgss/holidayStay/holidayStay/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno } = toRefs<any>(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno'));
|
||||
|
||||
// 用户store - 用于获取当前登录用户信息,实现权限控制
|
||||
const userStore = useUserStore();
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno, xg_holiday_stay_apply_status } = toRefs<any>(
|
||||
proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno', 'xg_holiday_stay_apply_status')
|
||||
);
|
||||
|
||||
const holidayStayList = ref<HolidayStayVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
@@ -701,6 +761,7 @@ const data = reactive<PageData<HolidayStayForm, HolidayStayQuery>>({
|
||||
extField1: undefined,
|
||||
extField2: undefined,
|
||||
extField3: undefined,
|
||||
applyStatus: '1', // 班主任默认看待审
|
||||
status: 'valid', // 默认显示有效状态的记录
|
||||
params: {}
|
||||
},
|
||||
@@ -734,34 +795,23 @@ const uploadData = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/** 查询假期留宿管理列表 */
|
||||
/** 查询假期留宿管理列表(班主任本班) */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
|
||||
// 确保用户信息已加载
|
||||
if (!userStore.userId) {
|
||||
await userStore.getInfo();
|
||||
}
|
||||
|
||||
// 处理状态过滤逻辑
|
||||
const requestParams = { ...queryParams.value };
|
||||
|
||||
// 添加创建人过滤条件 - 班主任只能看到自己创建的数据
|
||||
(requestParams as any).createBy = userStore.userId;
|
||||
|
||||
// 根据状态设置时间过滤条件
|
||||
// 根据有效期设置时间过滤条件
|
||||
if (requestParams.status) {
|
||||
const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
if (requestParams.status === 'valid') {
|
||||
// 查询有效状态:结束时间大于等于当前时间
|
||||
requestParams.params = {
|
||||
...requestParams.params,
|
||||
statusType: 'valid',
|
||||
currentTime: currentTime
|
||||
};
|
||||
} else if (requestParams.status === 'invalid') {
|
||||
// 查询失效状态:结束时间小于当前时间
|
||||
requestParams.params = {
|
||||
...requestParams.params,
|
||||
statusType: 'invalid',
|
||||
@@ -769,11 +819,10 @@ const getList = async () => {
|
||||
};
|
||||
}
|
||||
|
||||
// 移除status参数,因为后端不需要这个字段
|
||||
delete requestParams.status;
|
||||
}
|
||||
|
||||
const res = await listHolidayStay(requestParams);
|
||||
const res = await listTeacherHolidayStay(requestParams);
|
||||
holidayStayList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
@@ -911,15 +960,64 @@ const handleDelete = async (row?: HolidayStayVO) => {
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
/** 导出按钮操作:默认导出已通过名单;若已选申请状态则按筛选导出 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'holidayStay/holidayStay/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`holidayStay_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
const exportParams: any = { ...queryParams.value };
|
||||
if (!exportParams.applyStatus) {
|
||||
exportParams.applyStatus = '2';
|
||||
}
|
||||
if (exportParams.status) {
|
||||
const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
exportParams.params = {
|
||||
...exportParams.params,
|
||||
statusType: exportParams.status,
|
||||
currentTime
|
||||
};
|
||||
delete exportParams.status;
|
||||
}
|
||||
proxy?.download('holidayStay/holidayStay/export', exportParams, `holidayStay_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
const auditDialog = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
mode: 'approve' as 'approve' | 'reject' | 'revoke',
|
||||
rowId: undefined as string | number | undefined,
|
||||
opinion: '',
|
||||
loading: false
|
||||
});
|
||||
|
||||
const openAuditDialog = (row: HolidayStayVO, mode: 'approve' | 'reject' | 'revoke') => {
|
||||
auditDialog.mode = mode;
|
||||
auditDialog.rowId = row.id;
|
||||
auditDialog.opinion = '';
|
||||
auditDialog.title = mode === 'approve' ? '审批通过' : mode === 'reject' ? '审批驳回' : '撤销留宿';
|
||||
auditDialog.visible = true;
|
||||
};
|
||||
|
||||
const submitAudit = async () => {
|
||||
if (!auditDialog.rowId) return;
|
||||
if (auditDialog.mode === 'revoke' && !auditDialog.opinion?.trim()) {
|
||||
proxy?.$modal.msgWarning('请填写撤销原因');
|
||||
return;
|
||||
}
|
||||
auditDialog.loading = true;
|
||||
try {
|
||||
if (auditDialog.mode === 'approve') {
|
||||
await approveHolidayStay({ id: auditDialog.rowId, auditOpinion: auditDialog.opinion });
|
||||
proxy?.$modal.msgSuccess('已通过');
|
||||
} else if (auditDialog.mode === 'reject') {
|
||||
await rejectHolidayStay({ id: auditDialog.rowId, auditOpinion: auditDialog.opinion });
|
||||
proxy?.$modal.msgSuccess('已驳回');
|
||||
} else {
|
||||
await revokeHolidayStay({ id: auditDialog.rowId, revokeReason: auditDialog.opinion });
|
||||
proxy?.$modal.msgSuccess('已撤销');
|
||||
}
|
||||
auditDialog.visible = false;
|
||||
await getList();
|
||||
} finally {
|
||||
auditDialog.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 远程搜索学生 */
|
||||
|
||||
Reference in new issue
Block a user