feat(xg): 请假审批修复、点名今日汇总与寝室扣分条例脚本

同步请假/销假审批上下文与状态、宿舍点名今日汇总与假期留宿标识,并补充寝室检查扣分条例生产 SQL 与执行说明。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
“wuzhen”andCursor committed 2026-10-20 18:01:12 +08:00
1 parent 3e6349112c
commit 7cea0566ac
48 files changed
+1966 -116

No files matched your search

+8
View File
@@ -38,6 +38,14 @@ export const flowHisTaskList = (businessId: string | number) => {
});
};
/** 办理页审批上下文:是否可审批、taskId、建议业务状态 */
export const approvalContext = (businessId: string | number) => {
return request({
url: `/workflow/instance/approvalContext/${businessId}`,
method: 'get'
});
};
/**
* 分页查询当前登录人单据
* @param query
@@ -62,6 +62,8 @@ export interface DormRollCallVO {
presentCount: number;
/** 请假人数 */
leaveCount: number;
/** 假期留宿人数 */
holidayStayCount: number;
/** 不在人数 */
absentCount: number;
/** 空床位 */
@@ -106,7 +108,7 @@ export interface DormRollCallDetailVO {
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 点名状态(0在寝/1请假/2不在/3空床位) */
/** 点名状态(0在寝/1请假/2不在/3空床位/4假期留宿) */
rollCallStatus: string;
/** 请假ID */
leaveId?: string | number;
@@ -116,6 +118,14 @@ export interface DormRollCallDetailVO {
leaveStartTime?: string;
/** 请假结束时间 */
leaveEndTime?: string;
/** 假期留宿ID */
holidayStayId?: string | number;
/** 假期类型 */
holidayType?: string;
/** 留宿开始日期 */
holidayStartDate?: string;
/** 留宿结束日期 */
holidayEndDate?: string;
}
/**
@@ -134,6 +144,8 @@ export interface RoomRollCallStatusVO {
presentCount: number;
/** 请假人数 */
leaveCount: number;
/** 假期留宿人数 */
holidayStayCount: number;
/** 不在人数 */
absentCount: number;
/** 空床位 */
@@ -182,6 +194,8 @@ export interface DormRollCallForm extends BaseEntity {
presentCount?: number;
/** 请假人数 */
leaveCount?: number;
/** 假期留宿人数 */
holidayStayCount?: number;
/** 不在人数 */
absentCount?: number;
/** 空床位 */
@@ -222,7 +236,7 @@ export interface DormRollCallDetailForm {
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 点名状态(0在寝/1请假/2不在/3空床位) */
/** 点名状态(0在寝/1请假/2不在/3空床位/4假期留宿) */
rollCallStatus?: string;
/** 请假ID */
leaveId?: string | number;
@@ -232,4 +246,12 @@ export interface DormRollCallDetailForm {
leaveStartTime?: string;
/** 请假结束时间 */
leaveEndTime?: string;
/** 假期留宿ID */
holidayStayId?: string | number;
/** 假期类型 */
holidayType?: string;
/** 留宿开始日期 */
holidayStartDate?: string;
/** 留宿结束日期 */
holidayEndDate?: string;
}
@@ -19,7 +19,9 @@ const props = defineProps({
status: propTypes.string.def(''),
pageType: propTypes.string.def(''),
buttonLoading: propTypes.bool.def(false),
id: propTypes.string.def('') || propTypes.number.def()
id: propTypes.string.def('') || propTypes.number.def(),
/** 当前用户是否有可办审批待办(优先于 status===waiting) */
canApprove: propTypes.bool.def(false)
});
const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
//暂存,提交
@@ -43,8 +45,11 @@ const submitButtonShow = computed(() => {
);
});
//校验审批按钮是否显示
// 审批按钮:有待办可审,或传统 pageType=approval + waiting
const approvalButtonShow = computed(() => {
if (props.canApprove) {
return true;
}
return props.pageType === 'approval' && props.status && props.status === 'waiting';
});
@@ -13,6 +13,7 @@
<div class="summary-bar">
<span>在寝: <b style="color: #67c23a">{{ rollCallData.presentCount }}</b></span>
<span>请假: <b style="color: #e6a23c">{{ rollCallData.leaveCount }}</b></span>
<span>假期留宿: <b style="color: #409eff">{{ rollCallData.holidayStayCount || 0 }}</b></span>
<span>不在: <b style="color: #f56c6c">{{ rollCallData.absentCount }}</b></span>
<span>空床位: <b style="color: #909399">{{ rollCallData.emptyCount }}</b></span>
</div>
@@ -21,17 +22,19 @@
<el-table-column label="床位" prop="bedName" width="70" />
<el-table-column label="姓名" prop="studentName" width="80" />
<el-table-column label="学号" prop="studentNo" width="120" />
<el-table-column label="状态" prop="rollCallStatus" width="80">
<el-table-column label="状态" prop="rollCallStatus" width="90">
<template #default="scope">
<el-tag v-if="scope.row.rollCallStatus === '0'" type="success" size="small">在寝</el-tag>
<el-tag v-else-if="scope.row.rollCallStatus === '1'" type="warning" size="small">请假</el-tag>
<el-tag v-else-if="scope.row.rollCallStatus === '4'" type="primary" size="small">假期留宿</el-tag>
<el-tag v-else-if="scope.row.rollCallStatus === '2'" type="danger" size="small">不在</el-tag>
<el-tag v-else type="info" size="small">空床位</el-tag>
</template>
</el-table-column>
<el-table-column label="请假信息" min-width="120">
<el-table-column label="备注信息" min-width="120">
<template #default="scope">
<span v-if="scope.row.leaveTypeName">{{ scope.row.leaveTypeName }}</span>
<span v-else-if="scope.row.holidayType || scope.row.holidayStayId">假期留宿{{ scope.row.holidayType ? `(${scope.row.holidayType})` : '' }}</span>
<span v-else>-</span>
</template>
</el-table-column>
@@ -18,6 +18,11 @@
<span style="color: #e6a23c">{{ scope.row.leaveCount }}</span>
</template>
</el-table-column>
<el-table-column label="留宿" prop="holidayStayCount" width="60" align="center">
<template #default="scope">
<span style="color: #409eff">{{ scope.row.holidayStayCount || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="不在" prop="absentCount" width="60" align="center">
<template #default="scope">
<span style="color: #f56c6c">{{ scope.row.absentCount }}</span>
@@ -18,6 +18,9 @@
<div v-if="bed.leaveId" class="leave-info">
<el-tag type="warning" size="small">{{ bed.leaveTypeName || '请假' }}</el-tag>
</div>
<div v-else-if="bed.holidayStayId" class="leave-info">
<el-tag type="primary" size="small">假期留宿</el-tag>
</div>
<div class="status-label">
{{ getStatusLabel(bed.rollCallStatus) }}
</div>
@@ -28,6 +31,7 @@
<div v-if="bedDetails.length > 0" class="summary-bar">
<span>在寝: <b style="color: #67c23a">{{ summary.present }}</b></span>
<span>请假: <b style="color: #e6a23c">{{ summary.leave }}</b></span>
<span>假期留宿: <b style="color: #409eff">{{ summary.holidayStay }}</b></span>
<span>不在: <b style="color: #f56c6c">{{ summary.absent }}</b></span>
<span>空床位: <b style="color: #909399">{{ summary.empty }}</b></span>
</div>
@@ -73,6 +77,7 @@ interface BedItem extends DormRollCallDetailForm {
bedId: string | number;
originalStatus?: string;
isLeaveAuto?: boolean;
isHolidayStayAuto?: boolean;
}
const loading = ref(false);
@@ -80,21 +85,23 @@ const submitting = ref(false);
const bedDetails = ref<BedItem[]>([]);
const summary = computed(() => {
let present = 0, leave = 0, absent = 0, empty = 0;
let present = 0, leave = 0, holidayStay = 0, absent = 0, empty = 0;
bedDetails.value.forEach(bed => {
const status = bed.rollCallStatus;
if (status === '0') present++;
else if (status === '1') leave++;
else if (status === '4') holidayStay++;
else if (status === '2') absent++;
else if (status === '3') empty++;
});
return { present, leave, absent, empty };
return { present, leave, holidayStay, absent, empty };
});
const getBedCardClass = (bed: BedItem) => {
const status = bed.rollCallStatus;
if (status === '0') return 'bed-present';
if (status === '1') return 'bed-leave';
if (status === '4') return 'bed-holiday-stay';
if (status === '2') return 'bed-absent';
if (status === '3') return 'bed-empty';
return '';
@@ -103,6 +110,7 @@ const getBedCardClass = (bed: BedItem) => {
const getStatusLabel = (status?: string) => {
if (status === '0') return '在寝';
if (status === '1') return '请假';
if (status === '4') return '假期留宿';
if (status === '2') return '不在';
if (status === '3') return '空床位';
return '';
@@ -113,6 +121,10 @@ const handleBedClick = (bed: BedItem) => {
ElMessage.info('该学生处于请假状态,不可修改');
return;
}
if (bed.isHolidayStayAuto) {
ElMessage.info('该学生处于假期留宿状态,不可修改');
return;
}
if (!bed.studentId) {
return;
}
@@ -201,6 +213,13 @@ const loadBedDetails = async () => {
bed.leaveTypeName = item.leaveInfo.leaveTypeName;
bed.leaveStartTime = item.leaveInfo.leaveStartTime;
bed.leaveEndTime = item.leaveInfo.leaveEndTime;
} else if (item.holidayStayInfo) {
bed.rollCallStatus = '4';
bed.isHolidayStayAuto = true;
bed.holidayStayId = item.holidayStayInfo.holidayStayId;
bed.holidayType = item.holidayStayInfo.holidayType;
bed.holidayStartDate = item.holidayStayInfo.applyStartDate;
bed.holidayEndDate = item.holidayStayInfo.applyEndDate;
}
bed.originalStatus = bed.rollCallStatus;
@@ -247,7 +266,11 @@ const handleSubmit = async () => {
leaveId: bed.leaveId,
leaveTypeName: bed.leaveTypeName,
leaveStartTime: bed.leaveStartTime,
leaveEndTime: bed.leaveEndTime
leaveEndTime: bed.leaveEndTime,
holidayStayId: bed.holidayStayId,
holidayType: bed.holidayType,
holidayStartDate: bed.holidayStartDate,
holidayEndDate: bed.holidayEndDate
}));
const submitData: DormRollCallForm = {
@@ -259,6 +282,7 @@ const handleSubmit = async () => {
totalBeds: bedDetails.value.length,
presentCount: summary.value.present,
leaveCount: summary.value.leave,
holidayStayCount: summary.value.holidayStay,
absentCount: summary.value.absent,
emptyCount: summary.value.empty,
details
@@ -325,6 +349,12 @@ watch(() => props.visible, (val) => {
cursor: not-allowed;
}
.bed-holiday-stay {
background: #ecf5ff;
border-color: #409eff;
cursor: not-allowed;
}
.bed-absent {
background: #ffebee;
border-color: #f56c6c;
@@ -76,6 +76,11 @@
<span style="color: #e6a23c">{{ scope.row.leaveCount }}</span>
</template>
</el-table-column>
<el-table-column label="假期留宿" align="center" prop="holidayStayCount" width="90">
<template #default="scope">
<span style="color: #409eff">{{ scope.row.holidayStayCount || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="不在" align="center" prop="absentCount" width="70">
<template #default="scope">
<span style="color: #f56c6c">{{ scope.row.absentCount }}</span>
@@ -36,6 +36,7 @@
<div class="room-stats">
<span class="stat-item stat-present" title="在寝">{{ room.presentCount }}</span>
<span class="stat-item stat-leave" title="请假">{{ room.leaveCount }}</span>
<span class="stat-item stat-holiday-stay" title="假期留宿">{{ room.holidayStayCount || 0 }}</span>
<span class="stat-item stat-absent" title="不在">{{ room.absentCount }}</span>
<span class="stat-item stat-empty" title="空床位">{{ room.emptyCount }}</span>
</div>
@@ -248,6 +249,11 @@ onMounted(() => {
color: #e6a23c;
}
.stat-holiday-stay {
background: #ecf5ff;
color: #409eff;
}
.stat-absent {
background: #ffebee;
color: #f56c6c;
+31
View File
@@ -12,6 +12,7 @@
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
:canApprove="canApprove"
/>
</div>
</div>
@@ -309,6 +310,7 @@ import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import { addStudentLeave, getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers, getCollegeLeaders } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
import { startWorkFlow } from '@/api/workflow/task';
import { approvalContext } from '@/api/workflow/instance';
import { StudentLeaveForm, StudentLeaveVO } from '@/api/workflow/studentLeave/studentLeave/types';
import { StudentMsgVO } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
@@ -474,6 +476,8 @@ const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
/** 当前用户是否可审批(以流程待办为准) */
const canApprove = ref(false);
const leaveFormRef = ref<ElFormInstance>();
@@ -1177,6 +1181,31 @@ const openExternalMap = () => {
// }
// );
/**
* 以流程引擎待办为准刷新审批能力(覆盖 draft/back 残留导致无按钮)
*/
const ensureApprovalCapability = async (leaveId: string | number) => {
try {
canApprove.value = false;
const resp = await approvalContext(leaveId);
const ctx = resp?.data || {};
if (ctx.suggestedBusinessStatus) {
form.approvalStatus = ctx.suggestedBusinessStatus;
form.status = ctx.suggestedBusinessStatus;
}
if (ctx.canApprove) {
canApprove.value = true;
routeParams.value = {
...routeParams.value,
type: 'approval',
taskId: ctx.taskId != null ? String(ctx.taskId) : routeParams.value.taskId
};
}
} catch (e) {
console.warn('请假审批上下文检查失败(不影响查看):', e);
}
};
// 根据id获取学生请假信息
const getStudentLeaveInfo = async (id: string | number) => {
try {
@@ -1193,6 +1222,8 @@ const getStudentLeaveInfo = async (id: string | number) => {
// todo
form.status = leaveInfo.approvalStatus;
await ensureApprovalCapability(id);
// 如果有学生ID,则获取学生信息
if (leaveInfo.studentId) {
await getStudentInfo(leaveInfo.studentId);
+30
View File
@@ -12,6 +12,7 @@
:id="cancelForm.id"
:status="leaveInfo.status"
:pageType="routeParams.type"
:canApprove="canApprove"
/>
</div>
</div>
@@ -237,6 +238,7 @@ import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { AxiosResponse } from 'axios';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import { approvalContext } from '@/api/workflow/instance';
import { getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
import { getStudentMsg } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
import { startWorkFlow } from '@/api/workflow/task';
@@ -369,6 +371,7 @@ const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
const canApprove = ref(false);
const submitFormData = ref<StartProcessBo>({
businessId: '',
@@ -786,6 +789,10 @@ const getStudentLeaveInfo = async (id: string | number) => {
console.log('销假表单初始化完成:', cancelForm);
if (cancelForm.id) {
await ensureApprovalCapability(cancelForm.id);
}
// 如果有学生ID,则获取学生信息
if (leaveData.studentId) {
await getStudentInfo(leaveData.studentId);
@@ -955,6 +962,29 @@ const handleCancelAttachmentData = async () => {
}
};
/** 以流程引擎待办为准刷新销假审批能力 */
const ensureApprovalCapability = async (cancelLeaveId: string | number) => {
try {
canApprove.value = false;
const resp = await approvalContext(cancelLeaveId);
const ctx = resp?.data || {};
if (ctx.suggestedBusinessStatus) {
leaveInfo.status = ctx.suggestedBusinessStatus;
cancelForm.cancelStatus = ctx.suggestedBusinessStatus;
}
if (ctx.canApprove) {
canApprove.value = true;
routeParams.value = {
...routeParams.value,
type: 'approval',
taskId: ctx.taskId != null ? String(ctx.taskId) : routeParams.value.taskId
};
}
} catch (e) {
console.warn('销假审批上下文检查失败(不影响查看):', e);
}
};
// 初始化页面数据
const initPageData = () => {
// 重置销假表单数据