From 28fc65c34d1642b007dc45b83782e23be58d718c Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期三, 08 七月 2026 16:43:58 +0800
Subject: [PATCH] 已进行排班并且人员已打卡的前提下,对应的班次配置不能编辑删除。
---
src/views/personnelManagement/classsSheduling/index.vue | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 265 insertions(+), 25 deletions(-)
diff --git a/src/views/personnelManagement/classsSheduling/index.vue b/src/views/personnelManagement/classsSheduling/index.vue
index e932f50..59e65b4 100644
--- a/src/views/personnelManagement/classsSheduling/index.vue
+++ b/src/views/personnelManagement/classsSheduling/index.vue
@@ -49,13 +49,11 @@
<div class="search-actions">
<el-button size="small"
type="primary"
- @click="refreshTable()"
- :icon="Search">
+ @click="refreshTable()">
鏌ヨ
</el-button>
<el-button size="small"
@click="refresh()"
- :icon="Refresh"
style="margin-left: 8px">
閲嶇疆
</el-button>
@@ -63,22 +61,19 @@
<div class="search-buttons">
<el-button size="small"
type="primary"
- @click="configTime"
- :icon="Setting">
+ @click="configTime">
鐝閰嶇疆
</el-button>
<el-button size="small"
type="success"
@click="handleDown"
:loading="downLoading"
- :icon="Download"
style="margin-left: 8px">
瀵煎嚭
</el-button>
<el-button size="small"
type="warning"
@click="schedulingVisible = true"
- :icon="Calendar"
style="margin-left: 8px">
鎺掔彮
</el-button>
@@ -147,19 +142,20 @@
:key="'d' + i">
<el-dropdown trigger="click"
placement="bottom"
- @command="(e) => handleCommand(e, m)"
+ :disabled="isCellBlockedByLeaveDate(item, m)"
+ @command="(e) => handleCommand(e, item, m)"
class="shift-dropdown">
<div class="shift-box"
:class="{
'shift-box-early': m.shift === '鏃╃彮',
'shift-box-mid': m.shift === '涓彮',
'shift-box-night': m.shift === '澶滅彮',
- 'shift-box-rest': m.shift === '浼戞伅',
+ 'shift-box-rest': !m.shift || m.shift === '浼戞伅',
'shift-box-leave': m.shift === '璇峰亣',
'shift-box-other': m.shift === '澶�11',
'shift-box-business': m.shift === '澶�12',
}">
- <span class="shift-text">{{ getShiftNameByValue(m.shift) || '鈥�' }}</span>
+ <span class="shift-text">{{ getShiftNameByValue(m.shift) || '浼戞伅' }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
@@ -204,7 +200,7 @@
</div> -->
<div class="user-total">
<span class="total-label">鍚堣鍑哄嫟:</span>
- <span class="total-value">{{ item.work_time }}澶�</span>
+ <span class="total-value">{{ item.year.totalYearAttendance }}澶�</span>
</div>
</div>
</div>
@@ -276,6 +272,23 @@
<div class="search_thing">
<div class="search_label"
style="width: 90px">
+ <span style="color: red; margin-right: 4px">*</span>閮ㄩ棬锛�
+ </div>
+ <div class="search_input">
+ <el-tree-select v-model="schedulingQuery.deptId"
+ :data="deptOptions"
+ :props="{ value: 'id', label: 'label', children: 'children' }"
+ value-key="id"
+ placeholder="璇烽�夋嫨閮ㄩ棬"
+ check-strictly
+ clearable
+ @change="onSchedulingDeptChange"
+ style="width: 100%" />
+ </div>
+ </div>
+ <div class="search_thing">
+ <div class="search_label"
+ style="width: 90px">
<span style="color: red; margin-right: 4px">*</span>浜哄憳鍚嶇О锛�
</div>
<div class="search_input">
@@ -284,7 +297,8 @@
style="width: 100%"
multiple
clearable
- collapse-tags>
+ collapse-tags
+ :disabled="!schedulingQuery.deptId">
<el-option v-for="item in personList"
:key="item.id"
:label="item.staffName"
@@ -301,8 +315,9 @@
<div class="search_input">
<el-select v-model="schedulingQuery.shift"
placeholder="璇烽�夋嫨"
- style="width: 100%">
- <el-option v-for="item in classType"
+ style="width: 100%"
+ :disabled="!schedulingQuery.deptId">
+ <el-option v-for="item in schedulingClassType"
:key="item.id"
:label="getShiftNameByValue(item.shift)"
:value="item.id">
@@ -323,7 +338,14 @@
</template>
<script setup>
- import { ref, reactive, onMounted, getCurrentInstance } from "vue";
+ import {
+ ref,
+ reactive,
+ computed,
+ watch,
+ onMounted,
+ getCurrentInstance,
+ } from "vue";
import { useRouter } from "vue-router";
import {
page,
@@ -333,6 +355,7 @@
update,
staffOnJobListPage,
} from "@/api/personnelManagement/class";
+ import { findStaffLeaveListPage } from "@/api/personnelManagement/staffLeave.js";
import { deptTreeSelect } from "@/api/system/user.js";
import { getAttendanceRules } from "@/api/personnelManagement/attendanceRules.js";
import { useDict } from "@/utils/dict";
@@ -381,6 +404,8 @@
// 浜哄憳鍒楄〃
const personList = ref([]);
+ const personListAll = ref([]);
+ const leaveDateMap = ref({});
// 鍔犺浇鐘舵��
const loading = ref(false);
@@ -388,8 +413,18 @@
// 鎺掔彮鏌ヨ鏉′欢
const schedulingQuery = reactive({
week: "",
+ deptId: "",
userId: null,
shift: "",
+ });
+
+ const schedulingClassType = computed(() => {
+ if (!schedulingQuery.deptId) return classType.value || [];
+ return (classType.value || []).filter(item => {
+ if (["998", "999"].includes(String(item?.id))) return true;
+ const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id;
+ return String(itemDeptId) === String(schedulingQuery.deptId);
+ });
});
// 鍒楄〃鏁版嵁
@@ -598,6 +633,10 @@
proxy.$modal.msgError("璇烽�夋嫨鍛ㄦ");
return;
}
+ if (!schedulingQuery.deptId) {
+ proxy.$modal.msgError("璇烽�夋嫨閮ㄩ棬");
+ return;
+ }
let time = schedulingQuery.week.getTime();
// 鏍煎紡鍖栨棩鏈熶负 YYYY-MM-DD 鏍煎紡
@@ -621,6 +660,15 @@
proxy.$modal.msgError("璇烽�夋嫨鐝");
return;
}
+ const blockedEmployees = (personList.value || []).filter(item => {
+ const leaveDate = getEmployeeLeaveDate(item?.id);
+ const weekEndDate = getSchedulingWeekEndDate();
+ return leaveDate && weekEndDate && weekEndDate.getTime() > leaveDate.getTime();
+ });
+ if (blockedEmployees.length) {
+ proxy.$modal.msgError("瀛樺湪宸茶秴杩囩鑱屾棩鏈熺殑浜哄憳锛屼笉鑳界户缁帓鐝�");
+ return;
+ }
loading.value = true;
add({
startWeek,
@@ -633,8 +681,10 @@
proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
schedulingVisible.value = false;
schedulingQuery.week = "";
+ schedulingQuery.deptId = "";
schedulingQuery.userId = null;
schedulingQuery.shift = "";
+ personList.value = [];
refresh();
})
.catch(err => {
@@ -674,10 +724,12 @@
})
.then(res => {
proxy.$modal.msgSuccess("涓嬭浇鎴愬姛");
- downLoading.value = false;
- const blob = new Blob([res], {
- type: "application/force-download",
- });
+ const blob =
+ res instanceof Blob
+ ? res
+ : new Blob([res], {
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ });
let fileName = "";
if (query.month) {
fileName = year + "-" + query.month + " 鐝淇℃伅";
@@ -686,15 +738,20 @@
}
proxy.$download.saveAs(blob, fileName + ".xlsx");
})
- .catch(err => {
+ .catch(err => {})
+ .finally(() => {
downLoading.value = false;
});
};
// 澶勭悊鍛戒护
- const handleCommand = (e, m) => {
+ const handleCommand = (e, row, cell) => {
+ if (isCellBlockedByLeaveDate(row, cell)) {
+ proxy.$modal.msgError("璇ュ憳宸ュ凡瓒呭嚭绂昏亴鏃ユ湡锛屼笉鑳界户缁帓鐝�");
+ return;
+ }
// if (e != m.shift) {
update({
- id: m.id,
+ id: cell.id,
personalAttendanceLocationConfigId: e,
}).then(res => {
proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
@@ -710,7 +767,18 @@
// 鏌ヨ瑙勫垯鍒楄〃
const fetchData = () => {
getAttendanceRules({ current: -1, size: -1 }).then(res => {
- classType.value = res.data.records;
+ const records = Array.isArray(res?.data?.records) ? res.data.records : [];
+ const appendList = [
+ { id: 998, shift: "浼戞伅" },
+ { id: 999, shift: "璇峰亣" },
+ ];
+ const exists = new Set(records.map(r => String(r?.id)));
+ appendList.forEach(item => {
+ if (!exists.has(String(item.id))) {
+ records.push(item);
+ }
+ });
+ classType.value = records;
});
};
// 鑾峰彇鐢ㄦ埛
@@ -725,8 +793,139 @@
staffState: 1,
}).then(res => {
let arr = res.data.records;
- personList.value = arr;
+ personListAll.value = arr;
+ updatePersonListByDept();
});
+ };
+
+ const getLeaveDates = () => {
+ findStaffLeaveListPage({
+ current: -1,
+ size: -1,
+ }).then(res => {
+ const records = Array.isArray(res?.data?.records) ? res.data.records : [];
+ const map = {};
+ records.forEach(item => {
+ const staffId = item?.staffOnJobId ?? item?.staff_on_job_id;
+ const leaveDate = item?.leaveDate ?? item?.leave_date;
+ if (!staffId || !leaveDate) return;
+ const key = String(staffId);
+ const prev = map[key];
+ if (!prev || String(leaveDate) > String(prev)) {
+ map[key] = leaveDate;
+ }
+ });
+ leaveDateMap.value = map;
+ updatePersonListByDept();
+ syncPersonListSelection();
+ });
+ };
+
+ const findDeptNodeById = (deptId, deptList = deptOptions.value) => {
+ const list = Array.isArray(deptList) ? deptList : [];
+ for (const node of list) {
+ if (String(node.id) === String(deptId)) return node;
+ if (node.children && node.children.length) {
+ const found = findDeptNodeById(deptId, node.children);
+ if (found) return found;
+ }
+ }
+ return null;
+ };
+
+ const collectDeptIds = node => {
+ const ids = [];
+ if (!node) return ids;
+ ids.push(String(node.id));
+ if (node.children && node.children.length) {
+ node.children.forEach(child => {
+ ids.push(...collectDeptIds(child));
+ });
+ }
+ return ids;
+ };
+
+ const updatePersonListByDept = () => {
+ if (!schedulingQuery.deptId) {
+ personList.value = [];
+ return;
+ }
+ const node = findDeptNodeById(schedulingQuery.deptId);
+ const deptIds = new Set(collectDeptIds(node));
+ personList.value = (personListAll.value || []).filter(item => {
+ const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id;
+ return deptIds.has(String(itemDeptId)) && !isEmployeeBlockedByLeaveDate(item);
+ });
+ };
+
+ const parseDateOnly = value => {
+ if (!value) return null;
+ if (value instanceof Date) {
+ const d = new Date(value);
+ d.setHours(0, 0, 0, 0);
+ return d;
+ }
+ const normalized = String(value).trim().slice(0, 10);
+ if (!normalized) return null;
+ const d = new Date(normalized.replace(/-/g, "/"));
+ if (Number.isNaN(d.getTime())) return null;
+ d.setHours(0, 0, 0, 0);
+ return d;
+ };
+
+ const getSchedulingWeekEndDate = () => {
+ if (!schedulingQuery.week) return null;
+ const start = new Date(schedulingQuery.week);
+ start.setHours(0, 0, 0, 0);
+ return new Date(start.getTime() + 24 * 60 * 60 * 1000 * 5);
+ };
+
+ const getCellDate = cell => parseDateOnly(cell?.time);
+
+ const getEmployeeLeaveDate = staffId => {
+ const leaveDate = leaveDateMap.value[String(staffId)];
+ return parseDateOnly(leaveDate);
+ };
+
+ const isEmployeeBlockedByLeaveDate = item => {
+ const leaveDate = getEmployeeLeaveDate(item?.id);
+ if (!leaveDate) return false;
+ const weekEndDate = getSchedulingWeekEndDate();
+ if (!weekEndDate) return false;
+ return weekEndDate.getTime() > leaveDate.getTime();
+ };
+
+ const isCellBlockedByLeaveDate = (row, cell) => {
+ const staffId = row?.userId ?? row?.user_id ?? row?.id;
+ const leaveDate = getEmployeeLeaveDate(staffId);
+ const cellDate = getCellDate(cell);
+ if (!leaveDate || !cellDate) return false;
+ return cellDate.getTime() > leaveDate.getTime();
+ };
+
+ const syncPersonListSelection = () => {
+ if (!Array.isArray(schedulingQuery.userId) || !schedulingQuery.userId.length) return;
+ const validIds = new Set((personList.value || []).map(item => String(item.id)));
+ const nextIds = schedulingQuery.userId.filter(id => validIds.has(String(id)));
+ if (nextIds.length !== schedulingQuery.userId.length) {
+ schedulingQuery.userId = nextIds;
+ proxy.$modal.msgWarning("宸茶嚜鍔ㄥ墧闄よ秴鍑虹鑱屾棩鏈熺殑浜哄憳");
+ }
+ };
+
+ const setDefaultShiftByDept = () => {
+ if (!schedulingQuery.deptId) {
+ schedulingQuery.shift = "";
+ return;
+ }
+ const first = schedulingClassType.value?.[0];
+ schedulingQuery.shift = first ? first.id : "";
+ };
+
+ const onSchedulingDeptChange = () => {
+ schedulingQuery.userId = null;
+ updatePersonListByDept();
+ setDefaultShiftByDept();
};
// 鏍规嵁瀛楀吀鑾峰彇鏃ユ湡
@@ -750,6 +949,7 @@
onMounted(() => {
fetchData();
getUsers();
+ getLeaveDates();
fetchDeptOptions();
if (query.month) {
init();
@@ -762,6 +962,45 @@
}
monthList.value.reverse();
});
+
+ watch(
+ () => classType.value,
+ () => {
+ if (schedulingVisible.value && schedulingQuery.deptId) {
+ setDefaultShiftByDept();
+ }
+ }
+ );
+
+ watch(
+ () => schedulingQuery.week,
+ () => {
+ if (!schedulingVisible.value) return;
+ updatePersonListByDept();
+ syncPersonListSelection();
+ }
+ );
+
+ watch(
+ () => schedulingQuery.deptId,
+ () => {
+ if (!schedulingVisible.value) return;
+ updatePersonListByDept();
+ syncPersonListSelection();
+ }
+ );
+
+ watch(
+ () => schedulingVisible.value,
+ val => {
+ if (!val) return;
+ schedulingQuery.week = "";
+ schedulingQuery.deptId = "";
+ schedulingQuery.userId = null;
+ schedulingQuery.shift = "";
+ personList.value = [];
+ }
+ );
</script>
<style scoped>
@@ -974,7 +1213,7 @@
.user-stats {
/* display: flex; */
/* flex-wrap: wrap;
- gap: 10px; */
+ gap: 10px; */
margin-bottom: 4px;
}
@@ -1224,6 +1463,7 @@
display: flex;
flex-direction: column;
align-items: center;
+ height: 65px;
}
/* 鏈堝害鍑哄嫟 */
--
Gitblit v1.9.3