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