From e9a0f130fd9a076518009cb35fdea24edf1aaac7 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期三, 29 七月 2026 17:48:32 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' into dev_pro_山西长治

---
 multiple/config.json                                                                                       |   38 +++
 src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js                     |   77 +++++
 src/views/officeProcessAutomation/HrManage/work-handover/index.vue                                         |    1 
 src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue                           |  151 ++++++++---
 src/views/officeProcessAutomation/HrManage/regular-apply/index.vue                                         |    1 
 multiple/assets/favicon/QYHYfavicon.ico                                                                    |    0 
 multiple/assets/favicon/XJGM.ico                                                                           |    0 
 src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue                                        |    1 
 multiple/assets/logo/XJGMLogo.png                                                                          |    0 
 multiple/assets/favicon/HJGCfavicon.ico                                                                    |    0 
 multiple/assets/logo/OLDLogo.png                                                                           |    0 
 multiple/assets/logo/HJGCLogo.png                                                                          |    0 
 multiple/multiple-build.js                                                                                 |    8 
 src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue              |   17 
 multiple/assets/favicon/RBHB.ico                                                                           |    0 
 multiple/assets/logo/RBHBLogo.png                                                                          |    0 
 deploy.bat                                                                                                 |   10 
 src/views/login.vue                                                                                        |   20 
 src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js                 |   13 
 src/views/personnelManagement/classsSheduling/index.vue                                                    |  270 +++++++++++++++++++++-
 multiple/assets/favicon/OLD.ico                                                                            |    0 
 src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue                                     |   93 +++++--
 src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue   |    2 
 src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue |    2 
 src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateFormSection.vue  |    2 
 multiple/assets/logo/QYHYLogo.png                                                                          |    0 
 26 files changed, 581 insertions(+), 125 deletions(-)

diff --git a/deploy.bat b/deploy.bat
index 17c8665..7586c2d 100644
--- a/deploy.bat
+++ b/deploy.bat
@@ -3,12 +3,12 @@
 setlocal enabledelayedexpansion
 
 :: ================= Config =================
-set "USER=dell"
-set "PASS=xd123456@.."
-set "IP=1.15.17.182"
-set "PORT=10000"
+set "USER=root"
+set "PASS=Xdrjkj@10086#"
+set "IP=36.213.180.217
+set "PORT=65022"
 set "DIST_DIR=dist"
-set "REMOTE_DIR=/home/vue/newpro-product"
+set "REMOTE_DIR=/data/project/pim/vue/xjgm"
 set "SSH_OPTS=-o StrictHostKeyChecking=no -o ConnectTimeout=10"
 :: ===========================================
 
diff --git a/multiple/assets/favicon/HJGCfavicon.ico b/multiple/assets/favicon/HJGCfavicon.ico
new file mode 100644
index 0000000..7dfa6db
--- /dev/null
+++ b/multiple/assets/favicon/HJGCfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/OLD.ico b/multiple/assets/favicon/OLD.ico
new file mode 100644
index 0000000..236aae2
--- /dev/null
+++ b/multiple/assets/favicon/OLD.ico
Binary files differ
diff --git a/multiple/assets/favicon/QYHYfavicon.ico b/multiple/assets/favicon/QYHYfavicon.ico
new file mode 100644
index 0000000..5e6bb8f
--- /dev/null
+++ b/multiple/assets/favicon/QYHYfavicon.ico
Binary files differ
diff --git a/multiple/assets/favicon/RBHB.ico b/multiple/assets/favicon/RBHB.ico
new file mode 100644
index 0000000..a41f7ec
--- /dev/null
+++ b/multiple/assets/favicon/RBHB.ico
Binary files differ
diff --git a/multiple/assets/favicon/XJGM.ico b/multiple/assets/favicon/XJGM.ico
new file mode 100644
index 0000000..acff53d
--- /dev/null
+++ b/multiple/assets/favicon/XJGM.ico
Binary files differ
diff --git a/multiple/assets/logo/HJGCLogo.png b/multiple/assets/logo/HJGCLogo.png
new file mode 100644
index 0000000..9a4de9c
--- /dev/null
+++ b/multiple/assets/logo/HJGCLogo.png
Binary files differ
diff --git a/multiple/assets/logo/OLDLogo.png b/multiple/assets/logo/OLDLogo.png
new file mode 100644
index 0000000..8cdd10d
--- /dev/null
+++ b/multiple/assets/logo/OLDLogo.png
Binary files differ
diff --git a/multiple/assets/logo/QYHYLogo.png b/multiple/assets/logo/QYHYLogo.png
new file mode 100644
index 0000000..4f9b687
--- /dev/null
+++ b/multiple/assets/logo/QYHYLogo.png
Binary files differ
diff --git a/multiple/assets/logo/RBHBLogo.png b/multiple/assets/logo/RBHBLogo.png
new file mode 100644
index 0000000..605f429
--- /dev/null
+++ b/multiple/assets/logo/RBHBLogo.png
Binary files differ
diff --git a/multiple/assets/logo/XJGMLogo.png b/multiple/assets/logo/XJGMLogo.png
new file mode 100644
index 0000000..84cbc8a
--- /dev/null
+++ b/multiple/assets/logo/XJGMLogo.png
Binary files differ
diff --git a/multiple/config.json b/multiple/config.json
index b3351e2..e9dc9b3 100644
--- a/multiple/config.json
+++ b/multiple/config.json
@@ -258,6 +258,42 @@
     "logo": "logo/JXSMLogo.png",
     "favicon": "favicon/JXSMico.ico"
   },
+  "HYZC": {
+    "env": {
+      "VITE_APP_TITLE": "灞辫タ鍗庝嚎浼楁垚寤烘潗鏈夐檺鍏徃",
+      "VITE_BASE_API": "http://36.213.180.217:9009",
+      "VITE_JAVA_API": "http://36.213.180.217:9008"
+    },
+    "logo": "logo/HYZCLogo.png",
+    "favicon": "favicon/HYZCfavicon.ico"
+  },
+  "OLD": {
+    "env": {
+      "VITE_APP_TITLE": "闈掓捣娆ц幈寰锋柊鏉愭枡绉戞妧鏈夐檺鍏徃",
+      "VITE_BASE_API": "http://223.221.37.139:9000",
+      "VITE_JAVA_API": "http://223.221.37.139:9000"
+    },
+    "logo": "logo/OLDLogo.png",
+    "favicon": "favicon/OLD.ico"
+  },
+  "XJGM": {
+    "env": {
+      "VITE_APP_TITLE": "鍏嬫媺鐜涗緷甯傛柊鑱氬伐璐告湁闄愯矗浠诲叕鍙�",
+      "VITE_BASE_API": "http://36.213.180.217:9015",
+      "VITE_JAVA_API": "http://36.213.180.217:9016"
+    },
+    "logo": "logo/XJGMLogo.png",
+    "favicon": "favicon/XJGM.ico"
+  },
+  "RBHB": {
+    "env": {
+      "VITE_APP_TITLE": "鍏嬫媺鐜涗緷铻嶉偊鐜繚绉戞妧鏈夐檺鍏徃",
+      "VITE_BASE_API": "http://36.213.180.217:9017",
+      "VITE_JAVA_API": "http://36.213.180.217:9018"
+    },
+    "logo": "logo/RBHBLogo.png",
+    "favicon": "favicon/RBHB.ico"
+  },
   "logo": "/src/assets/logo/logo.png",
   "favicon": "/public/favicon.ico"
-}
\ No newline at end of file
+}
diff --git a/multiple/multiple-build.js b/multiple/multiple-build.js
index afcd4d5..933090c 100644
--- a/multiple/multiple-build.js
+++ b/multiple/multiple-build.js
@@ -60,7 +60,9 @@
     const backupFile = path.join(replacePath, config[key]);
     const replaceFile = path.join(resourcePath, companyMap[key]);
 
-    await copyFileWithOverwrite(originFile, backupFile);
+    if (fsSync.existsSync(originFile)) {
+      await copyFileWithOverwrite(originFile, backupFile);
+    }
     await copyFileWithOverwrite(replaceFile, originFile);
   }
 
@@ -82,7 +84,9 @@
 
       const originFile = path.join(rootPath, config[key]);
       const backupFile = path.join(replacePath, config[key]);
-      await copyFileWithOverwrite(backupFile, originFile);
+      if (fsSync.existsSync(backupFile)) {
+        await copyFileWithOverwrite(backupFile, originFile);
+      }
     }
     await fs.rm(replacePath, { recursive: true, force: true });
     console.log(`馃棏锔� 宸插垹闄� ${replacePath}`);
diff --git a/src/views/login.vue b/src/views/login.vue
index c7db15a..d044465 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -2,16 +2,16 @@
   <div class="login-page">
     <main class="page">
       <section class="factory">
-        <div class="brand hero-brand">
-          <div class="logo hero-logo">
-            <img
-              :src="brandLogoUrl"
-              :alt="`${companyName} logo`"
-              class="logo-image hero-logo-image"
-              @error="handleLogoError"
-            />
-          </div>
-        </div>
+<!--        <div class="brand hero-brand">-->
+<!--          <div class="logo hero-logo">-->
+<!--            <img-->
+<!--              :src="brandLogoUrl"-->
+<!--              :alt="`${companyName} logo`"-->
+<!--              class="logo-image hero-logo-image"-->
+<!--              @error="handleLogoError"-->
+<!--            />-->
+<!--          </div>-->
+<!--        </div>-->
 
         <div class="hero">
           <div class="chip">鏁板瓧宸ュ巶 路 鏅鸿兘鎺掍骇 路 璁惧浜掕仈 路 璐ㄩ噺杩芥函</div>
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
index 7933db5..817a007 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/FormPayloadFields.vue
@@ -80,12 +80,12 @@
           clearable
           filterable
         >
-          <el-option
-            v-for="o in getOptions(field)"
-            :key="String(o.value)"
-            :label="o.label"
-            :value="o.value"
-          />
+        <el-option
+          v-for="o in getOptions(field, { moduleKey: props.moduleKey })"
+          :key="String(o.value)"
+          :label="o.label"
+          :value="o.value"
+        />
         </el-select>
         <span v-else class="field-value">{{ displayValue(field) }}</span>
       </el-form-item>
@@ -103,13 +103,14 @@
   fields: { type: Array, default: () => [] },
   formPayload: { type: Object, default: () => ({}) },
   readonly: { type: Boolean, default: false },
+  moduleKey: { type: String, default: "" },
 });
 
 const { loading: optionSourceLoading, ensureForFields, getOptions, getDisplayLabel } =
   useSelectOptionSources();
 
 async function loadOptionCaches() {
-  await ensureForFields(props.fields);
+  await ensureForFields(props.fields, { moduleKey: props.moduleKey });
 }
 
 onMounted(() => {
@@ -127,7 +128,7 @@
 function displayValue(field) {
   const val = props.formPayload?.[field.key];
   if (field.type === "select" && field.optionSource && field.optionSource !== "static") {
-    return getDisplayLabel(field, val);
+    return getDisplayLabel(field, val, { moduleKey: props.moduleKey });
   }
   return formatFieldDisplayValue(field, val);
 }
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue
index 53de9e1..0f9646c 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue
@@ -24,6 +24,7 @@
         v-model:attachments="form.storageBlobDTOs"
         :template-attachments="form.templateAttachments"
         :user-options="userOptions"
+        :module-key="moduleKey"
         :show-template-name="!isEdit"
         :allow-change-template="false"
         :flow-attachments-only="flowAttachmentsOnly"
@@ -56,6 +57,7 @@
   fields: { type: Array, default: () => [] },
   activeTemplate: { type: Object, default: null },
   userOptions: { type: Array, default: () => [] },
+  moduleKey: { type: String, default: "" },
   isEdit: { type: Boolean, default: false },
   saving: { type: Boolean, default: false },
   formRef: { type: Object, default: null },
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue
index 409dd41..6d56084 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue
@@ -4,7 +4,7 @@
   鐢ㄦ硶锛�
   <ApprovalTemplateBindDialog
     v-model:visible="visible"
-    module-key="regular"
+    :module-key="APPROVAL_MODULE_KEYS.REGULAR"
     @confirm="onTemplateBound"
   />
 -->
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateFormSection.vue b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateFormSection.vue
index d6e7073..ebc5ac4 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateFormSection.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/components/ApprovalTemplateFormSection.vue
@@ -15,6 +15,7 @@
       v-if="!hideFormFields && !flowAttachmentsOnly && !flowOnly"
       :fields="fields"
       :form-payload="formPayload"
+      :module-key="moduleKey"
     />
 
     <el-form-item label="瀹℃壒娴佺▼" required>
@@ -87,6 +88,7 @@
   uploadLimit: { type: Number, default: 10 },
   /** 涓� true 鏃跺彲缂栬緫妯℃澘棰勭疆鐨勫鎵逛汉锛堜粎瀹℃壒妯℃澘绠$悊椤典娇鐢級 */
   flowEditable: { type: Boolean, default: false },
+  moduleKey: { type: String, default: "" },
 });
 
 const emit = defineEmits(["update:flowNodes", "update:attachments", "change-template"]);
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js b/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
index 99706b4..3dbc6f0 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/selectOptionSource.js
@@ -1,30 +1,42 @@
 import { deptTreeSelect, userListNoPageByTenantId } from "@/api/system/user.js";
+import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js";
 
 /** 涓嬫媺閫夐」鏉ユ簮锛堝啓鍏� formConfig锛屾彁浜ら〉鎸夋潵婧愭媺鍙栨暟鎹級 */
 export const SELECT_OPTION_SOURCE = {
   STATIC: "static",
   USER: "user",
   DEPT: "dept",
+  EMPLOYEE: "employee",
 };
 
 export const SELECT_OPTION_SOURCE_OPTIONS = [
   { value: SELECT_OPTION_SOURCE.STATIC, label: "鎵嬪姩閰嶇疆", desc: "鍦ㄦā鏉夸腑鑷畾涔夐�夐」鏂囨湰涓庡��" },
   { value: SELECT_OPTION_SOURCE.USER, label: "浜哄憳鍒楄〃", desc: "浠庣郴缁熺敤鎴蜂腑閫夋嫨锛屽�间负鐢ㄦ埛 ID" },
   { value: SELECT_OPTION_SOURCE.DEPT, label: "閮ㄩ棬鍒楄〃", desc: "浠庣粍缁囨灦鏋勪腑閫夋嫨锛屽�间负閮ㄩ棬 ID" },
+  { value: SELECT_OPTION_SOURCE.EMPLOYEE, label: "鍛樺伐鍒楄〃", desc: "浠庡湪鑱屽憳宸ヤ腑閫夋嫨锛屽�间负鍛樺伐 ID" },
 ];
+
+const EMPLOYEE_APPLICANT_MODULE_KEYS = new Set(["regular"]);
 
 export function selectOptionSourceLabel(source) {
   return SELECT_OPTION_SOURCE_OPTIONS.find((x) => x.value === source)?.label || "鈥�";
 }
 
 export function isDynamicOptionSource(source) {
-  return source === SELECT_OPTION_SOURCE.USER || source === SELECT_OPTION_SOURCE.DEPT;
+  return (
+    source === SELECT_OPTION_SOURCE.USER ||
+    source === SELECT_OPTION_SOURCE.DEPT ||
+    source === SELECT_OPTION_SOURCE.EMPLOYEE
+  );
 }
 
 function unwrapArray(payload) {
   if (Array.isArray(payload)) return payload;
+  if (Array.isArray(payload?.records)) return payload.records;
   if (payload?.data && Array.isArray(payload.data)) return payload.data;
+  if (Array.isArray(payload?.data?.records)) return payload.data.records;
   if (payload?.rows && Array.isArray(payload.rows)) return payload.rows;
+  if (Array.isArray(payload?.data?.rows)) return payload.data.rows;
   return [];
 }
 
@@ -39,6 +51,15 @@
   const value = u.userId ?? u.id;
   return {
     label: u.nickName || u.userName || `鐢ㄦ埛${value}`,
+    value,
+  };
+}
+
+/** 鍦ㄨ亴鍛樺伐 鈫� 涓嬫媺 option */
+export function mapEmployeeToSelectOption(u) {
+  const value = u.id ?? u.staffOnJobId ?? u.staffId;
+  return {
+    label: u.staffName || u.name || u.userName || `鍛樺伐${value}`,
     value,
   };
 }
@@ -70,21 +91,41 @@
 }
 
 /** 鎸夊瓧娈甸厤缃В鏋愪笅鎷� options锛堥渶浼犲叆宸插姞杞界殑缂撳瓨锛� */
-export function resolveFieldSelectOptions(field, caches = {}) {
+function isApplicantField(field) {
+  const key = String(field?.key || "").toLowerCase();
+  const label = String(field?.label || "");
+  return (
+    key === "applicant" ||
+    key === "applicantname" ||
+    label.includes("鐢宠浜�")
+  );
+}
+
+export function resolveFieldSelectOptions(field, caches = {}, context = {}) {
   const source = field?.optionSource || SELECT_OPTION_SOURCE.STATIC;
+  if (
+    source === SELECT_OPTION_SOURCE.USER &&
+    EMPLOYEE_APPLICANT_MODULE_KEYS.has(context.moduleKey) &&
+    isApplicantField(field)
+  ) {
+    return (caches.employees || []).map(mapEmployeeToSelectOption);
+  }
   if (source === SELECT_OPTION_SOURCE.USER) {
     return (caches.users || []).map(mapUserToSelectOption);
   }
   if (source === SELECT_OPTION_SOURCE.DEPT) {
     return caches.deptOptions || [];
   }
+  if (source === SELECT_OPTION_SOURCE.EMPLOYEE) {
+    return (caches.employees || []).map(mapEmployeeToSelectOption);
+  }
   return (field?.options || []).filter((o) => o.value !== "" && o.value != null);
 }
 
 /** 鏍规嵁宸茶В鏋愮殑 options 鍙嶆煡灞曠ず鏂囨湰 */
-export function resolveSelectDisplayLabel(field, val, caches = {}) {
+export function resolveSelectDisplayLabel(field, val, caches = {}, context = {}) {
   if (val == null || val === "") return "鈥�";
-  const options = resolveFieldSelectOptions(field, caches);
+  const options = resolveFieldSelectOptions(field, caches, context);
   const hit = options.find((o) => String(o.value) === String(val));
   return hit?.label || String(val);
 }
@@ -93,9 +134,10 @@
 export async function fetchSelectOptionCaches(sources = []) {
   const needUser = sources.includes(SELECT_OPTION_SOURCE.USER);
   const needDept = sources.includes(SELECT_OPTION_SOURCE.DEPT);
-  const caches = { users: [], deptOptions: [] };
+  const needEmployee = sources.includes(SELECT_OPTION_SOURCE.EMPLOYEE);
+  const caches = { users: [], deptOptions: [], employees: [] };
 
-  if (!needUser && !needDept) return caches;
+  if (!needUser && !needDept && !needEmployee) return caches;
 
   const tasks = [];
   if (needUser) {
@@ -123,17 +165,36 @@
         })
     );
   }
+  if (needEmployee) {
+    tasks.push(
+      staffOnJobListPage({ current: -1, size: -1, staffState: 1 })
+        .then((res) => {
+          caches.employees = unwrapArray(res);
+        })
+        .catch(() => {
+          caches.employees = [];
+        })
+    );
+  }
 
   await Promise.all(tasks);
   return caches;
 }
 
 /** 浠庡瓧娈靛垪琛ㄦ敹闆嗛渶瑕侀鍔犺浇鐨勫姩鎬佹潵婧� */
-export function collectOptionSourcesFromFields(fields) {
+export function collectOptionSourcesFromFields(fields, context = {}) {
   const set = new Set();
   (fields || []).forEach((f) => {
     if (f?.type === "select" && isDynamicOptionSource(f.optionSource)) {
-      set.add(f.optionSource);
+      if (
+        EMPLOYEE_APPLICANT_MODULE_KEYS.has(context.moduleKey) &&
+        f.optionSource === SELECT_OPTION_SOURCE.USER &&
+        isApplicantField(f)
+      ) {
+        set.add(SELECT_OPTION_SOURCE.EMPLOYEE);
+      } else {
+        set.add(f.optionSource);
+      }
     }
   });
   return [...set];
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js b/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
index 8397288..5d246e1 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/useSelectOptionSources.js
@@ -14,25 +14,26 @@
     deptOptions: [],
   });
 
-  async function ensureForFields(fields) {
-    const sources = collectOptionSourcesFromFields(fields);
+  async function ensureForFields(fields, context = {}) {
+    const sources = collectOptionSourcesFromFields(fields, context);
     if (!sources.length) return;
     loading.value = true;
     try {
       const next = await fetchSelectOptionCaches(sources);
       caches.users = next.users;
       caches.deptOptions = next.deptOptions;
+      caches.employees = next.employees || [];
     } finally {
       loading.value = false;
     }
   }
 
-  function getOptions(field) {
-    return resolveFieldSelectOptions(field, caches);
+  function getOptions(field, context = {}) {
+    return resolveFieldSelectOptions(field, caches, context);
   }
 
-  function getDisplayLabel(field, val) {
-    return resolveSelectDisplayLabel(field, val, caches);
+  function getDisplayLabel(field, val, context = {}) {
+    return resolveSelectDisplayLabel(field, val, caches, context);
   }
 
   return {
diff --git a/src/views/officeProcessAutomation/HrManage/regular-apply/index.vue b/src/views/officeProcessAutomation/HrManage/regular-apply/index.vue
index 291d57d..df70f4f 100644
--- a/src/views/officeProcessAutomation/HrManage/regular-apply/index.vue
+++ b/src/views/officeProcessAutomation/HrManage/regular-apply/index.vue
@@ -48,6 +48,7 @@
       :fields="submitFormFields"
       :active-template="activeTemplate"
       :user-options="flowUserOptions"
+      :module-key="APPROVAL_MODULE_KEYS.REGULAR"
       :is-edit="isSubmitEdit"
       :saving="submitSaving"
       :form-ref="submitFormRef"
diff --git a/src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue b/src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue
index f731d57..8b43805 100644
--- a/src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue
+++ b/src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue
@@ -58,6 +58,7 @@
       :fields="submitFormFields"
       :active-template="activeTemplate"
       :user-options="flowUserOptions"
+      :module-key="APPROVAL_MODULE_KEYS.TRANSFER"
       :is-edit="isSubmitEdit"
       :saving="submitSaving"
       :form-ref="submitFormRef"
diff --git a/src/views/officeProcessAutomation/HrManage/work-handover/index.vue b/src/views/officeProcessAutomation/HrManage/work-handover/index.vue
index a9b96ae..37ff223 100644
--- a/src/views/officeProcessAutomation/HrManage/work-handover/index.vue
+++ b/src/views/officeProcessAutomation/HrManage/work-handover/index.vue
@@ -58,6 +58,7 @@
       :fields="submitFormFields"
       :active-template="activeTemplate"
       :user-options="flowUserOptions"
+      :module-key="APPROVAL_MODULE_KEYS.WORK_HANDOVER"
       :is-edit="isSubmitEdit"
       :saving="submitSaving"
       :form-ref="submitFormRef"
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
index cbbb8af..0ddea67 100644
--- a/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
@@ -37,11 +37,17 @@
           <el-option v-for="item in shifts_list"
                      :key="item.value"
                      :label="item.label"
-                     :value="item.value" />
+                     :value="item.value"
+                     :disabled="
+              operationType === 'add' &&
+              (String(item.label || '').includes('鍋�') ||
+                String(item.label || '').includes('浼�'))
+            " />
         </el-select>
       </el-form-item>
       <el-form-item label="鎵撳崱鑼冨洿(m)"
-                    prop="radius">
+                    prop="radius"
+                    v-show="!hideClockFields">
         <el-input-number v-model="form.radius"
                          :min="10"
                          :max="1000"
@@ -51,7 +57,8 @@
       </el-form-item>
       <!-- 楂樺痉鍦板浘閫夋嫨 -->
       <el-form-item label="鎵撳崱浣嶇疆"
-                    prop="longitude">
+                    prop="longitude"
+                    v-show="!hideClockFields">
         <div class="map-container">
           <div class="map-header"
                style="margin-bottom: 10px">
@@ -82,14 +89,16 @@
         </div>
       </el-form-item>
       <el-form-item label="鍦扮偣鍚嶇О"
-                    prop="locationName">
+                    prop="locationName"
+                    v-show="!hideClockFields">
         <el-input v-model="form.locationName"
                   :disabled="operationType === 'view'"
                   placeholder="璇疯緭鍏ュ湴鐐瑰悕绉�" />
       </el-form-item>
       <!-- 涓婁笅鐝椂闂� -->
       <el-form-item label="涓婄彮鏃堕棿"
-                    prop="startAt">
+                    prop="startAt"
+                    v-show="!hideClockFields">
         <el-time-picker v-model="form.startAt"
                         format="HH:mm"
                         value-format="HH:mm"
@@ -97,7 +106,8 @@
                         :disabled="operationType === 'view'" />
       </el-form-item>
       <el-form-item label="涓嬬彮鏃堕棿"
-                    prop="endAt">
+                    prop="endAt"
+                    v-show="!hideClockFields">
         <el-time-picker v-model="form.endAt"
                         format="HH:mm"
                         value-format="HH:mm"
@@ -110,7 +120,9 @@
     </el-form>
     <template #footer>
       <span class="dialog-footer">
-        <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">纭畾</el-button>
+        <el-button type="primary"
+                   @click="submitForm"
+                   v-if="operationType !== 'view'">纭畾</el-button>
         <el-button @click="dialogVisible = false">鍙栨秷</el-button>
       </span>
     </template>
@@ -157,6 +169,21 @@
   // 鑾峰彇鐝瀛楀吀鍊�
   const { shifts_list } = useDict("shifts_list");
 
+  const selectedShiftLabel = computed(() => {
+    const item = shifts_list.value?.find(
+      s => String(s.value) === String(form.shift)
+    );
+    return item?.label || "";
+  });
+
+  const hideClockFields = computed(() => {
+    if (props.operationType === "view") return false;
+    return (
+      selectedShiftLabel.value.includes("鍋�") ||
+      selectedShiftLabel.value.includes("浼�")
+    );
+  });
+
   // 琛ㄥ崟鏁版嵁
   const formRef = ref();
   const form = reactive({
@@ -172,44 +199,53 @@
   });
 
   // 琛ㄥ崟楠岃瘉瑙勫垯
-  const rules = {
-    sysDeptId: [{ required: true, message: "璇烽�夋嫨閮ㄩ棬", trigger: "change" }],
-    locationName: [
-      { required: true, message: "璇疯緭鍏ュ湴鐐瑰悕绉�", trigger: "blur" },
-    ],
-    longitude: [{ required: true, message: "璇烽�夋嫨鎵撳崱浣嶇疆", trigger: "blur" }],
-    latitude: [{ required: true, message: "璇烽�夋嫨鎵撳崱浣嶇疆", trigger: "blur" }],
-    shift: [{ required: true, message: "璇烽�夋嫨鐝", trigger: "change" }],
-    radius: [{ required: true, message: "璇疯緭鍏ユ墦鍗¤寖鍥�", trigger: "blur" }],
-    startAt: [{ required: true, message: "璇烽�夋嫨涓婄彮鏃堕棿", trigger: "change" }],
-    endAt: [
-      { required: true, message: "璇烽�夋嫨涓嬬彮鏃堕棿", trigger: "change" },
-      {
-        validator: (rule, value, callback) => {
-          // 濡傛灉鏄鐝紝涓嶉檺鍒舵椂闂撮『搴�
-          if (form.shift && ['澶滅彮', '澶�'].includes(form.shift)) {
-            callback();
-            return;
-          }
-          if (form.startAt && value) {
-            const startParts = form.startAt.split(":");
-            const endParts = value.split(":");
-            const startTime =
-              parseInt(startParts[0]) * 60 + parseInt(startParts[1]);
-            const endTime = parseInt(endParts[0]) * 60 + parseInt(endParts[1]);
-            if (endTime <= startTime) {
-              callback(new Error("涓嬬彮鏃堕棿涓嶈兘鏃╀簬涓婄彮鏃堕棿"));
+  const rules = computed(() => {
+    const base = {
+      sysDeptId: [{ required: true, message: "璇烽�夋嫨閮ㄩ棬", trigger: "change" }],
+      shift: [{ required: true, message: "璇烽�夋嫨鐝", trigger: "change" }],
+    };
+
+    if (hideClockFields.value) {
+      return base;
+    }
+
+    return {
+      ...base,
+      locationName: [
+        { required: true, message: "璇疯緭鍏ュ湴鐐瑰悕绉�", trigger: "blur" },
+      ],
+      longitude: [{ required: true, message: "璇烽�夋嫨鎵撳崱浣嶇疆", trigger: "blur" }],
+      latitude: [{ required: true, message: "璇烽�夋嫨鎵撳崱浣嶇疆", trigger: "blur" }],
+      radius: [{ required: true, message: "璇疯緭鍏ユ墦鍗¤寖鍥�", trigger: "blur" }],
+      startAt: [{ required: true, message: "璇烽�夋嫨涓婄彮鏃堕棿", trigger: "change" }],
+      endAt: [
+        { required: true, message: "璇烽�夋嫨涓嬬彮鏃堕棿", trigger: "change" },
+        {
+          validator: (rule, value, callback) => {
+            if (form.shift && ["澶滅彮", "澶�"].includes(form.shift)) {
+              callback();
+              return;
+            }
+            if (form.startAt && value) {
+              const startParts = form.startAt.split(":");
+              const endParts = value.split(":");
+              const startTime =
+                parseInt(startParts[0]) * 60 + parseInt(startParts[1]);
+              const endTime = parseInt(endParts[0]) * 60 + parseInt(endParts[1]);
+              if (endTime <= startTime) {
+                callback(new Error("涓嬬彮鏃堕棿涓嶈兘鏃╀簬涓婄彮鏃堕棿"));
+              } else {
+                callback();
+              }
             } else {
               callback();
             }
-          } else {
-            callback();
-          }
+          },
+          trigger: "change",
         },
-        trigger: "change",
-      },
-    ],
-  };
+      ],
+    };
+  });
 
   // 閮ㄩ棬閫夐」
   const deptOptions = ref([]);
@@ -393,8 +429,8 @@
   watch(
     () => form.radius,
     newValue => {
-      if (circle) {
-        circle.setRadius(newValue);
+      if (circle && newValue != null && newValue !== "") {
+        circle.setRadius(Number(newValue));
       }
     }
   );
@@ -405,6 +441,29 @@
     () => {
       if (formRef.value && form.endAt) {
         formRef.value.validateField("endAt");
+      }
+    }
+  );
+
+  watch(
+    () => hideClockFields.value,
+    val => {
+      if (!val) return;
+      form.radius = null;
+      form.longitude = "";
+      form.latitude = "";
+      form.locationName = "";
+      form.startAt = "";
+      form.endAt = "";
+      if (formRef.value) {
+        formRef.value.clearValidate([
+          "radius",
+          "longitude",
+          "latitude",
+          "locationName",
+          "startAt",
+          "endAt",
+        ]);
       }
     }
   );
@@ -442,7 +501,9 @@
 
         // 鍒濆鍖栧湴鍥�
         setTimeout(() => {
-          initMap();
+          if (!hideClockFields.value) {
+            initMap();
+          }
         }, 100);
       }
     }
@@ -513,4 +574,4 @@
   .mt8 {
     margin-top: 8px;
   }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
index c47dd2e..9658b1f 100644
--- a/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
@@ -13,30 +13,37 @@
         </el-button>
       </div>
     </div>
-    <!-- 鏌ヨ鏉′欢 -->
-    <!-- <el-form :model="searchForm"
+    <el-form :model="searchForm"
              :inline="true"
              class="search-form mb16">
       <el-form-item label="閮ㄩ棬锛�"
-                    prop="countId">
-        <el-tree-select v-model="searchForm.countId"
+                    prop="sysDeptId">
+        <el-tree-select v-model="searchForm.sysDeptId"
                         :data="deptOptions"
                         :props="{ value: 'id', label: 'label', children: 'children' }"
                         value-key="id"
                         placeholder="璇烽�夋嫨閮ㄩ棬"
                         check-strictly
+                        clearable
                         style="width: 200px" />
       </el-form-item>
-      <el-form-item label="鍦扮偣锛�"
-                    prop="locationName">
-        <el-input v-model="searchForm.locationName"
-                  placeholder="璇疯緭鍏ュ湴鐐瑰悕绉�"
-                  clearable
-                  style="width: 200px" />
+      <el-form-item label="鐝锛�"
+                    prop="shift">
+        <el-select v-model="searchForm.shift"
+                   placeholder="璇烽�夋嫨鐝"
+                   clearable
+                   style="width: 200px">
+          <el-option
+              v-for="item in shifts_list"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+          />
+        </el-select>
       </el-form-item>
       <el-form-item>
         <el-button type="primary"
-                   @click="fetchData">
+                   @click="handleSearch">
           <el-icon>
             <Search />
           </el-icon>
@@ -49,7 +56,7 @@
           閲嶇疆
         </el-button>
       </el-form-item>
-    </el-form> -->
+    </el-form>
     <!-- 鐝鍒楄〃 -->
     <el-card shadow="never"
              class="mb16">
@@ -74,23 +81,39 @@
           </template>
         </el-table-column>
         <el-table-column prop="locationName"
-                         label="鍦扮偣鍚嶇О" />
+                         label="鍦扮偣鍚嶇О">
+          <template #default="scope">
+            {{ scope.row.locationName || "-" }}
+          </template>
+        </el-table-column>
         <el-table-column prop="longitude"
-                         label="缁忓害" />
+                         label="缁忓害">
+          <template #default="scope">
+            {{ scope.row.longitude ?? "-" }}
+          </template>
+        </el-table-column>
         <el-table-column prop="latitude"
-                         label="绾害" />
+                         label="绾害">
+          <template #default="scope">
+            {{ scope.row.latitude ?? "-" }}
+          </template>
+        </el-table-column>
         <el-table-column prop="radius"
-                         label="鎵撳崱鑼冨洿(m)" />
+                         label="鎵撳崱鑼冨洿(m)">
+          <template #default="scope">
+            {{ scope.row.radius ?? "-" }}
+          </template>
+        </el-table-column>
         <el-table-column prop="startAt"
                          label="涓婄彮鏃堕棿">
           <template #default="scope">
-            {{ scope.row.startAt }}
+            {{ scope.row.startAt || "-" }}
           </template>
         </el-table-column>
         <el-table-column prop="endAt"
                          label="涓嬬彮鏃堕棿">
           <template #default="scope">
-            {{ scope.row.endAt }}
+            {{ scope.row.endAt || "-" }}
           </template>
         </el-table-column>
         <el-table-column label="鎿嶄綔"
@@ -100,9 +123,11 @@
           <template #default="scope">
             <el-button type="primary"
                        link
+                       :disabled="isRuleLocked(scope.row)"
                        @click="openForm('edit', scope.row)">缂栬緫</el-button>
             <el-button type="danger"
                        link
+                       :disabled="isRuleLocked(scope.row)"
                        @click="handleDelete(scope.row.id)">鍒犻櫎</el-button>
           </template>
         </el-table-column>
@@ -124,7 +149,7 @@
 </template>
 
 <script setup>
-  import { ref, reactive, onMounted } from "vue";
+  import { ref, reactive, onMounted, getCurrentInstance } from "vue";
   import { ElMessage, ElMessageBox } from "element-plus";
   import {
     Plus,
@@ -158,8 +183,8 @@
 
   // 鏌ヨ琛ㄥ崟
   const searchForm = reactive({
-    countId: "",
-    locationName: "",
+    sysDeptId: "",
+    shift: "",
   });
 
   // 閮ㄩ棬閫夐」
@@ -187,6 +212,10 @@
     if (!value) return "";
     const shift = shifts_list.value.find(item => item.value === value);
     return shift ? shift.label : value;
+  };
+
+  const isRuleLocked = row => {
+    return Boolean(row?.usageLocked || row?.canEdit === false || row?.canDelete === false);
   };
 
   // 鑾峰彇閮ㄩ棬鍒楄〃
@@ -232,12 +261,18 @@
     tableLoading.value = true;
     getAttendanceRules({ ...page, ...searchForm })
       .then(res => {
-        tableData.value = res.data.records;
+        const records = Array.isArray(res?.data?.records) ? res.data.records : [];
+        tableData.value = records.filter(r => String(r?.id) !== "999");
         page.total = res.data.total;
       })
       .finally(() => {
         tableLoading.value = false;
       });
+  };
+
+  const handleSearch = () => {
+    page.current = 1;
+    fetchData();
   };
 
   // 鍒嗛〉鍙樻洿
@@ -249,13 +284,18 @@
 
   // 閲嶇疆鎼滅储
   const resetSearch = () => {
-    searchForm.countId = "";
-    searchForm.locationName = "";
+    searchForm.sysDeptId = "";
+    searchForm.shift = "";
+    page.current = 1;
     fetchData();
   };
 
   // 鎵撳紑琛ㄥ崟
   const openForm = (type, row = {}) => {
+    if (type === "edit" && isRuleLocked(row)) {
+      ElMessage.warning("璇ョ彮娆¢厤缃凡琚帓鐝苟浜х敓鎵撳崱璁板綍锛屼笉鑳界紪杈�");
+      return;
+    }
     operationType.value = type;
     currentRow.value = row;
     dialogVisible.value = true;
@@ -263,6 +303,11 @@
 
   // 鍒犻櫎鐝
   const handleDelete = id => {
+    const row = tableData.value.find(item => String(item.id) === String(id));
+    if (isRuleLocked(row)) {
+      ElMessage.warning("瀛樺湪宸叉帓鐝笖宸叉湁鎵撳崱璁板綍鐨勭彮娆¢厤缃紝涓嶈兘鍒犻櫎");
+      return;
+    }
     ElMessageBox.confirm("纭畾瑕佸垹闄よ繖鏉$彮娆″悧锛�", "鍒犻櫎纭", {
       confirmButtonText: "纭畾",
       cancelButtonText: "鍙栨秷",
diff --git a/src/views/personnelManagement/classsSheduling/index.vue b/src/views/personnelManagement/classsSheduling/index.vue
index 140ffba..59e65b4 100644
--- a/src/views/personnelManagement/classsSheduling/index.vue
+++ b/src/views/personnelManagement/classsSheduling/index.vue
@@ -142,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>
@@ -199,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>
@@ -271,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">
@@ -279,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"
@@ -296,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">
@@ -318,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,
@@ -328,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";
@@ -376,6 +404,8 @@
 
   // 浜哄憳鍒楄〃
   const personList = ref([]);
+  const personListAll = ref([]);
+  const leaveDateMap = ref({});
 
   // 鍔犺浇鐘舵��
   const loading = ref(false);
@@ -383,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);
+    });
   });
 
   // 鍒楄〃鏁版嵁
@@ -593,6 +633,10 @@
       proxy.$modal.msgError("璇烽�夋嫨鍛ㄦ");
       return;
     }
+    if (!schedulingQuery.deptId) {
+      proxy.$modal.msgError("璇烽�夋嫨閮ㄩ棬");
+      return;
+    }
     let time = schedulingQuery.week.getTime();
 
     // 鏍煎紡鍖栨棩鏈熶负 YYYY-MM-DD 鏍煎紡
@@ -616,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,
@@ -628,8 +681,10 @@
         proxy.$modal.msgSuccess("鎿嶄綔鎴愬姛");
         schedulingVisible.value = false;
         schedulingQuery.week = "";
+        schedulingQuery.deptId = "";
         schedulingQuery.userId = null;
         schedulingQuery.shift = "";
+        personList.value = [];
         refresh();
       })
       .catch(err => {
@@ -683,17 +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("鎿嶄綔鎴愬姛");
@@ -709,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;
     });
   };
   // 鑾峰彇鐢ㄦ埛
@@ -724,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();
   };
 
   // 鏍规嵁瀛楀吀鑾峰彇鏃ユ湡
@@ -749,6 +949,7 @@
   onMounted(() => {
     fetchData();
     getUsers();
+    getLeaveDates();
     fetchDeptOptions();
     if (query.month) {
       init();
@@ -761,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>
@@ -973,7 +1213,7 @@
   .user-stats {
     /* display: flex; */
     /* flex-wrap: wrap;
-                                                                                                                                                                                                                                                                                        gap: 10px; */
+                                                                                                                                                                                                                                                                                                      gap: 10px; */
     margin-bottom: 4px;
   }
 

--
Gitblit v1.9.3