From 83946cf045cc431dcbf66b9e192eacd329da1148 Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期三, 11 二月 2026 14:12:26 +0800
Subject: [PATCH] 打卡规则配置

---
 src/api/personnelManagement/attendanceRules.js                                   |   45 +++
 index.html                                                                       |    4 
 src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue           |  293 ++++++++++++++++++++
 src/views/personnelManagement/attendanceCheckin/index.vue                        |    3 
 src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue |  461 ++++++++++++++++++++++++++++++++
 5 files changed, 805 insertions(+), 1 deletions(-)

diff --git a/index.html b/index.html
index a684690..274352d 100644
--- a/index.html
+++ b/index.html
@@ -10,6 +10,10 @@
     />
     <link rel="icon" href="/favicon.ico" />
     <title>%VITE_APP_TITLE%</title>
+    <!-- 楂樺痉鍦板浘API -->
+    <script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=6af5d2639adbbabf95eddfbf2bae5739"></script>
+    <!-- 楂樺痉鍦板浘鎼滅储鎻掍欢 -->
+    <script type="text/javascript" src="https://webapi.amap.com/loca?v=2.0.0&key=6af5d2639adbbabf95eddfbf2bae5739"></script>
     <!--[if lt IE 11
       ]><script>
         window.location.href = "/html/ie.html";
diff --git a/src/api/personnelManagement/attendanceRules.js b/src/api/personnelManagement/attendanceRules.js
new file mode 100644
index 0000000..021442e
--- /dev/null
+++ b/src/api/personnelManagement/attendanceRules.js
@@ -0,0 +1,45 @@
+import request from "@/utils/request";
+
+// 鑾峰彇鎵撳崱瑙勫垯鍒楄〃
+export function getAttendanceRules(query) {
+  return request({
+    url: "/personalAttendanceLocationConfig/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板鎵撳崱瑙勫垯
+export function addAttendanceRule(data) {
+  return request({
+    url: "/personalAttendanceLocationConfig/add",
+    method: "post",
+    data,
+  });
+}
+
+// 鏇存柊鎵撳崱瑙勫垯
+export function updateAttendanceRule(data) {
+  return request({
+    url: "/attendanceRules/update",
+    method: "put",
+    data,
+  });
+}
+
+// 鍒犻櫎鎵撳崱瑙勫垯
+export function deleteAttendanceRule(ids) {
+  return request({
+    url: `/personalAttendanceLocationConfig/del`,
+    method: "delete",
+    data: ids,
+  });
+}
+
+// 鑾峰彇鍗曚釜鎵撳崱瑙勫垯璇︽儏
+export function getAttendanceRuleDetail(id) {
+  return request({
+    url: `/attendanceRules/detail/${id}`,
+    method: "get",
+  });
+}
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
new file mode 100644
index 0000000..39eacda
--- /dev/null
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
@@ -0,0 +1,461 @@
+<template>
+  <el-dialog v-model="dialogVisible"
+             :title="dialogTitle"
+             width="700px"
+             :close-on-click-modal="false">
+    <el-form ref="formRef"
+             :model="form"
+             :rules="rules"
+             label-width="120px"
+             class="mt8">
+      <!-- 閮ㄩ棬閫夋嫨 -->
+      <el-form-item label="閮ㄩ棬"
+                    prop="sysDeptId">
+        <el-tree-select v-model="form.sysDeptId"
+                        :data="deptOptions"
+                        :props="{ value: 'id', label: 'label', children: 'children' }"
+                        value-key="id"
+                        placeholder="璇烽�夋嫨閮ㄩ棬"
+                        check-strictly
+                        style="width: 100%"
+                        :disabled="operationType === 'view'" />
+      </el-form-item>
+      <!-- 鍦扮偣淇℃伅 -->
+      <!-- <el-form-item label="鍦扮偣鍚嶇О"
+                    prop="locationName">
+        <el-input v-model="form.locationName"
+                  placeholder="璇疯緭鍏ュ湴鐐瑰悕绉�"
+                  :disabled="operationType === 'view'" />
+      </el-form-item> -->
+      <!-- 鎵撳崱鑼冨洿 -->
+      <el-form-item label="鎵撳崱鑼冨洿(m)"
+                    prop="radius">
+        <el-input-number v-model="form.radius"
+                         :min="10"
+                         :max="1000"
+                         :step="10"
+                         placeholder="璇疯緭鍏ユ墦鍗¤寖鍥�"
+                         :disabled="operationType === 'view'" />
+      </el-form-item>
+      <!-- 楂樺痉鍦板浘閫夋嫨 -->
+      <el-form-item label="鎵撳崱浣嶇疆"
+                    prop="longitude">
+        <div class="map-container">
+          <div class="map-header"
+               style="margin-bottom: 10px">
+            <el-button @click="getCurrentLocation">
+              <el-icon>
+                <Position />
+              </el-icon>
+              褰撳墠浣嶇疆
+            </el-button>
+            <span style="margin-left: 10px; color: #909399;font-size: 12px;">鐐瑰嚮鍦板浘閫夋嫨浣嶇疆</span>
+          </div>
+          <div id="map-container"
+               class="map"
+               ref="mapContainer"></div>
+          <div class="coordinates-info mt10">
+            <el-input v-model="form.longitude"
+                      readonly
+                      placeholder="缁忓害"
+                      style="width: 140px; margin-right: 10px" />
+            <el-input v-model="form.latitude"
+                      readonly
+                      placeholder="绾害"
+                      style="width: 140px; margin-right: 10px" />
+            <!-- <el-input v-model="form.locationName"
+                      placeholder="鍦扮偣鍚嶇О"
+                      style="width: calc(100% - 290px)" /> -->
+          </div>
+        </div>
+      </el-form-item>
+      <el-form-item label="鍦扮偣鍚嶇О"
+                    prop="locationName">
+        <el-input v-model="form.locationName"
+                  :disabled="operationType === 'view'"
+                  placeholder="璇疯緭鍏ュ湴鐐瑰悕绉�" />
+      </el-form-item>
+      <!-- 涓婁笅鐝椂闂� -->
+      <el-form-item label="涓婄彮鏃堕棿"
+                    prop="startAt">
+        <el-time-picker v-model="form.startAt"
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        placeholder="璇烽�夋嫨涓婄彮鏃堕棿"
+                        :disabled="operationType === 'view'" />
+      </el-form-item>
+      <el-form-item label="涓嬬彮鏃堕棿"
+                    prop="endAt">
+        <el-time-picker v-model="form.endAt"
+                        format="HH:mm"
+                        value-format="HH:mm"
+                        placeholder="璇烽�夋嫨涓嬬彮鏃堕棿"
+                        :disabled="operationType === 'view'" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary"
+                   @click="submitForm"
+                   v-if="operationType !== 'view'">
+          纭畾
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, watch, onMounted, nextTick } from "vue";
+  import { ElMessage } from "element-plus";
+  import { Position } from "@element-plus/icons-vue";
+  import { deptTreeSelect } from "@/api/system/user.js";
+  import { addAttendanceRule } from "@/api/personnelManagement/attendanceRules.js";
+
+  const props = defineProps({
+    modelValue: {
+      type: Boolean,
+      default: false,
+    },
+    operationType: {
+      type: String,
+      default: "add",
+    },
+    row: {
+      type: Object,
+      default: () => ({}),
+    },
+  });
+
+  const emit = defineEmits(["update:modelValue", "close"]);
+
+  const dialogVisible = computed({
+    get: () => props.modelValue,
+    set: val => emit("update:modelValue", val),
+  });
+
+  const dialogTitle = computed(() => {
+    if (props.operationType === "add") return "鏂板鎵撳崱瑙勫垯";
+    if (props.operationType === "edit") return "缂栬緫鎵撳崱瑙勫垯";
+    return "鏌ョ湅鎵撳崱瑙勫垯";
+  });
+
+  // 琛ㄥ崟鏁版嵁
+  const formRef = ref();
+  const form = reactive({
+    id: "",
+    sysDeptId: "",
+    locationName: "",
+    longitude: "",
+    latitude: "",
+    radius: 100,
+    startAt: "09:00",
+    endAt: "18:00",
+  });
+
+  // 琛ㄥ崟楠岃瘉瑙勫垯
+  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" }],
+    radius: [{ required: true, message: "璇疯緭鍏ユ墦鍗¤寖鍥�", trigger: "blur" }],
+    startAt: [{ required: true, message: "璇烽�夋嫨涓婄彮鏃堕棿", trigger: "change" }],
+    endAt: [{ required: true, message: "璇烽�夋嫨涓嬬彮鏃堕棿", trigger: "change" }],
+  };
+
+  // 閮ㄩ棬閫夐」
+  const deptOptions = ref([]);
+
+  // 鍦板浘鐩稿叧
+  const mapContainer = ref(null);
+  let map = null;
+  let marker = null;
+  let circle = null;
+
+  // 鑾峰彇閮ㄩ棬鍒楄〃
+  const fetchDeptOptions = () => {
+    deptTreeSelect().then(response => {
+      deptOptions.value = filterDisabledDept(
+        JSON.parse(JSON.stringify(response.data))
+      );
+    });
+  };
+
+  // 杩囨护绂佺敤鐨勯儴闂�
+  const filterDisabledDept = deptList => {
+    return deptList.filter(dept => {
+      if (dept.disabled) {
+        return false;
+      }
+      if (dept.children && dept.children.length) {
+        dept.children = filterDisabledDept(dept.children);
+      }
+      return true;
+    });
+  };
+
+  // 鍒濆鍖栧湴鍥�
+  const initMap = () => {
+    nextTick(() => {
+      if (window.AMap && mapContainer.value) {
+        // 鍒濆鍖栧湴鍥�
+        map = new window.AMap.Map(mapContainer.value, {
+          zoom: 16,
+          center: [116.397428, 39.90923], // 榛樿鍖椾含
+        });
+
+        // 娣诲姞鎺т欢
+        window.AMap.plugin(["AMap.ToolBar", "AMap.Scale"], function () {
+          map.addControl(new window.AMap.ToolBar());
+          map.addControl(new window.AMap.Scale());
+        });
+
+        // 娣诲姞鏍囪
+        marker = new window.AMap.Marker({
+          position: [116.397428, 39.90923],
+          draggable: true,
+          cursor: "move",
+          title: "鎷栨嫿瀹氫綅",
+        });
+        map.add(marker);
+
+        // 娣诲姞鍦嗗舰鑼冨洿
+        circle = new window.AMap.Circle({
+          center: [116.397428, 39.90923],
+          radius: form.radius,
+          strokeColor: "#3366FF",
+          strokeOpacity: 0.8,
+          strokeWeight: 2,
+          fillColor: "#3366FF",
+          fillOpacity: 0.2,
+        });
+        map.add(circle);
+
+        // 鐩戝惉鏍囪鎷栨嫿
+        marker.on("dragend", e => {
+          const position = e.lnglat;
+          const lng = position.getLng();
+          const lat = position.getLat();
+          form.longitude = lng;
+          form.latitude = lat;
+          updateCircle(position);
+        });
+
+        // 鐩戝惉鏍囪鎷栨嫿寮�濮�
+        marker.on("dragstart", () => {
+          map.setDefaultCursor("move");
+        });
+
+        // 鐩戝惉鏍囪鎷栨嫿缁撴潫
+        marker.on("dragend", () => {
+          map.setDefaultCursor("default");
+        });
+
+        // 鐩戝惉鍦板浘鐐瑰嚮
+        map.on("click", e => {
+          const position = e.lnglat;
+          const lng = position.getLng();
+          const lat = position.getLat();
+          form.longitude = lng;
+          form.latitude = lat;
+          updateMarker(position);
+          updateCircle(position);
+        });
+
+        // 灏濊瘯鑾峰彇褰撳墠浣嶇疆骞惰缃负鍦板浘涓績
+        if (navigator.geolocation && !form.longitude && !form.latitude) {
+          navigator.geolocation.getCurrentPosition(
+            position => {
+              console.log("鑾峰彇鍒板綋鍓嶄綅缃�:", position);
+              const { longitude, latitude } = position.coords;
+              const currentPosition = [longitude, latitude];
+              map.setCenter(currentPosition);
+              updateMarker(currentPosition);
+              updateCircle(currentPosition);
+              form.longitude = longitude;
+              form.latitude = latitude;
+            },
+            error => {
+              console.log("鑾峰彇浣嶇疆澶辫触锛屼娇鐢ㄩ粯璁や綅缃�");
+            }
+          );
+        } else if (form.longitude && form.latitude) {
+          // 濡傛灉鏈夋暟鎹紝璁剧疆鍒板湴鍥�
+          const position = [form.longitude, form.latitude];
+          map.setCenter(position);
+          updateMarker(position);
+          updateCircle(position);
+        }
+      }
+    });
+  };
+
+  // 鏇存柊鏍囪浣嶇疆
+  const updateMarker = position => {
+    if (marker) {
+      marker.setPosition(position);
+    }
+  };
+
+  // 鏇存柊鍦嗗舰鑼冨洿
+  const updateCircle = position => {
+    if (circle) {
+      circle.setCenter(position);
+      circle.setRadius(form.radius);
+    }
+  };
+
+  // 鑾峰彇褰撳墠浣嶇疆
+  const getCurrentLocation = () => {
+    if (navigator.geolocation) {
+      navigator.geolocation.getCurrentPosition(
+        position => {
+          const { longitude, latitude } = position.coords;
+          form.longitude = longitude;
+          form.latitude = latitude;
+          if (map) {
+            map.setCenter([longitude, latitude]);
+            updateMarker([longitude, latitude]);
+            updateCircle([longitude, latitude]);
+          }
+
+          // 閫嗗湴鐞嗙紪鐮佽幏鍙栧湴鍧�
+          if (window.AMap) {
+            // 鍔犺浇Geocoder鎻掍欢
+            window.AMap.plugin("AMap.Geocoder", function () {
+              const geocoder = new window.AMap.Geocoder();
+              geocoder.getAddress([longitude, latitude], (status, result) => {
+                if (status === "complete" && result.regeocode) {
+                  form.locationName = result.regeocode.formattedAddress;
+                }
+              });
+            });
+          }
+        },
+        error => {
+          ElMessage.error("鑾峰彇浣嶇疆澶辫触锛岃鎵嬪姩閫夋嫨");
+        }
+      );
+    } else {
+      ElMessage.error("娴忚鍣ㄤ笉鏀寔鍦扮悊瀹氫綅");
+    }
+  };
+
+  // 鐩戝惉鍗婂緞鍙樺寲
+  watch(
+    () => form.radius,
+    newValue => {
+      if (circle) {
+        circle.setRadius(newValue);
+      }
+    }
+  );
+
+  // 鐩戝惉寮圭獥鏄剧ず
+  watch(
+    () => dialogVisible.value,
+    newValue => {
+      if (newValue) {
+        // 閲嶇疆琛ㄥ崟
+        Object.assign(form, {
+          id: "",
+          sysDeptId: "",
+          locationName: "",
+          longitude: "",
+          latitude: "",
+          radius: 100,
+          startAt: "09:00",
+          endAt: "18:00",
+        });
+
+        // 濡傛灉鏄紪杈戞垨鏌ョ湅锛屽~鍏呮暟鎹�
+        if (props.operationType !== "add" && props.row.id) {
+          // 澶勭悊鏃堕棿鏍煎紡锛岀‘淇濇槸HH:mm鏍煎紡
+          const rowData = { ...props.row };
+          if (rowData.startAt && rowData.startAt.includes(":")) {
+            rowData.startAt = rowData.startAt.split(":").slice(0, 2).join(":");
+          }
+          if (rowData.endAt && rowData.endAt.includes(":")) {
+            rowData.endAt = rowData.endAt.split(":").slice(0, 2).join(":");
+          }
+          Object.assign(form, rowData);
+        }
+
+        // 鍒濆鍖栧湴鍥�
+        setTimeout(() => {
+          initMap();
+        }, 100);
+      }
+    }
+  );
+
+  // 鎻愪氦琛ㄥ崟
+  const submitForm = () => {
+    formRef.value.validate(valid => {
+      if (valid) {
+        const submitData = {
+          ...form,
+          // 杞崲鏃堕棿鏍煎紡锛岀‘淇濆彧淇濈暀鏃跺垎閮ㄥ垎
+          startAt: form.startAt
+            ? `${form.startAt.split(":").slice(0, 2).join(":")}`
+            : null,
+          endAt: form.endAt
+            ? `${form.endAt.split(":").slice(0, 2).join(":")}`
+            : null,
+        };
+
+        if (props.operationType === "add") {
+          addAttendanceRule(submitData).then(() => {
+            ElMessage.success("鏂板鎴愬姛");
+            emit("close");
+          });
+        } else if (props.operationType === "edit") {
+          addAttendanceRule(submitData).then(() => {
+            ElMessage.success("鏇存柊鎴愬姛");
+            emit("close");
+          });
+        }
+      }
+    });
+  };
+
+  // 鍒濆鍖�
+  onMounted(() => {
+    fetchDeptOptions();
+  });
+</script>
+
+<style scoped lang="scss">
+  .map-container {
+    width: 100%;
+  }
+
+  .map {
+    width: 100%;
+    height: 400px;
+    border: 1px solid #e4e7ed;
+  }
+
+  .coordinates-info {
+    display: flex;
+    gap: 10px;
+  }
+
+  .coordinates-display {
+    padding: 10px;
+    background-color: #f5f7fa;
+    border-radius: 4px;
+  }
+
+  .mt10 {
+    margin-top: 10px;
+  }
+
+  .mt8 {
+    margin-top: 8px;
+  }
+</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
new file mode 100644
index 0000000..1fd19f3
--- /dev/null
+++ b/src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
@@ -0,0 +1,293 @@
+<template>
+  <div class="app-container">
+    <!-- 椤甸潰鏍囬鍜屾搷浣滄寜閽� -->
+    <div class="page-header">
+      <div class="title">鎵撳崱瑙勫垯閰嶇疆</div>
+      <div class="actions">
+        <el-button type="primary"
+                   @click="openForm('add')">
+          <el-icon>
+            <Plus />
+          </el-icon>
+          鏂板瑙勫垯
+        </el-button>
+      </div>
+    </div>
+    <!-- 鏌ヨ鏉′欢 -->
+    <!-- <el-form :model="searchForm"
+             :inline="true"
+             class="search-form mb16">
+      <el-form-item label="閮ㄩ棬锛�"
+                    prop="countId">
+        <el-tree-select v-model="searchForm.countId"
+                        :data="deptOptions"
+                        :props="{ value: 'id', label: 'label', children: 'children' }"
+                        value-key="id"
+                        placeholder="璇烽�夋嫨閮ㄩ棬"
+                        check-strictly
+                        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>
+      <el-form-item>
+        <el-button type="primary"
+                   @click="fetchData">
+          <el-icon>
+            <Search />
+          </el-icon>
+          鎼滅储
+        </el-button>
+        <el-button @click="resetSearch">
+          <el-icon>
+            <Refresh />
+          </el-icon>
+          閲嶇疆
+        </el-button>
+      </el-form-item>
+    </el-form> -->
+    <!-- 瑙勫垯鍒楄〃 -->
+    <el-card shadow="never"
+             class="mb16">
+      <el-table :data="tableData"
+                border
+                v-loading="tableLoading"
+                style="width: 100%"
+                row-key="id">
+        <el-table-column type="index"
+                         label="搴忓彿"
+                         width="60"
+                         align="center" />
+        <el-table-column label="閮ㄩ棬">
+          <template #default="scope">
+            {{ getDeptNameById(scope.row.sysDeptId) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="locationName"
+                         label="鍦扮偣鍚嶇О" />
+        <el-table-column prop="longitude"
+                         label="缁忓害" />
+        <el-table-column prop="latitude"
+                         label="绾害" />
+        <el-table-column prop="radius"
+                         label="鎵撳崱鑼冨洿(m)" />
+        <el-table-column prop="startAt"
+                         label="涓婄彮鏃堕棿">
+          <template #default="scope">
+            {{ scope.row.startAt }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="endAt"
+                         label="涓嬬彮鏃堕棿">
+          <template #default="scope">
+            {{ scope.row.endAt }}
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔"
+                         width="180"
+                         fixed="right"
+                         align="center">
+          <template #default="scope">
+            <el-button type="primary"
+                       size="small"
+                       link
+                       @click="openForm('edit', scope.row)">缂栬緫</el-button>
+            <el-button type="danger"
+                       size="small"
+                       link
+                       @click="handleDelete(scope.row.id)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :total="page.total"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="paginationChange"
+                  class="mt10" />
+    </el-card>
+    <!-- 鏂板/缂栬緫瑙勫垯寮圭獥 -->
+    <rule-form ref="ruleFormRef"
+               v-model="dialogVisible"
+               :operation-type="operationType"
+               :row="currentRow"
+               @close="dialogVisible = false; fetchData()" />
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, onMounted } from "vue";
+  import { ElMessage, ElMessageBox } from "element-plus";
+  import { Plus, Edit, Delete, Search, Refresh } from "@element-plus/icons-vue";
+  import Pagination from "@/components/Pagination/index.vue";
+  import RuleForm from "./components/form.vue";
+  import { deptTreeSelect } from "@/api/system/user.js";
+  import {
+    getAttendanceRules,
+    deleteAttendanceRule,
+  } from "@/api/personnelManagement/attendanceRules.js";
+
+  const { proxy } = getCurrentInstance();
+
+  // 琛ㄦ牸鏁版嵁
+  const tableData = ref([]);
+  const tableLoading = ref(false);
+
+  // 鍒嗛〉鍙傛暟
+  const page = reactive({
+    current: 1,
+    size: 10,
+    total: 0,
+  });
+
+  // 鏌ヨ琛ㄥ崟
+  const searchForm = reactive({
+    countId: "",
+    locationName: "",
+  });
+
+  // 閮ㄩ棬閫夐」
+  const deptOptions = ref([]);
+
+  // 寮圭獥鎺у埗
+  const dialogVisible = ref(false);
+  const operationType = ref("add");
+  const currentRow = ref({});
+  const ruleFormRef = ref();
+
+  // 鏍煎紡鍖栨椂闂�
+  const formatTime = timestamp => {
+    if (!timestamp) return "";
+    const date = new Date(timestamp);
+    return `${String(date.getHours()).padStart(2, "0")}:${String(
+      date.getMinutes()
+    ).padStart(2, "0")}`;
+  };
+
+  // 鑾峰彇閮ㄩ棬鍒楄〃
+  const fetchDeptOptions = () => {
+    deptTreeSelect().then(response => {
+      deptOptions.value = filterDisabledDept(
+        JSON.parse(JSON.stringify(response.data))
+      );
+    });
+  };
+
+  // 杩囨护绂佺敤鐨勯儴闂�
+  const filterDisabledDept = deptList => {
+    return deptList.filter(dept => {
+      if (dept.disabled) {
+        return false;
+      }
+      if (dept.children && dept.children.length) {
+        dept.children = filterDisabledDept(dept.children);
+      }
+      return true;
+    });
+  };
+
+  // 鏍规嵁閮ㄩ棬ID鏌ユ壘閮ㄩ棬鍚嶇О
+  const getDeptNameById = (deptId, deptList = deptOptions.value) => {
+    for (const dept of deptList) {
+      if (dept.id === deptId) {
+        return dept.label;
+      }
+      if (dept.children && dept.children.length) {
+        const name = getDeptNameById(deptId, dept.children);
+        if (name) {
+          return name;
+        }
+      }
+    }
+    return "";
+  };
+
+  // 鏌ヨ瑙勫垯鍒楄〃
+  const fetchData = () => {
+    tableLoading.value = true;
+    getAttendanceRules({ ...page, ...searchForm })
+      .then(res => {
+        tableData.value = res.data.records;
+        page.total = res.data.total;
+      })
+      .finally(() => {
+        tableLoading.value = false;
+      });
+  };
+
+  // 鍒嗛〉鍙樻洿
+  const paginationChange = pagination => {
+    page.current = pagination.page;
+    page.size = pagination.limit;
+    fetchData();
+  };
+
+  // 閲嶇疆鎼滅储
+  const resetSearch = () => {
+    searchForm.countId = "";
+    searchForm.locationName = "";
+    fetchData();
+  };
+
+  // 鎵撳紑琛ㄥ崟
+  const openForm = (type, row = {}) => {
+    operationType.value = type;
+    currentRow.value = row;
+    dialogVisible.value = true;
+  };
+
+  // 鍒犻櫎瑙勫垯
+  const handleDelete = id => {
+    ElMessageBox.confirm("纭畾瑕佸垹闄よ繖鏉¤鍒欏悧锛�", "鍒犻櫎纭", {
+      confirmButtonText: "纭畾",
+      cancelButtonText: "鍙栨秷",
+      type: "warning",
+    })
+      .then(() => {
+        deleteAttendanceRule([id]).then(() => {
+          ElMessage.success("鍒犻櫎鎴愬姛");
+          fetchData();
+        });
+      })
+      .catch(() => {
+        // 鍙栨秷鍒犻櫎
+      });
+  };
+
+  // 鍒濆鍖�
+  onMounted(() => {
+    fetchDeptOptions();
+    fetchData();
+  });
+</script>
+
+<style scoped lang="scss">
+  .page-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+
+    .title {
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .actions {
+      display: flex;
+      gap: 10px;
+    }
+  }
+
+  .mb16 {
+    margin-bottom: 16px;
+  }
+
+  .mt10 {
+    margin-top: 10px;
+  }
+</style>
\ No newline at end of file
diff --git a/src/views/personnelManagement/attendanceCheckin/index.vue b/src/views/personnelManagement/attendanceCheckin/index.vue
index 05949ef..7ce645b 100644
--- a/src/views/personnelManagement/attendanceCheckin/index.vue
+++ b/src/views/personnelManagement/attendanceCheckin/index.vue
@@ -5,7 +5,8 @@
              class="mb16">
       <div class="attendance-header">
         <div>
-          <div class="title">鎵撳崱绛惧埌</div>
+          <div class="title">鎵撳崱绛惧埌
+          </div>
           <div class="sub-title">鏀寔涓�閿墦鍗★紝鑷姩璁板綍涓婁笅鐝椂闂�</div>
         </div>
         <div class="attendance-actions">

--
Gitblit v1.9.3