| | |
| | | @cancel="showDatePicker = false" |
| | | title="搜索日期" /> |
| | | <view class="record-list"> |
| | | <view v-for="(item) in tableData" |
| | | <!-- 加载状态 --> |
| | | <view v-if="loading" |
| | | class="loading-state"> |
| | | <u-icon name="loading" |
| | | size="40" |
| | | color="#348fe2"></u-icon> |
| | | <text class="loading-text">加载中...</text> |
| | | </view> |
| | | <view v-else |
| | | v-for="(item) in tableData" |
| | | :key="item.id" |
| | | class="record-item-card" |
| | | :class="{ 'abnormal': item.status !== 'normal' }"> |
| | | :class="{ 'abnormal': item.status !== 0 }"> |
| | | <view class="record-item-header"> |
| | | <text class="record-date">{{ item.date }}</text> |
| | | <u-tag :type="item.status === 'normal' ? 'success' : 'error'" |
| | | <u-tag :type="item.status === 0 ? 'success' : 'error'" |
| | | size="small"> |
| | | {{ item.statusText }} |
| | | <!-- {{ item.status === 0 ? '正常' : (item.status === 1 ? '迟到' : (item.status === 2 ? '早退' : '迟到、早退')) }} --> |
| | | {{ getStatusText(item.status) }} |
| | | </u-tag> |
| | | </view> |
| | | <view class="record-item-body"> |
| | | <view class="record-detail"> |
| | | <text class="detail-label">员工</text> |
| | | <text class="detail-value">{{ item.name }} ({{ item.no }})</text> |
| | | <text class="detail-value">{{ item.staffName }} ({{ item.staffNo }})</text> |
| | | </view> |
| | | <view class="record-detail"> |
| | | <text class="detail-label">部门</text> |
| | | <text class="detail-value">{{ item.dept }}</text> |
| | | <text class="detail-value">{{ item.deptName }}</text> |
| | | </view> |
| | | <view class="record-detail"> |
| | | <text class="detail-label">上班时间</text> |
| | | <text class="detail-value">{{ item.checkInTime ? item.checkInTime : '缺卡' }}</text> |
| | | <text class="detail-value">{{ item.workStartAt || '缺卡' }}</text> |
| | | </view> |
| | | <view class="record-detail"> |
| | | <text class="detail-label">下班时间</text> |
| | | <text class="detail-value">{{ item.checkOutTime? item.checkOutTime : '缺卡' }}</text> |
| | | <text class="detail-value">{{ item.workEndAt || '缺卡' }}</text> |
| | | </view> |
| | | <view class="record-detail"> |
| | | <text class="detail-label">工时</text> |
| | |
| | | </view> |
| | | </view> |
| | | <!-- 空状态 --> |
| | | <view v-if="tableData.length === 0" |
| | | <view v-if="tableData.length === 0 && !loading" |
| | | class="empty-state"> |
| | | <u-icon name="clock-o" |
| | | <!-- <u-icon name="clock-o" |
| | | size="60" |
| | | color="#999"></u-icon> |
| | | color="#999"></u-icon> --> |
| | | <text class="empty-text">暂无考勤记录</text> |
| | | </view> |
| | | </view> |
| | |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 模拟当前登录员工 |
| | | const currentUser = reactive({ |
| | | id: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | }); |
| | | |
| | | // 模拟考勤原始数据 |
| | | const rawAttendance = ref([ |
| | | { |
| | | id: 1, |
| | | date: "2026-02-09", |
| | | userId: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | checkInTime: "08:58", |
| | | checkOutTime: "", |
| | | workHours: null, |
| | | status: "normal", |
| | | statusText: "正常", |
| | | remark: "", |
| | | }, |
| | | { |
| | | id: 2, |
| | | date: "2026-02-08", |
| | | userId: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | checkInTime: "09:15", |
| | | checkOutTime: "18:05", |
| | | workHours: 8.8, |
| | | status: "late", |
| | | statusText: "迟到", |
| | | remark: "因交通拥堵迟到", |
| | | }, |
| | | { |
| | | id: 3, |
| | | date: "2026-02-07", |
| | | userId: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | checkInTime: "08:45", |
| | | checkOutTime: "18:30", |
| | | workHours: 9.7, |
| | | status: "normal", |
| | | statusText: "正常", |
| | | remark: "加班0.5小时", |
| | | }, |
| | | { |
| | | id: 4, |
| | | date: "2026-02-06", |
| | | userId: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | checkInTime: "08:50", |
| | | checkOutTime: "17:45", |
| | | workHours: 8.9, |
| | | status: "early", |
| | | statusText: "早退", |
| | | remark: "家中有事提前离开", |
| | | }, |
| | | { |
| | | id: 5, |
| | | date: "2026-02-05", |
| | | userId: 1, |
| | | name: "张三", |
| | | no: "E10001", |
| | | dept: "生产一部", |
| | | checkInTime: "08:40", |
| | | checkOutTime: "18:20", |
| | | workHours: 9.7, |
| | | status: "normal", |
| | | statusText: "正常", |
| | | remark: "加班0.5小时", |
| | | }, |
| | | ]); |
| | | import { findPersonalAttendanceRecords } from "@/api/personnelManagement/attendance.js"; |
| | | |
| | | // 查询表单 |
| | | const searchForm = reactive({ |
| | | date: "", |
| | | }); |
| | | |
| | | // 分页参数 |
| | | const page = reactive({ |
| | | current: -1, |
| | | size: -1, |
| | | total: 0, |
| | | }); |
| | | |
| | | // 表格数据 |
| | | const tableData = ref([]); |
| | | |
| | | // 加载状态 |
| | | const loading = ref(false); |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | |
| | | showDatePicker.value = false; |
| | | handleQuery(); |
| | | }; |
| | | const getStatusText = status => { |
| | | switch (status) { |
| | | case 0: |
| | | return "正常"; |
| | | case 1: |
| | | return "迟到"; |
| | | case 2: |
| | | return "早退"; |
| | | case 3: |
| | | return "迟到、早退"; |
| | | case 4: |
| | | return "缺勤"; |
| | | } |
| | | }; |
| | | |
| | | // 显示日期选择器 |
| | | const selectDate = () => { |
| | |
| | | |
| | | // 清除日期选择 |
| | | const clearDate = () => { |
| | | resetQuery(); |
| | | }; |
| | | |
| | | // 查询 |
| | | const handleQuery = () => { |
| | | loading.value = true; |
| | | console.log(searchForm, "searchForm"); |
| | | |
| | | findPersonalAttendanceRecords({ |
| | | ...page, |
| | | ...searchForm, |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(error => { |
| | | console.error("查询失败:", error); |
| | | uni.showToast({ |
| | | title: "查询失败,请重试", |
| | | icon: "none", |
| | | }); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 重置查询 |
| | | const resetQuery = () => { |
| | | searchForm.date = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 查询 |
| | | const recomputeTable = () => { |
| | | const list = rawAttendance.value.filter(item => { |
| | | if (searchForm.date && item.date !== searchForm.date) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }); |
| | | tableData.value = list; |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | recomputeTable(); |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | margin: 0 20rpx 24rpx; |
| | | } |
| | | |
| | | /* 加载状态 */ |
| | | .loading-state { |
| | | background-color: $card-bg; |
| | | border-radius: 16rpx; |
| | | box-shadow: $shadow-md; |
| | | text-align: center; |
| | | padding: 120rpx 0; |
| | | margin: 0 20rpx; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .loading-text { |
| | | font-size: 14px; |
| | | color: $text-tertiary; |
| | | margin-top: 24rpx; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .record-item-card { |
| | | background-color: $card-bg; |
| | | border-radius: 16rpx; |