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