提交代码
This commit is contained in:
1 parent
f35359a84e
commit
0f199aa50d
7 files changed
+763
-32
No files matched your search
+1
-1
@@ -297,7 +297,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
@Override
|
||||
public List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId) {
|
||||
return baseMapper.selectUserList(new LambdaQueryWrapper<SysUser>()
|
||||
.select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName)
|
||||
.select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getPhonenumber)
|
||||
.eq(SysUser::getStatus, SystemConstants.NORMAL)
|
||||
.eq(ObjectUtil.isNotNull(deptId), SysUser::getDeptId, deptId)
|
||||
.in(CollUtil.isNotEmpty(userIds), SysUser::getUserId, userIds));
|
||||
|
||||
+28
@@ -1,6 +1,8 @@
|
||||
package org.dromara.xg.controller;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -101,4 +103,30 @@ public class XgStudentLeaveController extends BaseController {
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||
return toAjax(xgStudentLeaveService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID列表和检查日期查询学生请假信息
|
||||
*
|
||||
* @param studentIds 学生ID列表(逗号分隔)
|
||||
* @param checkDate 检查日期
|
||||
*/
|
||||
@SaCheckPermission("studentLeave:studentLeave:query")
|
||||
@GetMapping("/queryByCheckDate")
|
||||
public R<Map<Long, XgStudentLeaveVo>> queryByCheckDate(
|
||||
@RequestParam String studentIds,
|
||||
@RequestParam String checkDate) {
|
||||
try {
|
||||
List<Long> studentIdList = List.of(studentIds.split(","))
|
||||
.stream()
|
||||
.map(String::trim)
|
||||
.map(Long::valueOf)
|
||||
.toList();
|
||||
LocalDateTime checkDateTime = LocalDateTime.parse(checkDate.replace(" ", "T"));
|
||||
Map<Long, XgStudentLeaveVo> result = xgStudentLeaveService.queryStudentLeavesByCheckDate(studentIdList,
|
||||
checkDateTime);
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
return R.fail("参数格式错误:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -5,8 +5,10 @@ import org.dromara.xg.domain.bo.XgStudentLeaveBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 学生请假 - 记录学生的请假申请、审批和销假信息Service接口
|
||||
@@ -65,4 +67,13 @@ public interface IXgStudentLeaveService {
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 根据学生ID列表和检查日期查询学生请假信息
|
||||
*
|
||||
* @param studentIds 学生ID列表
|
||||
* @param checkDate 检查日期
|
||||
* @return 学生ID -> 请假信息的映射
|
||||
*/
|
||||
Map<Long, XgStudentLeaveVo> queryStudentLeavesByCheckDate(List<Long> studentIds, LocalDateTime checkDate);
|
||||
}
|
||||
+34
@@ -51,6 +51,7 @@ import org.dromara.xg.domain.XgStudentManager;
|
||||
import org.dromara.xg.domain.XgStudentManagerSource;
|
||||
import org.dromara.xg.domain.XgClassMsg;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
@@ -582,4 +583,37 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
|
||||
baseMapper.updateById(xgStudentLeave);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID列表和检查日期查询学生请假信息
|
||||
*
|
||||
* @param studentIds 学生ID列表
|
||||
* @param checkDate 检查日期
|
||||
* @return 学生ID -> 请假信息的映射
|
||||
*/
|
||||
@Override
|
||||
public Map<Long, XgStudentLeaveVo> queryStudentLeavesByCheckDate(List<Long> studentIds, LocalDateTime checkDate) {
|
||||
if (studentIds == null || studentIds.isEmpty() || checkDate == null) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<XgStudentLeave> lqw = Wrappers.lambdaQuery();
|
||||
lqw.in(XgStudentLeave::getStudentId, studentIds);
|
||||
// 查询审核状态为"waiting"(待审核)或"finish"(已完成)的请假记录
|
||||
lqw.in(XgStudentLeave::getApprovalStatus, "waiting", "finish");
|
||||
// 检查日期在请假时间段内
|
||||
lqw.le(XgStudentLeave::getStartTime, checkDate);
|
||||
lqw.ge(XgStudentLeave::getEndTime, checkDate);
|
||||
lqw.orderByDesc(XgStudentLeave::getCreateTime);
|
||||
|
||||
List<XgStudentLeave> leaveList = baseMapper.selectList(lqw);
|
||||
|
||||
// 转换为VO并按学生ID分组,每个学生只取最新的一条请假记录
|
||||
return leaveList.stream()
|
||||
.collect(Collectors.toMap(
|
||||
XgStudentLeave::getStudentId,
|
||||
leave -> MapstructUtils.convert(leave, XgStudentLeaveVo.class),
|
||||
(existing, replacement) -> existing // 保留第一条(最新的)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
+186
-22
@@ -204,8 +204,7 @@
|
||||
</div>
|
||||
|
||||
<div class="student-details">
|
||||
<p><strong>班级:</strong>{{ student.className }}</p>
|
||||
<p><strong>专业:</strong>{{ student.majorName }}</p>
|
||||
<p><strong>班级:</strong>{{ student.className }} <strong>专业:</strong>{{ student.majorName }}</p>
|
||||
</div>
|
||||
|
||||
<div class="student-score-section">
|
||||
@@ -249,6 +248,36 @@
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 请假信息区域 -->
|
||||
<div class="leave-section" v-if="getStudentLeave(student.studentId)">
|
||||
<div class="leave-header">
|
||||
<span>请假信息</span>
|
||||
<div class="leave-header-actions">
|
||||
<el-button size="small" plain @click="viewLeaveProcess(student, getStudentLeave(student.studentId))" title="查看请假流程">
|
||||
查看流程
|
||||
</el-button>
|
||||
<el-tag type="warning" size="small">请假中</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="leave-info">
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">发起时间:</span>
|
||||
<span class="leave-value">{{ formatDateTime(getStudentLeave(student.studentId)?.createTime) }}</span>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假时间:</span>
|
||||
<span class="leave-value"
|
||||
>{{ formatDateTime(getStudentLeave(student.studentId)?.startTime) }} 至
|
||||
{{ formatDateTime(getStudentLeave(student.studentId)?.endTime) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假类型:</span>
|
||||
<span class="leave-value">{{ getStudentLeave(student.studentId)?.leaveTypeName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 违纪录入区域 -->
|
||||
<div class="violation-section">
|
||||
<div class="violation-header">
|
||||
@@ -315,17 +344,30 @@
|
||||
@confirm="handleViolationConfirm"
|
||||
@close="handleViolationClose"
|
||||
/>
|
||||
|
||||
<!-- 请假流程查看弹窗 -->
|
||||
<LeaveProcessDialog
|
||||
ref="leaveProcessDialogRef"
|
||||
:visible="leaveProcessDialogVisible"
|
||||
:student-info="currentLeaveStudent"
|
||||
:leave-info="currentLeaveInfo"
|
||||
@update:visible="leaveProcessDialogVisible = $event"
|
||||
@close="handleLeaveProcessClose"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="DormHygieneCheckEditDialog" lang="ts">
|
||||
import { ref, reactive, computed, watch, toRefs, getCurrentInstance, type ComponentInternalInstance } from 'vue';
|
||||
import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
|
||||
import { ref, reactive, computed, watch, toRefs, getCurrentInstance, nextTick, type ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete, View } from '@element-plus/icons-vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import request from '@/utils/request';
|
||||
// @ts-ignore
|
||||
// @ts-expect-error - Component import without type declarations
|
||||
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
||||
// @ts-expect-error - Component import without type declarations
|
||||
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
||||
|
||||
// 组件实例
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -410,12 +452,21 @@ const studentScores = ref<any[]>([]);
|
||||
// 违纪记录数据
|
||||
const violationRecords = ref<Map<string | number, any[]>>(new Map());
|
||||
|
||||
// 学生请假数据
|
||||
const studentLeaves = ref<Map<string | number, any>>(new Map());
|
||||
|
||||
// 违纪记录弹窗相关
|
||||
const violationDialogVisible = ref(false);
|
||||
const currentStudent = ref<any>(null);
|
||||
const currentViolation = ref<any>(null);
|
||||
const currentViolationIndex = ref(-1);
|
||||
|
||||
// 请假流程弹窗相关
|
||||
const leaveProcessDialogVisible = ref(false);
|
||||
const leaveProcessDialogRef = ref();
|
||||
const currentLeaveStudent = ref<any>(null);
|
||||
const currentLeaveInfo = ref<any>(null);
|
||||
|
||||
// 字典数据
|
||||
const { xg_dorm_hygiene_grade, xg_student_attendance_status, xg_dorm_hygiene_check_type, xg_dorm_hygiene_check_level, student_discipline_status } =
|
||||
toRefs<any>(
|
||||
@@ -473,6 +524,12 @@ const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleDateString();
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
};
|
||||
|
||||
// 加载违纪类型数据
|
||||
const loadDisciplineTypes = async () => {
|
||||
try {
|
||||
@@ -496,9 +553,7 @@ const loadDisciplineTypes = async () => {
|
||||
} catch (error) {
|
||||
console.error('加载违纪类型数据失败:', error);
|
||||
console.error('加载违纪类型数据失败:', error);
|
||||
if (window.ElMessage) {
|
||||
window.ElMessage.error('加载违纪类型数据失败');
|
||||
}
|
||||
ElMessage.error('加载违纪类型数据失败');
|
||||
disciplineTypeOptions.value = [];
|
||||
}
|
||||
};
|
||||
@@ -514,9 +569,7 @@ const handleDisciplineTypeChange = () => {
|
||||
const minScore = parseFloat(selectedType.minScore);
|
||||
if (!isNaN(minScore)) {
|
||||
hygieneForm.totalScore = minScore;
|
||||
if (window.ElMessage) {
|
||||
window.ElMessage.success(`已自动填入最低扣分:${minScore}分`);
|
||||
}
|
||||
ElMessage.success(`已自动填入最低扣分:${minScore}分`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,17 +615,15 @@ const loadStudentScores = async () => {
|
||||
studentScores.value = res.data;
|
||||
console.log('学生评分数据加载成功:', res.data.length, '条');
|
||||
|
||||
// 同时加载违纪记录
|
||||
await loadViolationRecords();
|
||||
// 同时加载违纪记录和请假信息
|
||||
await Promise.all([loadViolationRecords(), loadStudentLeaves()]);
|
||||
} else {
|
||||
studentScores.value = [];
|
||||
console.log('学生评分数据为空,响应码:', res.code);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生评分数据失败:', error);
|
||||
if (window.ElMessage) {
|
||||
window.ElMessage.error('加载学生评分数据失败');
|
||||
}
|
||||
ElMessage.error('加载学生评分数据失败');
|
||||
studentScores.value = [];
|
||||
} finally {
|
||||
studentsLoading.value = false;
|
||||
@@ -602,9 +653,39 @@ const loadViolationRecords = async () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载违纪记录数据失败:', error);
|
||||
if (window.ElMessage) {
|
||||
window.ElMessage.error('加载违纪记录数据失败');
|
||||
ElMessage.error('加载违纪记录数据失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 加载学生请假信息
|
||||
const loadStudentLeaves = async () => {
|
||||
if (!hygieneForm.checkDate || studentScores.value.length === 0) return;
|
||||
|
||||
try {
|
||||
const studentIds = studentScores.value.map((student) => student.studentId).join(',');
|
||||
const res = await request({
|
||||
url: '/studentLeave/studentLeave/queryByCheckDate',
|
||||
method: 'get',
|
||||
params: {
|
||||
studentIds,
|
||||
checkDate: hygieneForm.checkDate
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
studentLeaves.value.clear();
|
||||
Object.keys(res.data).forEach((studentId) => {
|
||||
studentLeaves.value.set(Number(studentId), res.data[studentId]);
|
||||
// 如果学生有请假信息,自动设置出勤状态为3(请假)
|
||||
const student = studentScores.value.find((s) => s.studentId === Number(studentId));
|
||||
if (student) {
|
||||
student.attendanceStatus = '3';
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生请假信息失败:', error);
|
||||
ElMessage.error('加载学生请假信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -613,6 +694,11 @@ const getStudentViolations = (studentId: string | number) => {
|
||||
return violationRecords.value.get(studentId) || [];
|
||||
};
|
||||
|
||||
// 获取学生的请假信息
|
||||
const getStudentLeave = (studentId: string | number) => {
|
||||
return studentLeaves.value.get(studentId) || null;
|
||||
};
|
||||
|
||||
// 处理附件数据
|
||||
const handleAttachmentData = async () => {
|
||||
console.log('handleAttachmentData 方法被调用,attachmentIds:', hygieneForm.attachmentIds);
|
||||
@@ -805,6 +891,31 @@ const handleViolationClose = () => {
|
||||
violationDialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 查看请假流程
|
||||
const viewLeaveProcess = async (student: any, leaveInfo: any) => {
|
||||
if (!leaveInfo || !leaveInfo.id) {
|
||||
ElMessage.warning('请假信息不完整,无法查看流程');
|
||||
return;
|
||||
}
|
||||
|
||||
currentLeaveStudent.value = student;
|
||||
currentLeaveInfo.value = leaveInfo;
|
||||
leaveProcessDialogVisible.value = true;
|
||||
|
||||
// 等待弹窗打开后初始化流程数据
|
||||
await nextTick();
|
||||
if (leaveProcessDialogRef.value) {
|
||||
leaveProcessDialogRef.value.init(leaveInfo.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 请假流程弹窗关闭
|
||||
const handleLeaveProcessClose = () => {
|
||||
leaveProcessDialogVisible.value = false;
|
||||
currentLeaveStudent.value = null;
|
||||
currentLeaveInfo.value = null;
|
||||
};
|
||||
|
||||
// 文件上传相关方法
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const isValidType = /\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx|txt|mp4|avi|mov)$/i.test(file.name);
|
||||
@@ -1154,7 +1265,7 @@ watch(
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
/* margin-bottom: 12px; */
|
||||
}
|
||||
|
||||
.student-name-with-gender {
|
||||
@@ -1177,13 +1288,13 @@ watch(
|
||||
}
|
||||
|
||||
.student-details {
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
/* margin-bottom: 16px; */
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.student-details p {
|
||||
margin: 4px 0;
|
||||
/* margin: 4px 0; */
|
||||
}
|
||||
|
||||
.student-score-section {
|
||||
@@ -1249,6 +1360,59 @@ watch(
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.leave-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.leave-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.leave-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.leave-header span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.leave-info {
|
||||
background: #f8f9fa;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.leave-item {
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.leave-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.leave-label {
|
||||
color: #666;
|
||||
min-width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.leave-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.violation-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 12px;
|
||||
|
||||
+181
-9
@@ -162,8 +162,7 @@
|
||||
</div>
|
||||
|
||||
<div class="student-details">
|
||||
<p><strong>班级:</strong>{{ student.className }}</p>
|
||||
<p><strong>专业:</strong>{{ student.majorName }}</p>
|
||||
<p><strong>班级:</strong>{{ student.className }} <strong>专业:</strong>{{ student.majorName }}</p>
|
||||
</div>
|
||||
|
||||
<div class="student-score-section">
|
||||
@@ -207,6 +206,36 @@
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 请假信息区域 -->
|
||||
<div class="leave-section" v-if="getStudentLeave(student.studentId)">
|
||||
<div class="leave-header">
|
||||
<span>请假信息</span>
|
||||
<div class="leave-header-actions">
|
||||
<el-button size="small" @click="viewLeaveProcess(student, getStudentLeave(student.studentId))" title="查看请假流程">
|
||||
查看流程
|
||||
</el-button>
|
||||
<el-tag type="warning" size="small">请假中</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="leave-info">
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">发起时间:</span>
|
||||
<span class="leave-value">{{ formatDateTime(getStudentLeave(student.studentId)?.createTime) }}</span>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假时间:</span>
|
||||
<span class="leave-value"
|
||||
>{{ formatDateTime(getStudentLeave(student.studentId)?.startTime) }} 至
|
||||
{{ formatDateTime(getStudentLeave(student.studentId)?.endTime) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假类型:</span>
|
||||
<span class="leave-value">{{ getStudentLeave(student.studentId)?.leaveTypeName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 违纪录入区域 -->
|
||||
<div class="violation-section">
|
||||
<div class="violation-header">
|
||||
@@ -258,18 +287,30 @@
|
||||
@confirm="handleViolationConfirm"
|
||||
@close="handleViolationClose"
|
||||
/>
|
||||
|
||||
<!-- 请假流程查看弹窗 -->
|
||||
<LeaveProcessDialog
|
||||
ref="leaveProcessDialogRef"
|
||||
:visible="leaveProcessDialogVisible"
|
||||
:student-info="currentLeaveStudent"
|
||||
:leave-info="currentLeaveInfo"
|
||||
@update:visible="leaveProcessDialogVisible = $event"
|
||||
@close="handleLeaveProcessClose"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="DormHygieneCheckSingleDialog" lang="ts">
|
||||
import { ref, reactive, computed, watch, toRefs, getCurrentInstance, type ComponentInternalInstance } from 'vue';
|
||||
import { ref, reactive, computed, watch, toRefs, getCurrentInstance, nextTick, type ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
|
||||
import { Plus, InfoFilled, UploadFilled, Document, Loading, Download, Delete, View } from '@element-plus/icons-vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import request from '@/utils/request';
|
||||
// @ts-ignore
|
||||
// @ts-expect-error - Component import without type declarations
|
||||
import ViolationRecordDialog from './components/ViolationRecordDialog.vue';
|
||||
// @ts-expect-error - Component import without type declarations
|
||||
import LeaveProcessDialog from './components/LeaveProcessDialog.vue';
|
||||
|
||||
// 组件实例
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -334,6 +375,7 @@ const hygieneRules = reactive({
|
||||
// 学生相关数据
|
||||
const roomStudents = ref<any[]>([]);
|
||||
const violationRecords = ref<Map<string | number, any[]>>(new Map());
|
||||
const studentLeaves = ref<Map<string | number, any>>(new Map());
|
||||
|
||||
// 违纪记录弹窗相关
|
||||
const violationDialogVisible = ref(false);
|
||||
@@ -341,6 +383,12 @@ const currentStudent = ref<any>(null);
|
||||
const currentViolation = ref<any>(null);
|
||||
const currentViolationIndex = ref(-1);
|
||||
|
||||
// 请假流程弹窗相关
|
||||
const leaveProcessDialogVisible = ref(false);
|
||||
const leaveProcessDialogRef = ref();
|
||||
const currentLeaveStudent = ref<any>(null);
|
||||
const currentLeaveInfo = ref<any>(null);
|
||||
|
||||
// 字典数据
|
||||
const { xg_dorm_hygiene_grade, xg_student_attendance_status } = toRefs<any>(
|
||||
(proxy as any)?.useDict('xg_dorm_hygiene_grade', 'xg_student_attendance_status')
|
||||
@@ -439,6 +487,9 @@ const loadRoomStudents = async () => {
|
||||
attendance_status: '1',
|
||||
student_grade: 'A'
|
||||
}));
|
||||
|
||||
// 加载学生请假信息
|
||||
await loadStudentLeaves();
|
||||
} else {
|
||||
roomStudents.value = [];
|
||||
}
|
||||
@@ -451,6 +502,38 @@ const loadRoomStudents = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载学生请假信息
|
||||
const loadStudentLeaves = async () => {
|
||||
if (!props.checkInfo?.checkDate || roomStudents.value.length === 0) return;
|
||||
|
||||
try {
|
||||
const studentIds = roomStudents.value.map((student) => student.studentId).join(',');
|
||||
const res = await request({
|
||||
url: '/studentLeave/studentLeave/queryByCheckDate',
|
||||
method: 'get',
|
||||
params: {
|
||||
studentIds,
|
||||
checkDate: props.checkInfo.checkDate
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
studentLeaves.value.clear();
|
||||
Object.keys(res.data).forEach((studentId) => {
|
||||
studentLeaves.value.set(Number(studentId), res.data[studentId]);
|
||||
// 如果学生有请假信息,自动设置出勤状态为3(请假)
|
||||
const student = roomStudents.value.find((s) => s.studentId === Number(studentId));
|
||||
if (student) {
|
||||
student.attendance_status = '3';
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载学生请假信息失败:', error);
|
||||
ElMessage.error('加载学生请假信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 获取违纪类型说明
|
||||
const selectedDisciplineTypeRemark = computed(() => {
|
||||
if (hygieneForm.disciplineTypeId) {
|
||||
@@ -465,6 +548,17 @@ const getStudentViolations = (studentId: string | number) => {
|
||||
return violationRecords.value.get(studentId) || [];
|
||||
};
|
||||
|
||||
// 获取学生的请假信息
|
||||
const getStudentLeave = (studentId: string | number) => {
|
||||
return studentLeaves.value.get(studentId) || null;
|
||||
};
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
};
|
||||
|
||||
// 添加违纪记录
|
||||
const addViolationRecord = (student: any) => {
|
||||
currentStudent.value = student;
|
||||
@@ -529,6 +623,31 @@ const handleViolationClose = () => {
|
||||
violationDialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 查看请假流程
|
||||
const viewLeaveProcess = async (student: any, leaveInfo: any) => {
|
||||
if (!leaveInfo || !leaveInfo.id) {
|
||||
ElMessage.warning('请假信息不完整,无法查看流程');
|
||||
return;
|
||||
}
|
||||
|
||||
currentLeaveStudent.value = student;
|
||||
currentLeaveInfo.value = leaveInfo;
|
||||
leaveProcessDialogVisible.value = true;
|
||||
|
||||
// 等待弹窗打开后初始化流程数据
|
||||
await nextTick();
|
||||
if (leaveProcessDialogRef.value) {
|
||||
leaveProcessDialogRef.value.init(leaveInfo.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 请假流程弹窗关闭
|
||||
const handleLeaveProcessClose = () => {
|
||||
leaveProcessDialogVisible.value = false;
|
||||
currentLeaveStudent.value = null;
|
||||
currentLeaveInfo.value = null;
|
||||
};
|
||||
|
||||
// 处理宿舍总分变化
|
||||
const handleTotalScoreChange = () => {
|
||||
// 移除联动逻辑,仅做数值验证
|
||||
@@ -949,7 +1068,7 @@ watch(
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
/* margin-bottom: 12px; */
|
||||
}
|
||||
|
||||
.student-name-with-gender {
|
||||
@@ -972,13 +1091,13 @@ watch(
|
||||
}
|
||||
|
||||
.student-details {
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
/* margin-bottom: 16px; */
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.student-details p {
|
||||
margin: 4px 0;
|
||||
/* margin: 4px 0; */
|
||||
}
|
||||
|
||||
.student-score-section {
|
||||
@@ -1119,6 +1238,59 @@ watch(
|
||||
max-height: 32px !important;
|
||||
}
|
||||
|
||||
.leave-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.leave-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.leave-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.leave-header span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.leave-info {
|
||||
background: #f8f9fa;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.leave-item {
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.leave-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.leave-label {
|
||||
color: #666;
|
||||
min-width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.leave-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.violation-section {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 12px;
|
||||
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="请假流程信息" width="800px" append-to-body @close="handleClose">
|
||||
<div v-loading="loading" class="leave-process-container">
|
||||
<div v-if="studentInfo" class="student-info-header">
|
||||
<div class="student-basic">
|
||||
<h4>{{ studentInfo.studentName }}</h4>
|
||||
<span class="student-no">{{ studentInfo.studentNo }}</span>
|
||||
</div>
|
||||
<div class="leave-basic">
|
||||
<el-tag type="warning" size="small">请假中</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="leaveInfo" class="leave-detail-info">
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假类型:</span>
|
||||
<span class="leave-value">{{ leaveInfo.leaveTypeName }}</span>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假时间:</span>
|
||||
<span class="leave-value"> {{ formatDateTime(leaveInfo.startTime) }} 至 {{ formatDateTime(leaveInfo.endTime) }} </span>
|
||||
</div>
|
||||
<div class="leave-item">
|
||||
<span class="leave-label">请假原因:</span>
|
||||
<span class="leave-value">{{ leaveInfo.reason || '无' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="approval-records">
|
||||
<h5>审批记录</h5>
|
||||
<el-table :data="approvalList" border style="width: 100%">
|
||||
<el-table-column type="index" label="序号" align="center" width="60" />
|
||||
<el-table-column prop="nodeName" label="任务名称" align="center" />
|
||||
<el-table-column prop="approveName" label="办理人" align="center">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.approveName">
|
||||
<el-tag v-for="(item, index) in scope.row.approveName.split(',')" :key="index" type="success" size="small">
|
||||
{{ item }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag type="info" size="small">无</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="approverPhone" label="办理人电话" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.approverPhone && scope.row.approverPhone !== '未录入'">{{ scope.row.approverPhone }}</span>
|
||||
<el-tag v-else type="info" size="small">未录入</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="flowStatus" label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.flowStatus)" size="small">
|
||||
{{ getStatusText(scope.row.flowStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" label="审批意见" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.message || '无' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="开始时间" width="160" align="center" />
|
||||
<el-table-column prop="updateTime" label="结束时间" width="160" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.updateTime || '进行中' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="LeaveProcessDialog" lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 组件属性
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
studentInfo: any;
|
||||
leaveInfo: any;
|
||||
}
|
||||
|
||||
// 组件事件
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const approvalList = ref<any[]>([]);
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
});
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
};
|
||||
|
||||
// 获取状态类型
|
||||
const getStatusType = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pass':
|
||||
return 'success';
|
||||
case 'waiting':
|
||||
return 'warning';
|
||||
case 'reject':
|
||||
return 'danger';
|
||||
case 'draft':
|
||||
return 'info';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'pass':
|
||||
return '通过';
|
||||
case 'waiting':
|
||||
return '待审批';
|
||||
case 'reject':
|
||||
return '拒绝';
|
||||
case 'draft':
|
||||
return '草稿';
|
||||
default:
|
||||
return status || '未知';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户电话信息
|
||||
const getUserPhones = async (userIds: string[]) => {
|
||||
if (!userIds || userIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
// 过滤掉空值和重复值
|
||||
const validUserIds = [...new Set(userIds.filter((id) => id && id.trim()))];
|
||||
if (validUserIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
url: `/system/user/optionselect?userIds=${validUserIds.join(',')}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
const phoneMap: Record<string, string> = {};
|
||||
response.data.forEach((user: any) => {
|
||||
if (user.userId) {
|
||||
// 如果用户有手机号则使用,否则标记为"未录入"
|
||||
phoneMap[user.userId.toString()] = user.phonenumber || '未录入';
|
||||
}
|
||||
});
|
||||
return phoneMap;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户电话失败:', error);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// 加载审批记录
|
||||
const loadApprovalRecords = async (businessId: string | number) => {
|
||||
if (!businessId) {
|
||||
console.warn('businessId为空,无法加载审批记录');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await request({
|
||||
url: `/workflow/instance/flowHisTaskList/${businessId}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data?.list) {
|
||||
approvalList.value = response.data.list;
|
||||
console.log('审批记录加载成功:', approvalList.value.length, '条');
|
||||
|
||||
// 提取所有办理人ID
|
||||
const approverIds: string[] = [];
|
||||
approvalList.value.forEach((item: any) => {
|
||||
if (item.approver) {
|
||||
// approver可能包含多个用户ID,用逗号分隔
|
||||
const ids = item.approver.split(',').map((id: string) => id.trim());
|
||||
approverIds.push(...ids);
|
||||
}
|
||||
});
|
||||
|
||||
// 获取办理人电话信息
|
||||
if (approverIds.length > 0) {
|
||||
const phoneMap = await getUserPhones(approverIds);
|
||||
|
||||
// 为每条审批记录设置办理人电话
|
||||
approvalList.value.forEach((item: any) => {
|
||||
if (item.approver) {
|
||||
const ids = item.approver.split(',').map((id: string) => id.trim());
|
||||
const phones = ids.map((id: string) => phoneMap[id] || '未录入');
|
||||
item.approverPhone = phones.length > 0 ? phones.join(', ') : '未录入';
|
||||
} else {
|
||||
item.approverPhone = '未录入';
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
approvalList.value = [];
|
||||
console.warn('审批记录数据为空');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载审批记录失败:', error);
|
||||
approvalList.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
// 清空数据
|
||||
approvalList.value = [];
|
||||
};
|
||||
|
||||
// 对外暴露方法
|
||||
const init = async (businessId: string | number) => {
|
||||
console.log('初始化请假流程对话框,businessId:', businessId);
|
||||
await loadApprovalRecords(businessId);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
init
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.leave-process-container {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.student-info-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.student-basic h4 {
|
||||
margin: 0 0 4px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.leave-detail-info {
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.leave-item {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.leave-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.leave-label {
|
||||
color: #666;
|
||||
min-width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.leave-value {
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.approval-records h5 {
|
||||
margin: 0 0 16px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user