gaoluyang
2026-07-08 4ffc61bbe8dd6ec89965a8e2448725f514075b53
银川
1.考勤管理页面开发、联调
已添加13个文件
已修改5个文件
1560 ■■■■■ 文件已修改
src/api/hrm/attendance/exception/index.ts 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/holiday/index.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/record/index.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/rule/index.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/summary/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/constants/src/dict-enum.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/exception/index.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/data.ts 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/index.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/holiday/modules/form.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/record/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/record/modules/clock-panel.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/rule/data.ts 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/rule/index.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/rule/modules/form.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/summary/data.ts 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/summary/index.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hrm/attendance/summary/modules/generate-form.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/hrm/attendance/exception/index.ts
@@ -19,6 +19,12 @@
    remark?: string;
    createTime?: string;
  }
  /** å®¡æ‰¹æµç¨‹ */
  export interface ApproveProcess {
    key: string;
    name: string;
  }
}
/** æŸ¥è¯¢è€ƒå‹¤å¼‚常申请分页列表 */
@@ -44,6 +50,15 @@
}
/** æäº¤è€ƒå‹¤å¼‚常申请 */
export function submitAttendanceException(id: number) {
  return requestClient.post(`/hrm/attendance/exception/submit?id=${id}`);
export function submitAttendanceException(id: number, processDefinitionKey: string) {
  return requestClient.post('/hrm/attendance/exception/submit', null, {
    params: { id, processDefinitionKey },
  });
}
/** èŽ·å–è€ƒå‹¤å¼‚å¸¸å®¡æ‰¹æµç¨‹åˆ—è¡¨ */
export function getAttendanceExceptionApproveProcessList() {
  return requestClient.get<HrmAttendanceExceptionApi.ApproveProcess[]>(
    '/hrm/attendance/exception/approve-process-list',
  );
}
src/api/hrm/attendance/holiday/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
import type { PageParam, PageResult } from '#/packages/effects/request/src';
import { requestClient } from '#/api/request';
export namespace HrmAttendanceHolidayApi {
  /** èŠ‚å‡æ—¥ */
  export interface Holiday {
    id?: number;
    name?: string;
    date?: string;
    type?: number;
    typeName?: string;
    isLegal?: number;
    remark?: string;
    createTime?: string;
  }
}
/** æŸ¥è¯¢èŠ‚å‡æ—¥åˆ†é¡µåˆ—è¡¨ */
export function getAttendanceHolidayPage(params: PageParam) {
  return requestClient.get<PageResult<HrmAttendanceHolidayApi.Holiday>>(
    '/hrm/attendance/holiday/page',
    { params },
  );
}
/** æŸ¥è¯¢èŠ‚å‡æ—¥è¯¦æƒ… */
export function getAttendanceHoliday(id: number) {
  return requestClient.get<HrmAttendanceHolidayApi.Holiday>(
    `/hrm/attendance/holiday/get?id=${id}`,
  );
}
/** æ–°å¢žèŠ‚å‡æ—¥ */
export function createAttendanceHoliday(data: HrmAttendanceHolidayApi.Holiday) {
  return requestClient.post('/hrm/attendance/holiday/create', data);
}
/** æ›´æ–°èŠ‚å‡æ—¥ */
export function updateAttendanceHoliday(data: HrmAttendanceHolidayApi.Holiday) {
  return requestClient.put('/hrm/attendance/holiday/update', data);
}
/** åˆ é™¤èŠ‚å‡æ—¥ */
export function deleteAttendanceHoliday(id: number) {
  return requestClient.delete(`/hrm/attendance/holiday/delete?id=${id}`);
}
/** å¯¼å‡ºèŠ‚å‡æ—¥ Excel */
export function exportAttendanceHoliday(params: any) {
  return requestClient.download('/hrm/attendance/holiday/export-excel', { params });
}
src/api/hrm/attendance/record/index.ts
@@ -77,4 +77,22 @@
/** å¯¼å‡ºè€ƒå‹¤è®°å½• */
export function exportAttendanceRecord(params: any) {
  return requestClient.download('/hrm/attendance/record/export-excel', { params });
}
/** ä¸Šç­æ‰“卡 */
export function clockIn(params: { location: string }) {
  return requestClient.post<HrmAttendanceRecordApi.AttendanceRecord>(
    '/hrm/attendance/record/clock-in',
    null,
    { params },
  );
}
/** ä¸‹ç­æ‰“卡 */
export function clockOut(params: { location: string }) {
  return requestClient.post<HrmAttendanceRecordApi.AttendanceRecord>(
    '/hrm/attendance/record/clock-out',
    null,
    { params },
  );
}
src/api/hrm/attendance/rule/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
import type { PageParam, PageResult } from '#/packages/effects/request/src';
import { requestClient } from '#/api/request';
export namespace HrmAttendanceRuleApi {
  /** è€ƒå‹¤è§„则 */
  export interface AttendanceRule {
    id?: number;
    name?: string;
    deptId?: number;
    deptName?: string;
    workStartTime?: string;
    workEndTime?: string;
    lateAllowMinutes?: number;
    earlyAllowMinutes?: number;
    overtimeThreshold?: number;
    status?: number;
    remark?: string;
    createTime?: string;
  }
}
/** æŸ¥è¯¢è€ƒå‹¤è§„则分页列表 */
export function getAttendanceRulePage(params: PageParam) {
  return requestClient.get<PageResult<HrmAttendanceRuleApi.AttendanceRule>>(
    '/hrm/attendance/rule/page',
    { params },
  );
}
/** æŸ¥è¯¢è€ƒå‹¤è§„则列表(启用状态) */
export function getAttendanceRuleList() {
  return requestClient.get<HrmAttendanceRuleApi.AttendanceRule[]>(
    '/hrm/attendance/rule/list',
  );
}
/** æŸ¥è¯¢è€ƒå‹¤è§„则详情 */
export function getAttendanceRule(id: number) {
  return requestClient.get<HrmAttendanceRuleApi.AttendanceRule>(
    `/hrm/attendance/rule/get?id=${id}`,
  );
}
/** æ–°å¢žè€ƒå‹¤è§„则 */
export function createAttendanceRule(data: HrmAttendanceRuleApi.AttendanceRule) {
  return requestClient.post('/hrm/attendance/rule/create', data);
}
/** æ›´æ–°è€ƒå‹¤è§„则 */
export function updateAttendanceRule(data: HrmAttendanceRuleApi.AttendanceRule) {
  return requestClient.put('/hrm/attendance/rule/update', data);
}
/** åˆ é™¤è€ƒå‹¤è§„则 */
export function deleteAttendanceRule(id: number) {
  return requestClient.delete(`/hrm/attendance/rule/delete?id=${id}`);
}
/** å¯¼å‡ºè€ƒå‹¤è§„则 Excel */
export function exportAttendanceRule(params: any) {
  return requestClient.download('/hrm/attendance/rule/export-excel', { params });
}
src/api/hrm/attendance/summary/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
import type { PageParam, PageResult } from '#/packages/effects/request/src';
import { requestClient } from '#/api/request';
export namespace HrmAttendanceSummaryApi {
  /** è€ƒå‹¤ç»Ÿè®¡ */
  export interface AttendanceSummary {
    id?: number;
    userId?: number;
    userName?: string;
    deptId?: number;
    deptName?: string;
    yearMonth?: string;
    workDays?: number;
    actualDays?: number;
    lateCount?: number;
    earlyCount?: number;
    absentCount?: number;
    overtimeHours?: number;
    leaveHours?: number;
    remark?: string;
    createTime?: string;
  }
  /** ç”Ÿæˆç»Ÿè®¡å‚æ•° */
  export interface GenerateParams {
    yearMonth: string;
    deptId?: number;
    userId?: number;
  }
}
/** æŸ¥è¯¢è€ƒå‹¤ç»Ÿè®¡åˆ†é¡µåˆ—表 */
export function getAttendanceSummaryPage(params: PageParam) {
  return requestClient.get<PageResult<HrmAttendanceSummaryApi.AttendanceSummary>>(
    '/hrm/attendance/summary/page',
    { params },
  );
}
/** æŸ¥è¯¢å‘˜å·¥æœˆåº¦æ±‡æ€» */
export function getAttendanceSummary(params: { userId: number; yearMonth: string }) {
  return requestClient.get<HrmAttendanceSummaryApi.AttendanceSummary>(
    '/hrm/attendance/summary/get',
    { params },
  );
}
/** ç”Ÿæˆæœˆåº¦è€ƒå‹¤æ±‡æ€» */
export function generateAttendanceSummary(params: HrmAttendanceSummaryApi.GenerateParams) {
  return requestClient.post('/hrm/attendance/summary/generate', null, { params });
}
/** å¯¼å‡ºè€ƒå‹¤ç»Ÿè®¡ Excel */
export function exportAttendanceSummary(params: any) {
  return requestClient.download('/hrm/attendance/summary/export-excel', { params });
}
src/packages/constants/src/dict-enum.ts
@@ -304,6 +304,8 @@
  HRM_TRANSFER_TYPE: 'hrm_transfer_type', // HRM è°ƒå²—类型
  HRM_RESIGNATION_TYPE: 'hrm_resignation_type', // HRM ç¦»èŒç±»åž‹
  HRM_CLOCK_TYPE: 'hrm_clock_type', // HRM æ‰“卡类型
  HRM_HOLIDAY_TYPE: 'hrm_holiday_type', // HRM èŠ‚å‡æ—¥ç±»åž‹
  HRM_DATA_SOURCE: 'hrm_data_source', // HRM æ•°æ®æ¥æº
  HRM_SALARY_STATUS: 'hrm_salary_status', // HRM è–ªèµ„状态
  HRM_HANDOVER_STATUS: 'hrm_handover_status', // HRM äº¤æŽ¥çŠ¶æ€
  HRM_PAYMENT_METHOD: 'hrm_payment_method', // HRM å‘放方式
src/views/hrm/attendance/exception/index.vue
@@ -2,12 +2,18 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceExceptionApi } from '#/api/hrm/attendance/exception';
import { ref } from 'vue';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { message, Modal, Select } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAttendanceExceptionPage, submitAttendanceException } from '#/api/hrm/attendance/exception';
import {
  getAttendanceExceptionApproveProcessList,
  getAttendanceExceptionPage,
  submitAttendanceException,
} from '#/api/hrm/attendance/exception';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
@@ -33,11 +39,37 @@
  formModalApi.setData({ id: row.id, formType: 'detail' }).open();
}
/** æäº¤å®¡æ‰¹å¼¹çª— */
const submitModalVisible = ref(false);
const currentRow = ref<HrmAttendanceExceptionApi.AttendanceException>();
const processList = ref<HrmAttendanceExceptionApi.ApproveProcess[]>([]);
const selectedProcessKey = ref<string>();
const submitLoading = ref(false);
/** æäº¤è€ƒå‹¤å¼‚常申请 */
async function handleSubmit(row: HrmAttendanceExceptionApi.AttendanceException) {
  await submitAttendanceException(row.id!);
  message.success('提交成功');
  handleRefresh();
  const res = await getAttendanceExceptionApproveProcessList();
  processList.value = res;
  currentRow.value = row;
  selectedProcessKey.value = undefined;
  submitModalVisible.value = true;
}
/** ç¡®è®¤æäº¤ */
async function confirmSubmit() {
  if (!selectedProcessKey.value) {
    message.warning('请选择审批流程');
    return;
  }
  submitLoading.value = true;
  try {
    await submitAttendanceException(currentRow.value!.id!, selectedProcessKey.value);
    message.success('提交审批成功');
    submitModalVisible.value = false;
    handleRefresh();
  } finally {
    submitLoading.value = false;
  }
}
const [Grid, gridApi] = useVbenVxeGrid({
@@ -106,5 +138,28 @@
        />
      </template>
    </Grid>
    <!-- æäº¤å®¡æ‰¹å¼¹çª— -->
    <Modal
      v-model:open="submitModalVisible"
      title="选择审批流程"
      width="400px"
      @ok="confirmSubmit"
      :confirmLoading="submitLoading"
    >
      <Select
        v-model:value="selectedProcessKey"
        placeholder="请选择审批流程"
        class="w-full"
      >
        <Select.Option
          v-for="item in processList"
          :key="item.key"
          :value="item.key"
        >
          {{ item.name }}
        </Select.Option>
      </Select>
    </Modal>
  </Page>
</template>
src/views/hrm/attendance/holiday/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceHolidayApi } from '#/api/hrm/attendance/holiday';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictOptions } from '#/packages/effects/hooks/src';
/** è¡¨å•类型 */
export type FormType = 'create' | 'update' | 'detail';
/** æ–°å¢ž/编辑节假日的表单 */
export function useFormSchema(formType: FormType): VbenFormSchema[] {
  return [
    {
      fieldName: 'id',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'name',
      label: '节假日名称',
      component: 'Input',
      componentProps: {
        placeholder: '如:春节、国庆节',
      },
      rules: 'required',
    },
    {
      fieldName: 'date',
      label: '日期',
      component: 'DatePicker',
      componentProps: {
        placeholder: '选择日期',
        valueFormat: 'YYYY-MM-DD',
        style: { width: '100%' },
      },
      rules: 'required',
    },
    {
      fieldName: 'type',
      label: '类型',
      component: 'RadioGroup',
      componentProps: {
        options: getDictOptions(DICT_TYPE.HRM_HOLIDAY_TYPE, 'number'),
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'isLegal',
      label: '法定节假日',
      component: 'Checkbox',
      componentProps: {
        // @ts-ignore
        trueValue: 1,
        falseValue: 0,
      },
      help: '勾选表示为法定节假日',
    },
    {
      fieldName: 'remark',
      label: '备注',
      component: 'Textarea',
      formItemClass: 'col-span-2',
      componentProps: {
        placeholder: '请输入备注',
        rows: 2,
        maxlength: 500,
        showCount: true,
      },
    },
  ];
}
/** åˆ—表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'name',
      label: '节假日名称',
      component: 'Input',
      componentProps: {
        allowClear: true,
        placeholder: '请输入节假日名称',
      },
    },
    {
      fieldName: 'date',
      label: '日期',
      component: 'RangePicker',
      componentProps: {
        allowClear: true,
        valueFormat: 'YYYY-MM-DD',
        style: { width: '100%' },
      },
    },
    {
      fieldName: 'type',
      label: '类型',
      component: 'Select',
      componentProps: {
        allowClear: true,
        options: getDictOptions(DICT_TYPE.HRM_HOLIDAY_TYPE, 'number'),
        placeholder: '请选择类型',
      },
    },
    {
      fieldName: 'isLegal',
      label: '法定节假日',
      component: 'Select',
      componentProps: {
        allowClear: true,
        options: [
          { label: '是', value: 1 },
          { label: '否', value: 0 },
        ],
        placeholder: '请选择',
      },
    },
  ];
}
/** åˆ—表的字段 */
export function useGridColumns(): VxeTableGridOptions<HrmAttendanceHolidayApi.Holiday>['columns'] {
  return [
    { field: 'name', title: '节假日名称', minWidth: 150 },
    { field: 'date', title: '日期', width: 120 },
    {
      field: 'type',
      title: '类型',
      width: 100,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.HRM_HOLIDAY_TYPE },
      },
    },
    {
      field: 'isLegal',
      title: '法定节假日',
      width: 100,
      slots: {
        default: 'isLegal',
      },
    },
    { field: 'remark', title: '备注', minWidth: 200 },
    {
      field: 'createTime',
      title: '创建时间',
      width: 180,
      formatter: 'formatDateTime',
    },
    {
      title: '操作',
      width: 150,
      fixed: 'right',
      slots: {
        default: 'actions',
      },
    },
  ];
}
src/views/hrm/attendance/holiday/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,147 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceHolidayApi } from '#/api/hrm/attendance/holiday';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '#/packages/utils/src';
import { message, Modal, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteAttendanceHoliday, exportAttendanceHoliday, getAttendanceHolidayPage } from '#/api/hrm/attendance/holiday';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
function handleRefresh() {
  gridApi.query();
}
/** æ–°å¢žèŠ‚å‡æ—¥ */
function handleCreate() {
  formModalApi.setData({ formType: 'create' }).open();
}
/** ç¼–辑节假日 */
function handleUpdate(row: HrmAttendanceHolidayApi.Holiday) {
  formModalApi.setData({ id: row.id, formType: 'update' }).open();
}
/** æŸ¥çœ‹èŠ‚å‡æ—¥ */
function handleDetail(row: HrmAttendanceHolidayApi.Holiday) {
  formModalApi.setData({ id: row.id, formType: 'detail' }).open();
}
/** åˆ é™¤èŠ‚å‡æ—¥ */
async function handleDelete(id: number) {
  Modal.confirm({
    title: '确认删除',
    content: '确定要删除该节假日吗?',
    okText: '确定',
    cancelText: '取消',
    onOk: async () => {
      await deleteAttendanceHoliday(id);
      message.success('删除成功');
      handleRefresh();
    },
  });
}
/** å¯¼å‡ºèŠ‚å‡æ—¥ */
async function handleExport() {
  const data = await exportAttendanceHoliday(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '节假日配置.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
  formOptions: {
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(),
    height: 'auto',
    keepSource: true,
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) =>
          await getAttendanceHolidayPage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
          }),
      },
    },
    rowConfig: {
      keyField: 'id',
      isHover: true,
    },
    toolbarConfig: {
      refresh: true,
      search: true,
    },
  } as VxeTableGridOptions<HrmAttendanceHolidayApi.Holiday>,
});
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <Grid table-title="节假日配置列表">
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: $t('ui.actionTitle.create', ['节假日']),
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['hrm:attendance-holiday:create'],
              onClick: handleCreate,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
              auth: ['hrm:attendance-holiday:export'],
              onClick: handleExport,
            },
          ]"
        />
      </template>
      <template #isLegal="{ row }">
        <Tag v-if="row.isLegal === 1" color="red">法定</Tag>
        <span v-else>-</span>
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
            {
              label: '查看',
              type: 'link',
              onClick: handleDetail.bind(null, row),
            },
            {
              label: $t('ui.actionTitle.edit'),
              type: 'link',
              auth: ['hrm:attendance-holiday:update'],
              onClick: handleUpdate.bind(null, row),
            },
            {
              label: $t('ui.actionTitle.delete'),
              type: 'link',
              danger: true,
              auth: ['hrm:attendance-holiday:delete'],
              onClick: handleDelete.bind(null, row.id!),
            },
          ]"
        />
      </template>
    </Grid>
  </Page>
</template>
src/views/hrm/attendance/holiday/modules/form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
<script lang="ts" setup>
import type { FormType } from '../data';
import type { HrmAttendanceHolidayApi } from '#/api/hrm/attendance/holiday';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createAttendanceHoliday, getAttendanceHoliday, updateAttendanceHoliday } from '#/api/hrm/attendance/holiday';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
const isDetail = computed(() => formType.value === 'detail');
const getTitle = computed(() => {
  if (formType.value === 'detail') {
    return '查看节假日';
  }
  if (formType.value === 'update') {
    return '编辑节假日';
  }
  return '新增节假日';
});
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 100,
  },
  wrapperClass: 'grid-cols-2',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (isDetail.value) {
      await modalApi.close();
      return;
    }
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const data = (await formApi.getValues()) as HrmAttendanceHolidayApi.Holiday;
    try {
      if (formType.value === 'update') {
        await updateAttendanceHoliday(data);
      } else {
        await createAttendanceHoliday(data);
      }
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      return;
    }
    const data = modalApi.getData<{ formType: FormType; id?: number }>();
    formType.value = data.formType;
    formApi.setState({ schema: useFormSchema(data.formType) });
    formApi.setDisabled(data.formType === 'detail');
    modalApi.setState({ showConfirmButton: data.formType !== 'detail' });
    if (!data?.id) {
      return;
    }
    modalApi.lock();
    try {
      await formApi.setValues(await getAttendanceHoliday(data.id));
    } finally {
      modalApi.unlock();
    }
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-[600px]">
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/attendance/record/index.vue
@@ -12,6 +12,7 @@
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import ClockPanel from './modules/clock-panel.vue';
import ImportForm from './modules/import-form.vue';
const [ImportModal, importModalApi] = useVbenModal({
@@ -19,9 +20,19 @@
  destroyOnClose: true,
});
const [ClockModal, clockModalApi] = useVbenModal({
  connectedComponent: ClockPanel,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
function handleRefresh() {
  gridApi.query();
}
/** æ‰“卡 */
function handleClock() {
  clockModalApi.open();
}
/** å¯¼å…¥è€ƒå‹¤è®°å½• */
@@ -68,11 +79,19 @@
<template>
  <Page auto-content-height>
    <ImportModal @success="handleRefresh" />
    <ClockModal @success="handleRefresh" />
    <Grid table-title="考勤记录列表">
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: '打卡',
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['hrm:attendance-record:clock'],
              onClick: handleClock,
            },
            {
              label: '导入',
              type: 'primary',
              icon: ACTION_ICON.UPLOAD,
src/views/hrm/attendance/record/modules/clock-panel.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { clockIn, clockOut } from '#/api/hrm/attendance/record';
const emit = defineEmits(['success']);
const location = ref('公司前台');
const loading = ref(false);
/** ä¸Šç­æ‰“卡 */
async function handleClockIn() {
  loading.value = true;
  try {
    await clockIn({ location: location.value });
    message.success('上班打卡成功');
    emit('success');
  } catch (e: any) {
    message.error(e.message || '打卡失败');
  } finally {
    loading.value = false;
  }
}
/** ä¸‹ç­æ‰“卡 */
async function handleClockOut() {
  loading.value = true;
  try {
    await clockOut({ location: location.value });
    message.success('下班打卡成功');
    emit('success');
  } catch (e: any) {
    message.error(e.message || '打卡失败');
  } finally {
    loading.value = false;
  }
}
const [Modal, modalApi] = useVbenModal({
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      location.value = '公司前台';
    }
  },
});
</script>
<template>
  <Modal title="打卡" class="w-[400px]">
    <div class="p-4">
      <div class="mb-4">
        <label class="mr-2">打卡地点:</label>
        <a-input
          v-model:value="location"
          placeholder="请输入打卡地点"
          style="width: 200px"
        />
      </div>
      <div class="flex gap-4">
        <a-button
          type="primary"
          size="large"
          :loading="loading"
          @click="handleClockIn"
        >
          ä¸Šç­æ‰“卡
        </a-button>
        <a-button
          size="large"
          :loading="loading"
          @click="handleClockOut"
        >
          ä¸‹ç­æ‰“卡
        </a-button>
      </div>
    </div>
  </Modal>
</template>
src/views/hrm/attendance/rule/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,206 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceRuleApi } from '#/api/hrm/attendance/rule';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictOptions } from '#/packages/effects/hooks/src';
import { getDeptList } from '#/api/system/dept';
import { handleTree } from '#/packages/utils/src';
/** è¡¨å•类型 */
export type FormType = 'create' | 'update' | 'detail';
/** æ–°å¢ž/编辑考勤规则的表单 */
export function useFormSchema(formType: FormType): VbenFormSchema[] {
  return [
    {
      fieldName: 'id',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'name',
      label: '规则名称',
      component: 'Input',
      componentProps: {
        placeholder: '请输入规则名称',
      },
      rules: 'required',
    },
    {
      fieldName: 'deptId',
      label: '适用部门',
      component: 'ApiTreeSelect',
      componentProps: {
        api: async () => {
          const data = await getDeptList();
          return handleTree(data);
        },
        labelField: 'name',
        valueField: 'id',
        childrenField: 'children',
        allowClear: true,
        placeholder: '请选择部门(不选表示全局)',
        treeDefaultExpandAll: true,
      },
    },
    {
      fieldName: 'workStartTime',
      label: '上班时间',
      component: 'TimePicker',
      componentProps: {
        placeholder: '选择上班时间',
        valueFormat: 'HH:mm:ss',
        format: 'HH:mm:ss',
        style: { width: '100%' },
      },
      rules: 'required',
    },
    {
      fieldName: 'workEndTime',
      label: '下班时间',
      component: 'TimePicker',
      componentProps: {
        placeholder: '选择下班时间',
        valueFormat: 'HH:mm:ss',
        format: 'HH:mm:ss',
        style: { width: '100%' },
      },
      rules: 'required',
    },
    {
      fieldName: 'lateAllowMinutes',
      label: '迟到允许(分钟)',
      component: 'InputNumber',
      componentProps: {
        placeholder: '分钟',
        min: 0,
        max: 60,
        style: { width: '100%' },
      },
    },
    {
      fieldName: 'earlyAllowMinutes',
      label: '早退允许(分钟)',
      component: 'InputNumber',
      componentProps: {
        placeholder: '分钟',
        min: 0,
        max: 60,
        style: { width: '100%' },
      },
    },
    {
      fieldName: 'overtimeThreshold',
      label: '加班起算(小时)',
      component: 'InputNumber',
      componentProps: {
        placeholder: '小时',
        min: 0,
        precision: 1,
        style: { width: '100%' },
      },
    },
    {
      fieldName: 'status',
      label: '状态',
      component: 'RadioGroup',
      componentProps: {
        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
      },
    },
    {
      fieldName: 'remark',
      label: '备注',
      component: 'Textarea',
      formItemClass: 'col-span-2',
      componentProps: {
        placeholder: '请输入备注',
        rows: 2,
        maxlength: 500,
        showCount: true,
      },
    },
  ];
}
/** åˆ—表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'name',
      label: '规则名称',
      component: 'Input',
      componentProps: {
        allowClear: true,
        placeholder: '请输入规则名称',
      },
    },
    {
      fieldName: 'deptId',
      label: '适用部门',
      component: 'ApiTreeSelect',
      componentProps: {
        api: async () => {
          const data = await getDeptList();
          return handleTree(data);
        },
        labelField: 'name',
        valueField: 'id',
        childrenField: 'children',
        allowClear: true,
        placeholder: '请选择部门',
        treeDefaultExpandAll: true,
      },
    },
    {
      fieldName: 'status',
      label: '状态',
      component: 'Select',
      componentProps: {
        allowClear: true,
        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
        placeholder: '请选择状态',
      },
    },
  ];
}
/** åˆ—表的字段 */
export function useGridColumns(): VxeTableGridOptions<HrmAttendanceRuleApi.AttendanceRule>['columns'] {
  return [
    { field: 'name', title: '规则名称', minWidth: 150 },
    {
      field: 'deptName',
      title: '适用部门',
      minWidth: 120,
      formatter: ({ cellValue }) => cellValue || '全局',
    },
    { field: 'workStartTime', title: '上班时间', width: 100 },
    { field: 'workEndTime', title: '下班时间', width: 100 },
    { field: 'lateAllowMinutes', title: '迟到允许(分钟)', width: 120 },
    { field: 'earlyAllowMinutes', title: '早退允许(分钟)', width: 120 },
    { field: 'overtimeThreshold', title: '加班起算(小时)', width: 120 },
    {
      field: 'status',
      title: '状态',
      width: 80,
      cellRender: {
        name: 'CellDict',
        props: { type: DICT_TYPE.COMMON_STATUS },
      },
    },
    {
      title: '操作',
      width: 150,
      fixed: 'right',
      slots: {
        default: 'actions',
      },
    },
  ];
}
src/views/hrm/attendance/rule/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceRuleApi } from '#/api/hrm/attendance/rule';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '#/packages/utils/src';
import { message, Modal } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteAttendanceRule, exportAttendanceRule, getAttendanceRulePage } from '#/api/hrm/attendance/rule';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
  connectedComponent: Form,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
function handleRefresh() {
  gridApi.query();
}
/** æ–°å¢žè€ƒå‹¤è§„则 */
function handleCreate() {
  formModalApi.setData({ formType: 'create' }).open();
}
/** ç¼–辑考勤规则 */
function handleUpdate(row: HrmAttendanceRuleApi.AttendanceRule) {
  formModalApi.setData({ id: row.id, formType: 'update' }).open();
}
/** æŸ¥çœ‹è€ƒå‹¤è§„则 */
function handleDetail(row: HrmAttendanceRuleApi.AttendanceRule) {
  formModalApi.setData({ id: row.id, formType: 'detail' }).open();
}
/** åˆ é™¤è€ƒå‹¤è§„则 */
async function handleDelete(id: number) {
  Modal.confirm({
    title: '确认删除',
    content: '确定要删除该考勤规则吗?',
    okText: '确定',
    cancelText: '取消',
    onOk: async () => {
      await deleteAttendanceRule(id);
      message.success('删除成功');
      handleRefresh();
    },
  });
}
/** å¯¼å‡ºè€ƒå‹¤è§„则 */
async function handleExport() {
  const data = await exportAttendanceRule(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '考勤规则.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
  formOptions: {
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(),
    height: 'auto',
    keepSource: true,
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) =>
          await getAttendanceRulePage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
          }),
      },
    },
    rowConfig: {
      keyField: 'id',
      isHover: true,
    },
    toolbarConfig: {
      refresh: true,
      search: true,
    },
  } as VxeTableGridOptions<HrmAttendanceRuleApi.AttendanceRule>,
});
</script>
<template>
  <Page auto-content-height>
    <FormModal @success="handleRefresh" />
    <Grid table-title="考勤规则列表">
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: $t('ui.actionTitle.create', ['考勤规则']),
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['hrm:attendance-rule:create'],
              onClick: handleCreate,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
              auth: ['hrm:attendance-rule:export'],
              onClick: handleExport,
            },
          ]"
        />
      </template>
      <template #actions="{ row }">
        <TableAction
          :actions="[
            {
              label: '查看',
              type: 'link',
              onClick: handleDetail.bind(null, row),
            },
            {
              label: $t('ui.actionTitle.edit'),
              type: 'link',
              auth: ['hrm:attendance-rule:update'],
              onClick: handleUpdate.bind(null, row),
            },
            {
              label: $t('ui.actionTitle.delete'),
              type: 'link',
              danger: true,
              auth: ['hrm:attendance-rule:delete'],
              onClick: handleDelete.bind(null, row.id!),
            },
          ]"
        />
      </template>
    </Grid>
  </Page>
</template>
src/views/hrm/attendance/rule/modules/form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
<script lang="ts" setup>
import type { FormType } from '../data';
import type { HrmAttendanceRuleApi } from '#/api/hrm/attendance/rule';
import { computed, ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createAttendanceRule, getAttendanceRule, updateAttendanceRule } from '#/api/hrm/attendance/rule';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
const isDetail = computed(() => formType.value === 'detail');
const getTitle = computed(() => {
  if (formType.value === 'detail') {
    return '查看考勤规则';
  }
  if (formType.value === 'update') {
    return '编辑考勤规则';
  }
  return '新增考勤规则';
});
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 120,
  },
  wrapperClass: 'grid-cols-2',
  layout: 'horizontal',
  schema: [],
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    if (isDetail.value) {
      await modalApi.close();
      return;
    }
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const data = (await formApi.getValues()) as HrmAttendanceRuleApi.AttendanceRule;
    try {
      if (formType.value === 'update') {
        await updateAttendanceRule(data);
      } else {
        await createAttendanceRule(data);
      }
      await modalApi.close();
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      return;
    }
    const data = modalApi.getData<{ formType: FormType; id?: number }>();
    formType.value = data.formType;
    formApi.setState({ schema: useFormSchema(data.formType) });
    formApi.setDisabled(data.formType === 'detail');
    modalApi.setState({ showConfirmButton: data.formType !== 'detail' });
    if (!data?.id) {
      return;
    }
    modalApi.lock();
    try {
      await formApi.setValues(await getAttendanceRule(data.id));
    } finally {
      modalApi.unlock();
    }
  },
});
</script>
<template>
  <Modal :title="getTitle" class="w-[800px]">
    <Form class="mx-4" />
  </Modal>
</template>
src/views/hrm/attendance/summary/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceSummaryApi } from '#/api/hrm/attendance/summary';
import { getDeptList } from '#/api/system/dept';
import { handleTree } from '#/packages/utils/src';
import { getEmployeeSimpleList } from '#/api/hrm/employee';
/** åˆ—表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'yearMonth',
      label: '年月',
      component: 'DatePicker',
      componentProps: {
        picker: 'month',
        allowClear: true,
        valueFormat: 'YYYY-MM',
        format: 'YYYY-MM',
        style: { width: '100%' },
        placeholder: '选择年月',
      },
    },
    {
      fieldName: 'deptId',
      label: '部门',
      component: 'ApiTreeSelect',
      componentProps: {
        api: async () => {
          const data = await getDeptList();
          return handleTree(data);
        },
        labelField: 'name',
        valueField: 'id',
        childrenField: 'children',
        allowClear: true,
        placeholder: '请选择部门',
        treeDefaultExpandAll: true,
      },
    },
    {
      fieldName: 'userName',
      label: '员工姓名',
      component: 'Input',
      componentProps: {
        allowClear: true,
        placeholder: '请输入员工姓名',
      },
    },
  ];
}
/** åˆ—表的字段 */
export function useGridColumns(): VxeTableGridOptions<HrmAttendanceSummaryApi.AttendanceSummary>['columns'] {
  return [
    { field: 'userName', title: '员工姓名', minWidth: 100 },
    { field: 'deptName', title: '部门', minWidth: 120 },
    { field: 'yearMonth', title: '年月', width: 100 },
    { field: 'workDays', title: '应出勤天数', width: 100 },
    { field: 'actualDays', title: '实际出勤天数', width: 110 },
    {
      field: 'lateCount',
      title: '迟到次数',
      width: 90,
      slots: {
        default: 'lateCount',
      },
    },
    {
      field: 'earlyCount',
      title: '早退次数',
      width: 90,
      slots: {
        default: 'earlyCount',
      },
    },
    {
      field: 'absentCount',
      title: '缺卡次数',
      width: 90,
      slots: {
        default: 'absentCount',
      },
    },
    { field: 'overtimeHours', title: '加班时长(h)', width: 100 },
    { field: 'leaveHours', title: '请假时长(h)', width: 100 },
  ];
}
/** ç”Ÿæˆç»Ÿè®¡å¼¹çª—的表单 */
export function useGenerateFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'yearMonth',
      label: '年月',
      component: 'DatePicker',
      componentProps: {
        picker: 'month',
        valueFormat: 'YYYY-MM',
        format: 'YYYY-MM',
        style: { width: '100%' },
        placeholder: '选择年月',
      },
      rules: 'required',
    },
    {
      fieldName: 'rangeType',
      label: '生成范围',
      component: 'RadioGroup',
      componentProps: {
        options: [
          { label: '全公司', value: 'all' },
          { label: '指定部门', value: 'dept' },
          { label: '指定员工', value: 'user' },
        ],
      },
      defaultValue: 'all',
    },
    {
      fieldName: 'deptId',
      label: '部门',
      component: 'ApiTreeSelect',
      dependencies: {
        triggerFields: ['rangeType'],
        show: (values) => values.rangeType === 'dept',
        required: (values) => values.rangeType === 'dept',
      },
      componentProps: {
        api: async () => {
          const data = await getDeptList();
          return handleTree(data);
        },
        labelField: 'name',
        valueField: 'id',
        childrenField: 'children',
        placeholder: '选择部门',
        treeDefaultExpandAll: true,
      },
    },
    {
      fieldName: 'userId',
      label: '员工',
      component: 'ApiSelect',
      dependencies: {
        triggerFields: ['rangeType'],
        show: (values) => values.rangeType === 'user',
        required: (values) => values.rangeType === 'user',
      },
      componentProps: {
        api: async () => {
          const data = await getEmployeeSimpleList();
          return data;
        },
        labelField: 'name',
        valueField: 'id',
        placeholder: '选择员工',
        showSearch: true,
        filterOption: true,
      },
    },
  ];
}
src/views/hrm/attendance/summary/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { HrmAttendanceSummaryApi } from '#/api/hrm/attendance/summary';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '#/packages/utils/src';
import { Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { exportAttendanceSummary, getAttendanceSummaryPage } from '#/api/hrm/attendance/summary';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import GenerateForm from './modules/generate-form.vue';
const [GenerateModal, generateModalApi] = useVbenModal({
  connectedComponent: GenerateForm,
  destroyOnClose: true,
});
/** åˆ·æ–°è¡¨æ ¼ */
function handleRefresh() {
  gridApi.query();
}
/** ç”Ÿæˆç»Ÿè®¡ */
function handleGenerate() {
  generateModalApi.open();
}
/** å¯¼å‡ºè€ƒå‹¤ç»Ÿè®¡ */
async function handleExport() {
  const data = await exportAttendanceSummary(await gridApi.formApi.getValues());
  downloadFileFromBlobPart({ fileName: '考勤统计.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
  formOptions: {
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(),
    height: 'auto',
    keepSource: true,
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) =>
          await getAttendanceSummaryPage({
            pageNo: page.currentPage,
            pageSize: page.pageSize,
            ...formValues,
          }),
      },
    },
    rowConfig: {
      keyField: 'id',
      isHover: true,
    },
    toolbarConfig: {
      refresh: true,
      search: true,
    },
  } as VxeTableGridOptions<HrmAttendanceSummaryApi.AttendanceSummary>,
});
</script>
<template>
  <Page auto-content-height>
    <GenerateModal @success="handleRefresh" />
    <Grid table-title="考勤统计列表">
      <template #toolbar-tools>
        <TableAction
          :actions="[
            {
              label: '生成统计',
              type: 'primary',
              icon: ACTION_ICON.ADD,
              auth: ['hrm:attendance-summary:generate'],
              onClick: handleGenerate,
            },
            {
              label: $t('ui.actionTitle.export'),
              type: 'primary',
              icon: ACTION_ICON.DOWNLOAD,
              auth: ['hrm:attendance-summary:export'],
              onClick: handleExport,
            },
          ]"
        />
      </template>
      <template #lateCount="{ row }">
        <Tag v-if="row.lateCount && row.lateCount > 0" color="warning">{{ row.lateCount }}</Tag>
        <span v-else>{{ row.lateCount || 0 }}</span>
      </template>
      <template #earlyCount="{ row }">
        <Tag v-if="row.earlyCount && row.earlyCount > 0" color="warning">{{ row.earlyCount }}</Tag>
        <span v-else>{{ row.earlyCount || 0 }}</span>
      </template>
      <template #absentCount="{ row }">
        <Tag v-if="row.absentCount && row.absentCount > 0" color="error">{{ row.absentCount }}</Tag>
        <span v-else>{{ row.absentCount || 0 }}</span>
      </template>
    </Grid>
  </Page>
</template>
src/views/hrm/attendance/summary/modules/generate-form.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
<script lang="ts" setup>
import { useVbenModal } from '#/packages/effects/common-ui/src';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { generateAttendanceSummary } from '#/api/hrm/attendance/summary';
import { useGenerateFormSchema } from '../data';
const emit = defineEmits(['success']);
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
    formItemClass: 'col-span-1',
    labelWidth: 100,
  },
  layout: 'horizontal',
  schema: useGenerateFormSchema(),
  showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) {
      return;
    }
    modalApi.lock();
    const data = await formApi.getValues();
    try {
      const params: any = {
        yearMonth: data.yearMonth,
      };
      if (data.rangeType === 'dept') {
        params.deptId = data.deptId;
      } else if (data.rangeType === 'user') {
        params.userId = data.userId;
      }
      await generateAttendanceSummary(params);
      await modalApi.close();
      emit('success');
      message.success('生成考勤统计成功');
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      formApi.resetForm();
    }
  },
});
</script>
<template>
  <Modal title="生成考勤统计" class="w-[500px]">
    <Form class="mx-4" />
  </Modal>
</template>