| | |
| | | import { confirm, toast } from "../shared"; |
| | | const { hasPermission, state } = useAuth(); |
| | | const rows = ref<AttendanceSummary[]>([]), period = ref(new Date().toISOString().slice(0, 7)), loading = ref(false), pageNo = ref(1), total = ref(0); |
| | | // 按日区间筛选:传了开始/结束日期则实时统计区间内考勤;不传则看已生成的月度汇总 |
| | | const startDate = ref(""), endDate = ref(""); |
| | | const pageSize = 15; |
| | | const canQuery = computed(() => hasPermission("hrm:attendance-summary:query")); |
| | | const hasMore = computed(() => rows.value.length < total.value); |
| | | const rangeActive = computed(() => Boolean(startDate.value || endDate.value)); |
| | | function buildParams() { |
| | | const base = { pageNo: pageNo.value, pageSize }; |
| | | if (rangeActive.value) { |
| | | if (!startDate.value || !endDate.value) { |
| | | toast("请选择完整的开始和结束日期"); |
| | | return null; |
| | | } |
| | | return { ...base, startDate: startDate.value, endDate: endDate.value }; |
| | | } |
| | | return { ...base, yearMonth: period.value }; |
| | | } |
| | | async function load(reset = true) { |
| | | if (!state.accessToken || !canQuery.value || loading.value) return; |
| | | const params = buildParams(); |
| | | if (!params) return; |
| | | if (reset) { pageNo.value = 1; total.value = 0; } |
| | | loading.value = true; |
| | | try { |
| | | const data = await getAttendanceSummaryPage(state.accessToken, { pageNo: pageNo.value, pageSize, yearMonth: period.value }); |
| | | const data = await getAttendanceSummaryPage(state.accessToken, params); |
| | | rows.value = reset ? (data.list || []) : [...rows.value, ...(data.list || [])]; |
| | | total.value = Number(data.total || 0); |
| | | if (rows.value.length < total.value) pageNo.value += 1; |
| | | } catch (error) { toast(error instanceof Error ? error.message : "加载失败"); } |
| | | finally { loading.value = false; uni.stopPullDownRefresh(); } |
| | | } |
| | | function clearRange() { |
| | | startDate.value = ""; |
| | | endDate.value = ""; |
| | | load(true); |
| | | } |
| | | async function generate() { |
| | | if (!state.accessToken || !await confirm("确认重新生成 " + period.value + " 考勤汇总?")) return; |
| | |
| | | onShow(() => load(true)); |
| | | onPullDownRefresh(() => load(true)); |
| | | onReachBottom(() => { if (hasMore.value) load(false); }); |
| | | </script><template><view class="page"><template v-if="canQuery"><view class="search"><input v-model="period" placeholder="统计月份 YYYY-MM" @confirm="load"/><text @click="load">查询</text></view><view v-if="hasPermission('hrm:attendance-summary:generate')" class="top-actions"><button @click="generate">生成月度汇总</button></view><view v-for="row in rows" :key="row.id" class="card"><view class="head"><view class="head-main"><text class="name">{{row.userName||'--'}}</text><text class="sub">{{row.deptName||'--'}} · {{row.yearMonth}}</text></view><text class="tag tag-green">{{row.actualDays??0}} / {{row.workDays??0}} 天</text></view><view class="grid"><text>迟到:{{row.lateCount??0}} 次</text><text>早退:{{row.earlyCount??0}} 次</text><text>缺勤:{{row.absentCount??0}} 次</text><text>加班:{{row.overtimeHours??0}} 小时</text><text>请假:{{row.leaveHours??0}} 小时</text></view></view><view v-if="!rows.length" class="state">{{loading?'加载中...':'暂无考勤汇总'}}</view><view v-else class="state more">{{loading ? "加载中..." : hasMore ? "上拉加载更多" : "没有更多了"}}</view></template><view v-else class="state">暂无考勤汇总查看权限</view></view></template><style scoped lang="scss">@import "../list.scss";.top-actions{display:flex;justify-content:flex-end;margin-bottom:18rpx}.top-actions button{margin:0;color:var(--color-primary, #1769d8);font-size:24rpx;background:#fff}</style> |
| | | </script> |
| | | <template> |
| | | <view class="page"> |
| | | <template v-if="canQuery"> |
| | | <view class="search range"> |
| | | <picker mode="date" :value="startDate" @change="startDate=$event.detail.value"> |
| | | <text :class="['date', !startDate && 'placeholder']">{{ startDate || "开始日期" }}</text> |
| | | </picker> |
| | | <text class="sep">至</text> |
| | | <picker mode="date" :value="endDate" @change="endDate=$event.detail.value"> |
| | | <text :class="['date', !endDate && 'placeholder']">{{ endDate || "结束日期" }}</text> |
| | | </picker> |
| | | <text @click="load(true)">查询</text> |
| | | <text v-if="rangeActive" @click="clearRange">清除</text> |
| | | </view> |
| | | <view class="search month"> |
| | | <input v-model="period" placeholder="统计月份 YYYY-MM(不选日期时生效)" @confirm="load(true)" /> |
| | | <text @click="load(true)">按月查</text> |
| | | </view> |
| | | <view v-if="hasPermission('hrm:attendance-summary:generate')" class="top-actions"><button @click="generate">生成月度汇总</button></view> |
| | | <view v-for="row in rows" :key="row.id" class="card"> |
| | | <view class="head"> |
| | | <view class="head-main"> |
| | | <text class="name">{{row.userName||'--'}}</text> |
| | | <text class="sub">{{row.deptName||'--'}} · {{row.yearMonth}}</text> |
| | | </view> |
| | | <text class="tag tag-green">{{row.actualDays??0}} / {{row.workDays??0}} 天</text> |
| | | </view> |
| | | <view class="grid"> |
| | | <text>迟到:{{row.lateCount??0}} 次</text> |
| | | <text>早退:{{row.earlyCount??0}} 次</text> |
| | | <text>缺勤:{{row.absentCount??0}} 次</text> |
| | | <text>加班:{{row.overtimeHours??0}} 小时</text> |
| | | <text>请假:{{row.leaveHours??0}} 小时</text> |
| | | </view> |
| | | </view> |
| | | <view v-if="!rows.length" class="state">{{loading?'加载中...': rangeActive ? '该日期区间暂无考勤统计' : '暂无考勤汇总'}}</view> |
| | | <view v-else class="state more">{{loading ? "加载中..." : hasMore ? "上拉加载更多" : "没有更多了"}}</view> |
| | | </template> |
| | | <view v-else class="state">暂无考勤汇总查看权限</view> |
| | | </view> |
| | | </template> |
| | | <style scoped lang="scss"> |
| | | @import "../list.scss"; |
| | | .range{gap:10rpx} |
| | | .range .sep{color:#97a4b4;font-size:23rpx} |
| | | .range .date{min-width:160rpx;font-size:25rpx;color:#20334c} |
| | | .range .date.placeholder{color:#9aa6b5} |
| | | .month{margin-top:0} |
| | | .top-actions{display:flex;justify-content:flex-end;margin-bottom:18rpx} |
| | | .top-actions button{margin:0;color:var(--color-primary, #1769d8);font-size:24rpx;background:#fff} |
| | | </style> |