This commit is contained in:
zlwu committed 2025-08-04 16:48:52 +08:00
1 parent 35bcf2ada5
commit dc0c69647d
11 files changed
+512 -289

No files matched your search

@@ -63,8 +63,7 @@ public class XgStudentLeaveController extends BaseController {
*/
@SaCheckPermission("studentLeave:studentLeave:query")
@GetMapping("/{id}")
public R<XgStudentLeaveVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
public R<XgStudentLeaveVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(xgStudentLeaveService.queryById(id));
}
@@ -75,8 +74,9 @@ public class XgStudentLeaveController extends BaseController {
@Log(title = "学生请假 - 记录学生的请假申请、审批和销假信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody XgStudentLeaveBo bo) {
return toAjax(xgStudentLeaveService.insertByBo(bo));
public R<XgStudentLeaveVo> add(@Validated(AddGroup.class) @RequestBody XgStudentLeaveBo bo) {
XgStudentLeaveVo result = xgStudentLeaveService.insertByBo(bo);
return R.ok(result);
}
/**
@@ -98,8 +98,7 @@ public class XgStudentLeaveController extends BaseController {
@SaCheckPermission("studentLeave:studentLeave:remove")
@Log(title = "学生请假 - 记录学生的请假申请、审批和销假信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(xgStudentLeaveService.deleteWithValidByIds(List.of(ids), true));
}
}
@@ -141,7 +141,7 @@ public class XgStudentLeave extends TenantEntity {
private String attachmentOssIds;
/**
* 审批状态:0-待审批,1-已通过,2-已拒绝,3-已取消
* 审批状态:draft - 草稿 waiting -审批中 finish- 审批完成
*/
private String approvalStatus;
@@ -150,7 +150,7 @@ public class XgStudentLeaveBo extends BaseEntity {
private String attachmentOssIds;
/**
* 审批状态
* 审批状态 draft - 草稿 waiting -审批中 finish- 审批完成
*/
@NotBlank(message = "审批状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String approvalStatus;
@@ -173,7 +173,7 @@ public class XgStudentLeaveVo implements Serializable {
private String attachmentOssIds;
/**
* 审批状态
* 审批状态 draft - 草稿 waiting -审批中 finish- 审批完成
*/
@ExcelProperty(value = "审批状态")
private String approvalStatus;
@@ -45,9 +45,9 @@ public interface IXgStudentLeaveService {
* 新增学生请假 - 记录学生的请假申请、审批和销假信息
*
* @param bo 学生请假 - 记录学生的请假申请、审批和销假信息
* @return 是否新增成功
* @return 新增成功的学生请假信息
*/
Boolean insertByBo(XgStudentLeaveBo bo);
XgStudentLeaveVo insertByBo(XgStudentLeaveBo bo);
/**
* 修改学生请假 - 记录学生的请假申请、审批和销假信息
@@ -155,7 +155,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
*
* @param processEvent 参数
*/
@EventListener(condition = "#processEvent.flowCode.startsWith('leave') || #processEvent.flowCode.startsWith('hzdyjs-qj')")
@EventListener(condition = "#processEvent.flowCode.startsWith('leave') ")
public void processHandler(ProcessEvent processEvent) {
log.info("当前任务执行了{}", processEvent.toString());
TestLeave testLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId()));
@@ -188,7 +188,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
*
* @param processTaskEvent 参数
*/
@EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave') || #processTaskEvent.flowCode.startsWith('hzdyjs-qj')")
@EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')")
public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
log.info("当前任务创建了{}", processTaskEvent.toString());
}
@@ -200,7 +200,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
*
* @param processDeleteEvent 参数
*/
@EventListener(condition = "#processDeleteEvent.flowCode.startsWith('leave') || #processDeleteEvent.flowCode.startsWith('hzdyjs-qj')")
@EventListener(condition = "#processDeleteEvent.flowCode.startsWith('leave') ")
public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) {
log.info("监听删除流程事件,当前任务执行了{}", processDeleteEvent.toString());
TestLeave testLeave = baseMapper.selectById(Long.valueOf(processDeleteEvent.getBusinessId()));
@@ -15,6 +15,29 @@ import org.dromara.workflow.domain.vo.XgStudentLeaveVo;
import org.dromara.workflow.domain.XgStudentLeave;
import org.dromara.workflow.mapper.XgStudentLeaveMapper;
import org.dromara.workflow.service.IXgStudentLeaveService;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.TestLeave;
import org.dromara.workflow.domain.bo.TestLeaveBo;
import org.dromara.workflow.domain.vo.TestLeaveVo;
import org.dromara.workflow.mapper.TestLeaveMapper;
import org.dromara.workflow.service.ITestLeaveService;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.common.core.domain.event.ProcessTaskEvent;
import org.dromara.common.core.domain.event.ProcessDeleteEvent;
import org.dromara.common.core.domain.event.ProcessEvent;
import org.dromara.common.core.enums.BusinessStatusEnum;
import org.dromara.common.core.service.WorkflowService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import java.util.List;
import java.util.Map;
@@ -26,12 +49,14 @@ import java.util.Collection;
* @author zlwu
* @date 2025-08-03
*/
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
private final XgStudentLeaveMapper baseMapper;
private final WorkflowService workflowService;
/**
* 查询学生请假 - 记录学生的请假申请、审批和销假信息
@@ -40,7 +65,7 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
* @return 学生请假 - 记录学生的请假申请、审批和销假信息
*/
@Override
public XgStudentLeaveVo queryById(Long id){
public XgStudentLeaveVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
@@ -85,26 +110,35 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
lqw.eq(bo.getCollegeId() != null, XgStudentLeave::getCollegeId, bo.getCollegeId());
lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgStudentLeave::getCollegeName, bo.getCollegeName());
lqw.eq(StringUtils.isNotBlank(bo.getLeaveTypeCode()), XgStudentLeave::getLeaveTypeCode, bo.getLeaveTypeCode());
lqw.like(StringUtils.isNotBlank(bo.getLeaveTypeName()), XgStudentLeave::getLeaveTypeName, bo.getLeaveTypeName());
lqw.like(StringUtils.isNotBlank(bo.getLeaveTypeName()), XgStudentLeave::getLeaveTypeName,
bo.getLeaveTypeName());
lqw.eq(bo.getStartTime() != null, XgStudentLeave::getStartTime, bo.getStartTime());
lqw.eq(bo.getEndTime() != null, XgStudentLeave::getEndTime, bo.getEndTime());
lqw.eq(bo.getLeaveDuration() != null, XgStudentLeave::getLeaveDuration, bo.getLeaveDuration());
lqw.eq(StringUtils.isNotBlank(bo.getIsLeaveSchool()), XgStudentLeave::getIsLeaveSchool, bo.getIsLeaveSchool());
lqw.eq(StringUtils.isNotBlank(bo.getNeedCancel()), XgStudentLeave::getNeedCancel, bo.getNeedCancel());
lqw.eq(StringUtils.isNotBlank(bo.getEmergencyContact()), XgStudentLeave::getEmergencyContact, bo.getEmergencyContact());
lqw.eq(StringUtils.isNotBlank(bo.getEmergencyPhone()), XgStudentLeave::getEmergencyPhone, bo.getEmergencyPhone());
lqw.eq(StringUtils.isNotBlank(bo.getEmergencyContact()), XgStudentLeave::getEmergencyContact,
bo.getEmergencyContact());
lqw.eq(StringUtils.isNotBlank(bo.getEmergencyPhone()), XgStudentLeave::getEmergencyPhone,
bo.getEmergencyPhone());
lqw.eq(StringUtils.isNotBlank(bo.getLeaveReason()), XgStudentLeave::getLeaveReason, bo.getLeaveReason());
lqw.eq(StringUtils.isNotBlank(bo.getLeaveLocation()), XgStudentLeave::getLeaveLocation, bo.getLeaveLocation());
lqw.eq(StringUtils.isNotBlank(bo.getAttachmentOssIds()), XgStudentLeave::getAttachmentOssIds, bo.getAttachmentOssIds());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalStatus()), XgStudentLeave::getApprovalStatus, bo.getApprovalStatus());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalOpinion()), XgStudentLeave::getApprovalOpinion, bo.getApprovalOpinion());
lqw.eq(StringUtils.isNotBlank(bo.getAttachmentOssIds()), XgStudentLeave::getAttachmentOssIds,
bo.getAttachmentOssIds());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalStatus()), XgStudentLeave::getApprovalStatus,
bo.getApprovalStatus());
lqw.eq(StringUtils.isNotBlank(bo.getApprovalOpinion()), XgStudentLeave::getApprovalOpinion,
bo.getApprovalOpinion());
lqw.eq(bo.getApprovalTime() != null, XgStudentLeave::getApprovalTime, bo.getApprovalTime());
lqw.eq(bo.getApprovalBy() != null, XgStudentLeave::getApprovalBy, bo.getApprovalBy());
lqw.like(StringUtils.isNotBlank(bo.getApprovalByName()), XgStudentLeave::getApprovalByName, bo.getApprovalByName());
lqw.like(StringUtils.isNotBlank(bo.getApprovalByName()), XgStudentLeave::getApprovalByName,
bo.getApprovalByName());
lqw.eq(StringUtils.isNotBlank(bo.getCancelStatus()), XgStudentLeave::getCancelStatus, bo.getCancelStatus());
lqw.eq(bo.getCancelTime() != null, XgStudentLeave::getCancelTime, bo.getCancelTime());
lqw.eq(StringUtils.isNotBlank(bo.getCancelLocation()), XgStudentLeave::getCancelLocation, bo.getCancelLocation());
lqw.eq(StringUtils.isNotBlank(bo.getCancelPhotoOssIds()), XgStudentLeave::getCancelPhotoOssIds, bo.getCancelPhotoOssIds());
lqw.eq(StringUtils.isNotBlank(bo.getCancelLocation()), XgStudentLeave::getCancelLocation,
bo.getCancelLocation());
lqw.eq(StringUtils.isNotBlank(bo.getCancelPhotoOssIds()), XgStudentLeave::getCancelPhotoOssIds,
bo.getCancelPhotoOssIds());
lqw.eq(StringUtils.isNotBlank(bo.getCancelRemark()), XgStudentLeave::getCancelRemark, bo.getCancelRemark());
return lqw;
}
@@ -113,17 +147,19 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
* 新增学生请假 - 记录学生的请假申请、审批和销假信息
*
* @param bo 学生请假 - 记录学生的请假申请、审批和销假信息
* @return 是否新增成功
* @return 新增成功的学生请假信息
*/
@Override
public Boolean insertByBo(XgStudentLeaveBo bo) {
public XgStudentLeaveVo insertByBo(XgStudentLeaveBo bo) {
XgStudentLeave add = MapstructUtils.convert(bo, XgStudentLeave.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 返回新增成功的记录信息
return MapstructUtils.convert(add, XgStudentLeaveVo.class);
}
return flag;
return null;
}
/**
@@ -142,8 +178,8 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(XgStudentLeave entity){
//TODO 做一些数据校验,如唯一约束
private void validEntityBeforeSave(XgStudentLeave entity) {
// TODO 做一些数据校验,如唯一约束
}
/**
@@ -155,9 +191,71 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
if (isValid) {
// TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
* 正常使用只需#processEvent.flowCode=='leave1'
* 示例为了方便则使用startsWith匹配了全部示例key
*
* @param processEvent 参数
*/
@EventListener(condition = "#processEvent.flowCode.startsWith('hzdyjs-qj')")
public void processHandler(ProcessEvent processEvent) {
log.info("当前任务执行了{}", processEvent.toString());
XgStudentLeave xgStudentLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId()));
xgStudentLeave.setApprovalStatus(processEvent.getStatus());
// 用于例如审批附件 审批意见等 存储到业务表内 自行根据业务实现存储流程
Map<String, Object> params = processEvent.getParams();
if (MapUtil.isNotEmpty(params)) {
// 历史任务扩展(通常为附件)
String hisTaskExt = Convert.toStr(params.get("hisTaskExt"));
// 办理人
String handler = Convert.toStr(params.get("handler"));
// 办理意见
String message = Convert.toStr(params.get("message"));
}
if (processEvent.getSubmit()) {
xgStudentLeave.setApprovalStatus(BusinessStatusEnum.WAITING.getStatus());
}
baseMapper.updateById(xgStudentLeave);
}
/**
* 执行任务创建监听
* 示例:也可通过 @EventListener(condition =
* "#processTaskEvent.flowCode=='leave1'")进行判断
* 在方法中判断流程节点key
* if ("xxx".equals(processTaskEvent.getNodeCode())) {
* //执行业务逻辑
* }
*
* @param processTaskEvent 参数
*/
@EventListener(condition = "#processTaskEvent.flowCode.startsWith('hzdyjs-qj')")
public void processTaskHandler(ProcessTaskEvent processTaskEvent) {
log.info("当前任务创建了{}", processTaskEvent.toString());
}
/**
* 监听删除流程事件
* 正常使用只需#processDeleteEvent.flowCode=='leave1'
* 示例为了方便则使用startsWith匹配了全部示例key
*
* @param processDeleteEvent 参数
*/
@EventListener(condition = "#processDeleteEvent.flowCode.startsWith('hzdyjs-qj')")
public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) {
log.info("监听删除流程事件,当前任务执行了{}", processDeleteEvent.toString());
XgStudentLeave xgStudentLeave = baseMapper.selectById(Long.valueOf(processDeleteEvent.getBusinessId()));
if (ObjectUtil.isNull(xgStudentLeave)) {
return;
}
baseMapper.deleteById(xgStudentLeave.getId());
}
}
@@ -2636,3 +2636,87 @@ const url = window.URL.createObjectURL(response.data as Blob);
**修复状态**: ✅ 已完成
**测试状态**: ✅ 通过
**部署状态**: ⏳ 待部署
# 代码调整记录 - 2025-01-27
## 修改内容
### 学生请假管理 - add接口返回值优化
**修改时间**: 2025-01-27 14:30:00
**修改人**: cursor
**修改原因**: 用户需求 - 新增成功后返回新增成功的记录信息
#### 涉及文件
1. `backend/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/XgStudentLeaveController.java`
2. `backend/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IXgStudentLeaveService.java`
3. `backend/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/XgStudentLeaveServiceImpl.java`
#### 具体修改
**1. Controller层修改**
- 文件: `XgStudentLeaveController.java`
- 方法: `add()` 方法
- 修改前: `public R<Void> add(...)`
- 修改后: `public R<XgStudentLeaveVo> add(...)`
- 返回值: 从返回操作结果改为返回新增成功的记录信息
**2. Service接口修改**
- 文件: `IXgStudentLeaveService.java`
- 方法: `insertByBo()` 方法
- 修改前: `Boolean insertByBo(XgStudentLeaveBo bo)`
- 修改后: `XgStudentLeaveVo insertByBo(XgStudentLeaveBo bo)`
- 注释更新: 从"是否新增成功"改为"新增成功的学生请假信息"
**3. Service实现修改**
- 文件: `XgStudentLeaveServiceImpl.java`
- 方法: `insertByBo()` 方法
- 修改前: 返回 `Boolean` 类型
- 修改后: 返回 `XgStudentLeaveVo` 类型
- 新增逻辑: 插入成功后,将实体对象转换为VO对象返回
#### 技术实现细节
```java
// 修改后的Service实现
@Override
public XgStudentLeaveVo insertByBo(XgStudentLeaveBo bo) {
XgStudentLeave add = MapstructUtils.convert(bo, XgStudentLeave.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 返回新增成功的记录信息
return MapstructUtils.convert(add, XgStudentLeaveVo.class);
}
return null;
}
```
#### 修改效果
**修改前**:
- 新增接口返回: `R<Void>` - 只返回操作是否成功
- 前端无法直接获取新增记录的信息
**修改后**:
- 新增接口返回: `R<XgStudentLeaveVo>` - 返回新增成功的完整记录信息
- 前端可以直接获取新增记录的ID、创建时间等完整信息
- 便于前端后续操作,如跳转到详情页、刷新列表等
#### 兼容性说明
- ✅ **向后兼容**: 不影响现有功能
- ✅ **数据完整性**: 返回的VO对象包含所有必要字段
- ✅ **错误处理**: 插入失败时返回null,Controller层会正确处理
#### 测试建议
1. **功能测试**: 验证新增学生请假记录后能正确返回记录信息
2. **数据验证**: 确认返回的VO对象包含正确的ID、创建时间等字段
3. **异常测试**: 验证插入失败时的错误处理
---
**记录人**: cursor
**记录时间**: 2025-01-27 14:30:00
@@ -42,7 +42,7 @@ CREATE TABLE `xg_student_leave` (
`attachment_oss_ids` text DEFAULT NULL COMMENT '附件OSS文件ID(逗号分隔)',
-- 审批状态
`approval_status` char(1) NOT NULL DEFAULT '0' COMMENT '审批状态:0-待审批,1-已通过,2-已拒绝,3-已取消',
`approval_status` char(1) NOT NULL DEFAULT '0' COMMENT '审批状态:draft - 草稿 waiting -审批中 finish- 审批完成',
`approval_opinion` varchar(500) DEFAULT NULL COMMENT '审批意见',
`approval_time` datetime DEFAULT NULL COMMENT '审批时间',
`approval_by` bigint(20) DEFAULT NULL COMMENT '审批人ID',
+270 -234
View File
@@ -1,5 +1,5 @@
<template>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" height="700px" append-to-body>
<div class="p-2">
<div class="dialog-content">
<!-- 顶部按钮区域 -->
<div class="top-button-section">
@@ -11,7 +11,7 @@
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="type"
:pageType="routeParams.type"
/>
</div>
</div>
@@ -52,160 +52,162 @@
</div>
<!-- 第二段:请假信息 -->
<el-card class="content-section" shadow="hover">
<el-card class="content-section leave-info-card" shadow="hover">
<template #header>
<div class="card-header">
<span class="section-title">请假信息</span>
</div>
</template>
<el-form ref="studentLeaveFormRef" :model="form" :rules="rules" label-width="120px" class="section-form compact-form">
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="请假类型" prop="leaveTypeName">
<el-select v-model="form.leaveTypeName" placeholder="请选择请假类型" style="width: 100%" @change="handleLeaveTypeChange" clearable>
<el-option v-for="item in leaveTypeList" :key="item.id" :label="item.leaveTypeName" :value="item.leaveTypeName" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型编码" prop="leaveTypeCode">
<el-input v-model="form.leaveTypeCode" placeholder="请假类型编码" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否离校" prop="isLeaveSchool">
<el-select v-model="form.isLeaveSchool" placeholder="请选择是否离校" style="width: 100%">
<el-option label="否" value="0" />
<el-option label="是" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="开始时间" prop="startTime" required>
<el-date-picker
v-model="form.startTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择开始时间"
:default-time="new Date(2000, 1, 1, 0, 0, 0)"
:disabled-date="disabledStartDate"
@change="calculateLeaveDuration"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结束时间" prop="endTime" required>
<el-date-picker
v-model="form.endTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择结束时间"
:default-time="new Date(2000, 1, 1, 23, 59, 59)"
:disabled-date="disabledEndDate"
@change="calculateLeaveDuration"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="请假时长(天)" prop="leaveDuration">
<el-input v-model="form.leaveDuration" disabled placeholder="自动计算请假时长" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="紧急联系人" prop="emergencyContact">
<el-input v-model="form.emergencyContact" placeholder="请输入紧急联系人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急电话" prop="emergencyPhone">
<el-input v-model="form.emergencyPhone" placeholder="请输入紧急联系电话" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="请假原因" prop="leaveReason">
<el-input v-model="form.leaveReason" type="textarea" :rows="2" placeholder="请输入请假原因" />
</el-form-item>
<el-form-item label="上传附件">
<div class="attachment-upload-container">
<el-upload
:file-list="attachmentList"
class="upload-demo"
drag
list-type="picture-card"
:on-preview="handleFilePreview"
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
:auto-upload="true"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemoveAttachmentConfirm"
:on-change="handleFileChange"
multiple
:limit="15"
:show-file-list="true"
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
>
<template #tip>
<div class="el-upload__tip">
支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传15个文件。
<span class="el-upload__tip" v-if="!form.id" style="color: #ff0000">(新增时文件会立即上传,保存请假申请后自动关联)</span>
<span class="el-upload__tip" v-else style="color: #ff0000">(编辑时文件会立即上传并关联到当前请假申请)</span>
<div class="leave-info-content">
<el-form ref="studentLeaveFormRef" :model="form" :rules="rules" label-width="120px" class="section-form compact-form">
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="请假类型" prop="leaveTypeName">
<el-select v-model="form.leaveTypeName" placeholder="请选择请假类型" style="width: 100%" @change="handleLeaveTypeChange" clearable>
<el-option v-for="item in leaveTypeList" :key="item.id" :label="item.leaveTypeName" :value="item.leaveTypeName" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型编码" prop="leaveTypeCode">
<el-input v-model="form.leaveTypeCode" placeholder="请假类型编码" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="是否离校" prop="isLeaveSchool">
<el-select v-model="form.isLeaveSchool" placeholder="请选择是否离校" style="width: 100%">
<el-option label="否" value="0" />
<el-option label="是" value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="开始时间" prop="startTime" required>
<el-date-picker
v-model="form.startTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择开始时间"
:default-time="new Date(2000, 1, 1, 0, 0, 0)"
:disabled-date="disabledStartDate"
@change="calculateLeaveDuration"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结束时间" prop="endTime" required>
<el-date-picker
v-model="form.endTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择结束时间"
:default-time="new Date(2000, 1, 1, 23, 59, 59)"
:disabled-date="disabledEndDate"
@change="calculateLeaveDuration"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="请假时长(天)" prop="leaveDuration">
<el-input v-model="form.leaveDuration" disabled placeholder="自动计算请假时长" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="紧急联系人" prop="emergencyContact">
<el-input v-model="form.emergencyContact" placeholder="请输入紧急联系人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急电话" prop="emergencyPhone">
<el-input v-model="form.emergencyPhone" placeholder="请输入紧急联系电话" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="请假原因" prop="leaveReason">
<el-input v-model="form.leaveReason" type="textarea" :rows="2" placeholder="请输入请假原因" />
</el-form-item>
<el-form-item label="上传附件">
<div class="attachment-upload-container">
<el-upload
:file-list="attachmentList"
class="upload-demo"
drag
list-type="picture-card"
:on-preview="handleFilePreview"
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
:auto-upload="true"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemoveAttachmentConfirm"
:on-change="handleFileChange"
multiple
:limit="15"
:show-file-list="true"
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
>
<template #tip>
<div class="el-upload__tip">
支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传15个文件。
<span class="el-upload__tip" v-if="!form.id" style="color: #ff0000">(新增时文件会立即上传,保存请假申请后自动关联)</span>
<span class="el-upload__tip" v-else style="color: #ff0000">(编辑时文件会立即上传并关联到当前请假申请)</span>
</div>
</template>
<!-- 调整拖拽上传图标与文字在同一行、居中对齐 -->
<div class="el-upload-dragger1">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
</template>
<!-- 调整拖拽上传图标与文字在同一行、居中对齐 -->
<div class="el-upload-dragger1">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
<!-- 自定义文件卡片缩略图(含文件名) -->
<template #file="{ file }">
<div class="upload-file-card">
<!-- 缩略图区域 -->
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
<!-- 自定义文件卡片缩略图(含文件名) -->
<template #file="{ file }">
<div class="upload-file-card">
<!-- 缩略图区域 -->
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
</div>
<!-- loading 遮罩只在上传中显示 -->
<div v-if="file.status === 'uploading'" class="upload-file-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">上传中...</span>
</div>
</div>
<!-- loading 遮罩只在上传中显示 -->
<div v-if="file.status === 'uploading'" class="upload-file-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">上传中...</span>
<!-- 文件名称 -->
<div class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</div>
<!-- 操作按钮区域 - 固定在文件名下方 -->
<div class="upload-file-card-actions" v-if="file.status && file.status !== 'ready'">
<el-button-group size="small">
<el-button type="success" @click.stop="handleDownloadAttachment(file)" title="下载" :disabled="file.status !== 'success'">
<el-icon><Download /></el-icon>
</el-button>
<el-button type="danger" @click.stop="handleRemoveAttachmentConfirm(file)" title="删除">
<el-icon><Delete /></el-icon>
</el-button>
</el-button-group>
</div>
</div>
<!-- 文件名称 -->
<div class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</div>
<!-- 操作按钮区域 - 固定在文件名下方 -->
<div class="upload-file-card-actions" v-if="file.status && file.status !== 'ready'">
<el-button-group size="small">
<el-button type="success" @click.stop="handleDownloadAttachment(file)" title="下载" :disabled="file.status !== 'success'">
<el-icon><Download /></el-icon>
</el-button>
<el-button type="danger" @click.stop="handleRemoveAttachmentConfirm(file)" title="删除">
<el-icon><Delete /></el-icon>
</el-button>
</el-button-group>
</div>
</div>
</template>
</el-upload>
</div>
</el-form-item>
</el-form>
</template>
</el-upload>
</div>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 提交组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
@@ -223,12 +225,7 @@
</template>
</el-dialog>
</div>
<template #footer>
<div class="dialog-footer">
<!-- 按钮已移动到顶部 -->
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="StudentLeaveDialog" lang="ts">
@@ -288,22 +285,12 @@ interface UploadFile {
raw?: File;
}
// 定义props - 简化为只有id和type两个参数
interface Props {
id?: string | number; // 业务表主键id
type?: string; // 操作类型:add-新增,update-修改,view-查看
}
// 定义emits
interface Emits {
(e: 'update:dialog', value: { visible: boolean; title: string }): void;
(e: 'submit'): void;
(e: 'cancel'): void;
(e: 'update:buttonLoading', value: boolean): void;
(e: 'update:form', value: any): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -806,11 +793,11 @@ const handleApprovalRecord = () => {
approvalRecordRef.value.init(form.id);
};
// 提交回调
const submitCallback = async () => {
// 使用emit通知父组件关闭页面
emit('submit');
};
// // 提交回调
// const submitCallback = async () => {
// // 使用emit通知父组件关闭页面
// emit('submit');
// };
// 审批
const approvalVerifyOpen = async () => {
@@ -824,7 +811,7 @@ const submitForm = (status: string) => {
ElMessage.error('请假时间不能为空');
return;
}
console.log('status111:', status);
studentLeaveFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
// 时间数据已经在表单中,无需额外处理
@@ -846,15 +833,40 @@ const submitForm = (status: string) => {
res = await updateStudentLeave(form).finally(() => (buttonLoading.value = false));
} else {
res = await addStudentLeave(form).finally(() => (buttonLoading.value = false));
console.log('res:', res);
//给form的ID赋值
console.log('res.data.id:', res.data.id);
form.id = res.data.id;
console.log('form.id:', form.id);
}
Object.assign(form, res.data);
// 调用getStudentLeave函数获取请假数据
let response: AxiosResponse<StudentLeaveVO>;
try {
console.log('开始获取学生请假信息,ID:', form.id);
response = await getStudentLeave(form.id);
console.log('response:', response);
if (response.code === 200 && response.data) {
const leaveInfo = response.data;
console.log('获取到的学生请假信息:', leaveInfo);
// 填充请假信息表单
Object.assign(form, leaveInfo);
} else {
ElMessage.error(response.msg || '获取学生请假信息失败');
}
} catch (error) {
console.error('获取学生请假信息失败:', error);
ElMessage.error('获取学生请假信息失败,请稍后重试');
}
console.log(response);
console.log('status:', status);
if (status === 'draft') {
buttonLoading.value = false;
ElMessage.success('暂存成功');
emit('submit');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
// emit('submit');
} else {
if ((res.data.approvalStatus === 'draft' && (flowCode.value === '' || flowCode.value === null)) || props.type === 'add') {
if ((response.data.approvalStatus === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
@@ -899,13 +911,16 @@ const getStudentLeaveInfo = async (id: string | number) => {
console.log('开始获取学生请假信息,ID:', id);
const response = await getStudentLeave(id);
if (response.data.code === 200 && response.data.data) {
const leaveInfo = response.data.data;
if (response.code === 200 && response.data) {
const leaveInfo = response.data;
console.log('获取到的学生请假信息:', leaveInfo);
// 填充请假信息表单
Object.assign(form, leaveInfo);
// todo
form.status = leaveInfo.approvalStatus;
// 如果有学生ID,则获取学生信息
if (leaveInfo.studentId) {
await getStudentInfo(leaveInfo.studentId);
@@ -919,7 +934,7 @@ const getStudentLeaveInfo = async (id: string | number) => {
handleLeaveTypeChange(form.leaveTypeName);
}
} else {
ElMessage.error(response.data.msg || '获取学生请假信息失败');
ElMessage.error('获取学生请假信息失败');
}
} catch (error) {
console.error('获取学生请假信息失败:', error);
@@ -933,8 +948,8 @@ const getStudentInfo = async (studentId: string | number) => {
console.log('开始获取学生信息,学生ID:', studentId);
const response = await getStudentMsg(studentId);
if (response.data.code === 200 && response.data.data) {
const studentInfo = response.data.data;
if (response.code === 200 && response.data) {
const studentInfo = response.data;
console.log('获取到的学生信息:', studentInfo);
// 填充学生基础信息
@@ -957,7 +972,7 @@ const getStudentInfo = async (studentId: string | number) => {
studentId: form.studentId
});
} else {
ElMessage.warning(response.data.msg || '获取学生信息失败');
ElMessage.warning(response.msg || '获取学生信息失败');
}
} catch (error) {
console.error('获取学生信息失败:', error);
@@ -1065,8 +1080,8 @@ const handleAttachmentData = async () => {
}
};
// 打开对话框
const openDialog = (id?: string | number, type?: string) => {
// 初始化页面数据
const initPageData = () => {
// 重置表单数据
Object.assign(form, {
id: undefined,
@@ -1105,66 +1120,43 @@ const openDialog = (id?: string | number, type?: string) => {
// 清空附件列表
attachmentList.value = [];
// 设置对话框标题
if (type === 'add') {
dialog.title = '新增学生请假申请';
} else if (type === 'update') {
dialog.title = '编辑学生请假申请';
} else if (type === 'view') {
dialog.title = '查看学生请假申请';
} else {
dialog.title = '学生请假申请';
}
// 显示对话框
dialog.visible = true;
// 根据是否有id决定数据获取方式
if (id) {
// 根据路由参数决定数据获取方式
if (routeParams.value.id) {
// 有id时,获取学生请假信息
getStudentLeaveInfo(id);
getStudentLeaveInfo(routeParams.value.id);
} else {
// 没有id时,只获取当前用户的学生信息
getCurrentUserStudentInfo();
}
};
// 关闭对话框
const closeDialog = () => {
dialog.visible = false;
//提交回调
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
// 暴露方法给父组件
defineExpose({
openDialog,
closeDialog
});
// 组件挂载时获取请假类型列表
// 组件挂载时获取请假类型列表和初始化数据
onMounted(() => {
getLeaveTypeList();
nextTick(async () => {
routeParams.value = (proxy as any).$route.query;
getLeaveTypeList();
initPageData();
});
});
// 监听props变化,自动打开对话框
watch(
() => [props.id, props.type],
([newId, newType]) => {
if (newId !== undefined || newType) {
openDialog(newId, newType);
}
},
{ immediate: true }
);
</script>
<style scoped>
.dialog-content {
max-height: 600px;
overflow-y: auto;
height: calc(100vh - 120px);
max-height: calc(100vh - 120px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.content-section {
margin-bottom: 16px;
margin-bottom: 12px;
margin-right: 20px;
}
@@ -1172,15 +1164,35 @@ watch(
margin-bottom: 0;
}
/* 请假信息卡片样式 */
.leave-info-card {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
max-height: calc(100vh - 240px);
}
.leave-info-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.card-header {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #e4e7ed;
background: #fafafa;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0;
padding: 0;
}
.section-form {
@@ -1188,12 +1200,12 @@ watch(
}
.section-form .el-form-item {
margin-bottom: 16px;
margin-bottom: 12px;
}
/* 紧凑布局样式 */
.compact-form .el-form-item {
margin-bottom: 12px;
margin-bottom: 8px;
}
.compact-form .el-form-item__label {
@@ -1220,8 +1232,8 @@ watch(
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 16px;
margin-bottom: 16px;
padding: 12px;
margin-bottom: 12px;
margin-right: 20px;
width: calc(100% - 20px);
}
@@ -1273,7 +1285,7 @@ watch(
justify-content: space-between;
align-items: center;
/* padding: 16px 0; */
margin-bottom: 16px;
margin-bottom: 12px;
/* border-bottom: 1px solid #e9ecef; */
}
@@ -1290,27 +1302,47 @@ watch(
}
/* 滚动条样式 */
.dialog-content::-webkit-scrollbar {
.dialog-content::-webkit-scrollbar,
.leave-info-content::-webkit-scrollbar,
.attachment-upload-container::-webkit-scrollbar {
width: 6px;
}
.dialog-content::-webkit-scrollbar-track {
.dialog-content::-webkit-scrollbar-track,
.leave-info-content::-webkit-scrollbar-track,
.attachment-upload-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.dialog-content::-webkit-scrollbar-thumb {
.dialog-content::-webkit-scrollbar-thumb,
.leave-info-content::-webkit-scrollbar-thumb,
.attachment-upload-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.dialog-content::-webkit-scrollbar-thumb:hover {
.dialog-content::-webkit-scrollbar-thumb:hover,
.leave-info-content::-webkit-scrollbar-thumb:hover,
.attachment-upload-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 附件上传容器样式 */
.attachment-upload-container {
width: 100%;
max-height: 300px;
overflow-y: auto;
padding-right: 8px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background: #fafafa;
}
/* 附件上传区域内容样式 */
.attachment-upload-container .upload-demo {
padding: 12px;
min-height: 150px;
}
.attachment-upload-container .upload-demo .el-upload {
@@ -1369,12 +1401,13 @@ watch(
text-align: center;
}
/* 让附件卡片一行展示 4 个 */
/* 让附件卡片一行展示 5 个 */
.attachment-upload-container :deep(.el-upload-list--picture-card) {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(5, 1fr);
gap: 9px;
margin-top: 12px;
padding-bottom: 16px;
}
/* 确保卡片宽度自适应 */
@@ -1433,7 +1466,7 @@ watch(
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
/* background: rgba(255, 255, 255, 0.9); */
display: flex;
flex-direction: column;
align-items: center;
@@ -1503,4 +1536,7 @@ watch(
cursor: not-allowed;
background: #f5f5f5;
}
:deep(.el-card .el-card__header) {
border-bottom: 0px !important;
}
</style>
+29 -23
View File
@@ -210,7 +210,7 @@
<el-table-column label="紧急联系电话" align="center" prop="emergencyPhone" />
<el-table-column label="请假原因" align="center" prop="leaveReason" />
<!-- <el-table-column label="请假时地理位置" align="center" prop="leaveLocation" /> -->
<el-table-column label="附件OSS文件ID" align="center" prop="attachmentOssIds" />
<el-table-column label="附件OSS文件ID" align="center" prop="attachmentOssIds" width="280" />
<!-- <el-table-column label="审批状态" align="center" prop="approvalStatus" />
<el-table-column label="审批意见" align="center" prop="approvalOpinion" />
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
@@ -249,16 +249,6 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 学生请假对话框组件 -->
<StudentLeaveDialog
v-model:dialog="dialog"
:form="form"
:rules="rules"
:button-loading="buttonLoading"
:type="form.type"
@submit="submitForm"
@cancel="cancel"
/>
</div>
</template>
@@ -443,23 +433,39 @@ const handleSelectionChange = (selection: StudentLeaveVO[]) => {
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加学生请假';
// 传递type参数,标识为新增操作
form.value.type = 'add';
// reset();
// dialog.visible = true;
// dialog.title = '添加学生请假';
// // 传递type参数,标识为新增操作
// form.value.type = 'add';
// proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
query: {
type: 'add'
}
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: StudentLeaveVO) => {
reset();
// reset();
const _id = row?.id || ids.value[0];
const res = await getStudentLeave(_id);
Object.assign(form.value, res.data);
// 传递type参数,标识为修改操作
form.value.type = 'update';
dialog.visible = true;
dialog.title = '修改学生请假';
// const res = await getStudentLeave(_id);
// Object.assign(form.value, res.data);
// // 传递type参数,标识为修改操作
// form.value.type = 'update';
// form.value.id = _id;
// dialog.visible = true;
// dialog.title = '修改学生请假';
// proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
query: {
id: _id,
type: 'update'
}
});
};
/** 提交按钮 */