From f9a9009a9ead99c731eccfbf66ca943fb601b9e2 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 26 二月 2026 11:08:25 +0800
Subject: [PATCH] 湟水峡 1.人员模块代码迁移

---
 src/views/personnelManagement/attendanceCheckin/index.vue |  507 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 507 insertions(+), 0 deletions(-)

diff --git a/src/views/personnelManagement/attendanceCheckin/index.vue b/src/views/personnelManagement/attendanceCheckin/index.vue
new file mode 100644
index 0000000..f2d8776
--- /dev/null
+++ b/src/views/personnelManagement/attendanceCheckin/index.vue
@@ -0,0 +1,507 @@
+<template>
+  <div class="app-container">
+    <!-- 鍛樺伐鎵撳崱鍖� -->
+    <!-- <el-card shadow="never"
+             class="mb16">
+      <div class="attendance-header">
+        <div>
+          <div class="title">鎵撳崱绛惧埌
+          </div>
+          <div class="sub-title">鏀寔涓�閿墦鍗★紝鑷姩璁板綍涓婁笅鐝椂闂�</div>
+        </div>
+        <div class="attendance-actions">
+          <div class="time-block">
+            <div class="label">褰撳墠鏃堕棿</div>
+            <div class="value">{{ nowTime }}</div>
+          </div>
+          <el-button type="primary"
+                     size="large"
+                     @click="handleCheckInOut"
+                     :disabled="todayRecord.workEndAt">
+            {{ checkInOutText }}
+          </el-button>
+        </div>
+      </div>
+      <el-descriptions border
+                       :column="4"
+                       class="mt10">
+        <el-descriptions-item label="鍛樺伐濮撳悕">
+          {{ todayRecord.staffName }}
+        </el-descriptions-item>
+        <el-descriptions-item label="宸ュ彿">
+          {{ todayRecord.staffNo }}
+        </el-descriptions-item>
+        <el-descriptions-item label="鎵�灞為儴闂�">
+          {{ todayRecord.deptName }}
+        </el-descriptions-item>
+        <el-descriptions-item label="浠婃棩鐘舵��">
+          <el-tag :type="todayStatusTag"
+                  size="small">
+            {{ todayStatusText }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="涓婄彮鏃堕棿">
+          {{ todayRecord?.workStartAt || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="涓嬬彮鏃堕棿">
+          {{ todayRecord?.workEndAt || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="宸ユ椂(灏忔椂)">
+          {{ todayRecord?.workHours ?? '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="寮傚父鏍囪">
+          <span v-if="!todayRecord.id || todayRecord?.status === 0">-</span>
+          <el-tag v-else
+                  type="danger"
+                  size="small">
+            {{ todayRecord?.status ? getStatusText(todayRecord.status) : '-' }}
+          </el-tag>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-card> -->
+    <div class="attendance-operation">
+      <!-- 鏌ヨ鏉′欢锛堢鐞嗗憳鑰冨嫟鏃ユ姤锛� -->
+      <el-form :model="searchForm"
+               :inline="true"
+               class="search-form">
+        <el-form-item label="閮ㄩ棬锛�"
+                      prop="deptId">
+          <el-tree-select v-model="searchForm.deptId"
+                          :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="date">
+          <el-date-picker v-model="searchForm.date"
+                          type="date"
+                          value-format="YYYY-MM-DD"
+                          format="YYYY-MM-DD"
+                          placeholder="璇烽�夋嫨鏃ユ湡"
+                          clearable />
+        </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-button icon="Download"
+                 @click="handleExport">
+        瀵煎嚭鑰冨嫟鏃ユ姤
+      </el-button>
+    </div>
+    <!-- 鑰冨嫟鏃ユ姤琛ㄦ牸 -->
+    <div class="table_list">
+      <el-table :data="tableData"
+                border
+                v-loading="tableLoading"
+                style="width: 100%"
+                height="calc(100vh - 24em)"
+                :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
+                :row-class-name="rowClassName">
+        <el-table-column type="index"
+                         label="搴忓彿"
+                         width="60"
+                         align="center" />
+        <el-table-column prop="date"
+                         label="鏃ユ湡"
+                         width="120" />
+        <el-table-column prop="deptName"
+                         label="閮ㄩ棬"
+                         width="140" />
+        <el-table-column prop="staffName"
+                         label="濮撳悕"
+                         width="120" />
+        <el-table-column prop="staffNo"
+                         label="宸ュ彿"
+                         width="120" />
+        <el-table-column prop="workStartAt"
+                         label="涓婄彮鏃堕棿"
+                         width="140" />
+        <el-table-column prop="workEndAt"
+                         label="涓嬬彮鏃堕棿"
+                         width="140" />
+        <el-table-column prop="workHours"
+                         label="宸ユ椂(灏忔椂)"
+                         align="center" />
+        <el-table-column prop="status"
+                         label="鑰冨嫟鐘舵��"
+                         align="center">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0"
+                    type="success"
+                    size="small">
+              姝e父
+            </el-tag>
+            <el-tag v-else
+                    type="danger"
+                    size="small">
+              <!-- {{ scope.row.status === 1 ? '杩熷埌' : scope.row.status === 2 ? '鏃╅��' : '杩熷埌銆佹棭閫�' }} -->
+              {{ getStatusText(scope.row.status) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="remark"
+                         label="澶囨敞"
+                         show-overflow-tooltip />
+      </el-table>
+      <pagination :total="page.total"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="paginationChange" />
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue";
+  import { ElMessage, ElMessageBox } from "element-plus";
+  import {
+    createPersonalAttendanceRecord,
+    findPersonalAttendanceRecords,
+    findTodayPersonalAttendanceRecord,
+  } from "@/api/personnelManagement/personalAttendanceRecords.js";
+  import Pagination from "@/components/Pagination/index.vue";
+  import { deptTreeSelect } from "@/api/system/user.js";
+  import { Refresh, Search } from "@element-plus/icons-vue";
+
+  const { proxy } = getCurrentInstance();
+  const tableLoading = ref(false);
+  // 鍒嗛〉鍙傛暟
+  const page = reactive({
+    current: 1,
+    size: 10,
+    total: 0,
+  });
+  // 浠婃棩鏁版嵁
+  const todayRecord = ref({});
+
+  // 閮ㄩ棬閫夐」
+  const deptOptions = ref([]);
+
+  // 鏌ヨ琛ㄥ崟
+  const searchForm = reactive({
+    deptId: "",
+    date: "",
+  });
+
+  // 琛ㄦ牸鏁版嵁
+  const tableData = ref([]);
+
+  // 褰撳墠鏃堕棿灞曠ず
+  const nowTime = ref("");
+  let timer = null;
+
+  const updateNowTime = () => {
+    const now = new Date();
+    const Y = now.getFullYear();
+    const M = String(now.getMonth() + 1).padStart(2, "0");
+    const D = String(now.getDate()).padStart(2, "0");
+    const h = String(now.getHours()).padStart(2, "0");
+    const m = String(now.getMinutes()).padStart(2, "0");
+    const s = String(now.getSeconds()).padStart(2, "0");
+    nowTime.value = `${Y}-${M}-${D} ${h}:${m}:${s}`;
+  };
+
+  // 鎵撳崱鎸夐挳鏂囨湰
+  const checkInOutText = computed(() => {
+    if (!todayRecord.value || !todayRecord.value.workStartAt) {
+      return "涓婄彮鎵撳崱";
+    }
+    if (!todayRecord.value.workEndAt) {
+      return "涓嬬彮鎵撳崱";
+    }
+    return "浠婃棩宸叉墦鍗″畬鎴�";
+  });
+
+  // 浠婃棩鐘舵�佸睍绀�
+  const todayStatusTag = computed(() => {
+    if (!todayRecord.value.id) return "info";
+    if (todayRecord.value.status === 0) return "success";
+    return "danger";
+  });
+  const getStatusText = status => {
+    switch (status) {
+      case 0:
+        return "姝e父";
+      case 1:
+        return "杩熷埌";
+      case 2:
+        return "鏃╅��";
+      case 3:
+        return "杩熷埌銆佹棭閫�";
+      case 4:
+        return "缂哄嫟";
+    }
+  };
+
+  const todayStatusText = computed(() => {
+    if (!todayRecord.value.id) return "鏈墦鍗�";
+    switch (todayRecord.value.status) {
+      case 0:
+        return "姝e父";
+      case 1:
+        return "杩熷埌";
+      case 2:
+        return "鏃╅��";
+      case 3:
+        return "杩熷埌銆佹棭閫�";
+      case 4:
+        return "缂哄嫟";
+    }
+  });
+
+  // 琛屾牱寮忥細寮傚父楂樹寒
+  const rowClassName = ({ row }) => {
+    if (row.status === 1 || row.status === 2) {
+      return "row-abnormal";
+    }
+    return "";
+  };
+
+  // 鏌ヨ閮ㄩ棬鍒楄〃
+  const fetchDeptOptions = () => {
+    deptTreeSelect().then(response => {
+      deptOptions.value = filterDisabledDept(
+        JSON.parse(JSON.stringify(response.data))
+      );
+    });
+  };
+
+  /** 杩囨护绂佺敤鐨勯儴闂� */
+  function 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 fetchData = () => {
+    tableLoading.value = true;
+    findPersonalAttendanceRecords({ ...page, ...searchForm })
+      .then(res => {
+        tableData.value = res.data.records;
+        page.total = res.data.total;
+      })
+      .finally(() => {
+        tableLoading.value = false;
+      });
+  };
+
+  // 鏌ヨ浠婃棩鎵撳崱淇℃伅
+  const fetchTodayData = () => {
+    // findTodayPersonalAttendanceRecord({}).then(res => {
+    //   todayRecord.value = res.data;
+    // });
+  };
+
+  const paginationChange = pagination => {
+    page.current = pagination.page;
+    page.size = pagination.limit;
+    fetchData();
+  };
+
+  const resetSearch = () => {
+    searchForm.deptId = "";
+    searchForm.date = "";
+    fetchData();
+  };
+
+  const handleExport = () => {
+    ElMessageBox.confirm("鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
+      confirmButtonText: "纭",
+      cancelButtonText: "鍙栨秷",
+      type: "warning",
+    })
+      .then(() => {
+        proxy.download("/personalAttendanceRecords/export", {}, "鑰冨嫟璁板綍.xlsx");
+      })
+      .catch(() => {
+        proxy.$modal.msg("宸插彇娑�");
+      });
+  };
+
+  // 鑾峰彇褰撳墠浣嶇疆
+  const getCurrentLocation = () => {
+    return new Promise((resolve, reject) => {
+      if (!navigator.geolocation) {
+        reject(new Error("娴忚鍣ㄤ笉鏀寔鍦扮悊瀹氫綅"));
+        return;
+      }
+
+      // 妫�鏌ユ槸鍚︿娇鐢℉TTPS
+      const isSecureContext =
+        window.isSecureContext || window.location.protocol === "https:";
+      console.log(
+        "褰撳墠鍗忚:",
+        window.location.protocol,
+        "鏄惁瀹夊叏涓婁笅鏂�:",
+        isSecureContext
+      );
+
+      if (!isSecureContext) {
+        console.warn("褰撳墠涓嶆槸HTTPS鍗忚锛屽湴鐞嗕綅缃瓵PI鍙兘鍙楅檺");
+      }
+
+      navigator.geolocation.getCurrentPosition(
+        position => {
+          const { longitude, latitude } = position.coords;
+          console.log("鑾峰彇浣嶇疆鎴愬姛:", longitude, latitude);
+          resolve({ longitude, latitude });
+        },
+        error => {
+          console.log("鑾峰彇浣嶇疆澶辫触:", error);
+          let errorMessage = "鑾峰彇浣嶇疆澶辫触";
+
+          // 鏍规嵁閿欒绫诲瀷鎻愪緵鏇村叿浣撶殑鎻愮ず
+          switch (error.code) {
+            case error.PERMISSION_DENIED:
+              errorMessage =
+                "鐢ㄦ埛鎷掔粷浜嗕綅缃潈闄愯姹傦紝璇峰湪娴忚鍣ㄨ缃腑鍏佽浣嶇疆璁块棶";
+              break;
+            case error.POSITION_UNAVAILABLE:
+              errorMessage = "浣嶇疆淇℃伅涓嶅彲鐢紝璇锋鏌ヨ澶囧畾浣嶅姛鑳�";
+              break;
+            case error.TIMEOUT:
+              errorMessage = "鑾峰彇浣嶇疆瓒呮椂锛岃閲嶈瘯";
+              break;
+            case error.UNKNOWN_ERROR:
+              errorMessage = "鑾峰彇浣嶇疆鏃跺彂鐢熸湭鐭ラ敊璇�";
+              break;
+            default:
+              errorMessage = `鑾峰彇浣嶇疆澶辫触: ${error.message}`;
+          }
+
+          // 妫�鏌ユ槸鍚︽槸HTTPS闂
+          if (error.code === error.PERMISSION_DENIED && !isSecureContext) {
+            errorMessage += "锛堟敞鎰忥細鐢熶骇鐜闇�瑕佷娇鐢℉TTPS鍗忚鎵嶈兘鑾峰彇浣嶇疆锛�";
+          }
+
+          reject(new Error(errorMessage));
+        },
+        {
+          enableHighAccuracy: true,
+          timeout: 10000,
+          maximumAge: 0,
+        }
+      );
+    });
+  };
+
+  // 鎵撳崱
+  const handleCheckInOut = () => {
+    getCurrentLocation()
+      .then(location => {
+        console.log("浣嶇疆鎴愬姛");
+        createPersonalAttendanceRecord(location).then(res => {
+          fetchData();
+          fetchTodayData();
+          ElMessage.success("鎵撳崱鎴愬姛锛�");
+        });
+      })
+      .catch(error => {
+        // 鑾峰彇浣嶇疆澶辫触鏃讹紝浠嶅厑璁告墦鍗�
+        ElMessage.warning("鑾峰彇浣嶇疆澶辫触锛屽皢浣跨敤榛樿浣嶇疆鎵撳崱");
+        createPersonalAttendanceRecord({}).then(res => {
+          fetchData();
+          fetchTodayData();
+          ElMessage.success("鎵撳崱鎴愬姛锛�");
+        });
+      });
+  };
+
+  onMounted(() => {
+    updateNowTime();
+    timer = setInterval(updateNowTime, 1000);
+    // 榛樿灞曠ず褰撳ぉ鏁版嵁
+    const today = new Date();
+    const Y = today.getFullYear();
+    const M = String(today.getMonth() + 1).padStart(2, "0");
+    const D = String(today.getDate()).padStart(2, "0");
+    searchForm.date = `${Y}-${M}-${D}`;
+    fetchData();
+    fetchTodayData();
+    fetchDeptOptions();
+  });
+
+  onBeforeUnmount(() => {
+    if (timer) {
+      clearInterval(timer);
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  .mb16 {
+    margin-bottom: 16px;
+  }
+
+  .attendance-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .attendance-header .title {
+    font-size: 18px;
+    font-weight: 600;
+    margin-bottom: 4px;
+  }
+
+  .attendance-header .sub-title {
+    font-size: 13px;
+    color: #909399;
+  }
+
+  .attendance-actions {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .time-block {
+    text-align: right;
+  }
+
+  .time-block .label {
+    font-size: 12px;
+    color: #909399;
+  }
+
+  .time-block .value {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  ::v-deep(.row-abnormal) {
+    background-color: #fff5f5;
+  }
+
+  .attendance-operation {
+    display: flex;
+    justify-content: space-between;
+  }
+</style>
+

--
Gitblit v1.9.3