zhangwencui
3 天以前 fe51058c98e4b808740caf54b195877550622326
feat(app): 考勤统计支持按日期区间筛选

传开始/结束日期时按日实时统计(startDate/endDate),不传仍查月度汇总
已修改1个文件
76 ■■■■■ 文件已修改
src/pages/hrm/attendance-summary/index.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/hrm/attendance-summary/index.vue
@@ -6,20 +6,41 @@
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;
@@ -29,4 +50,55 @@
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>