| | |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | | /** å®¡æ¹æµç¨ */ |
| | | export interface ApproveProcess { |
| | | key: string; |
| | | name: string; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢èå¤å¼å¸¸ç³è¯·å页å表 */ |
| | |
| | | } |
| | | |
| | | /** æäº¤èå¤å¼å¸¸ç³è¯· */ |
| | | 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', |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 }); |
| | | } |
| | |
| | | /** 导åºèå¤è®°å½ */ |
| | | 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 }, |
| | | ); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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 }); |
| | | } |
| | |
| | | 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 åæ¾æ¹å¼ |
| | |
| | | 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'; |
| | |
| | | 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({ |
| | |
| | | /> |
| | | </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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| | |
| | | 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({ |
| | |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ClockModal, clockModalApi] = useVbenModal({ |
| | | connectedComponent: ClockPanel, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** æå¡ */ |
| | | function handleClock() { |
| | | clockModalApi.open(); |
| | | } |
| | | |
| | | /** 导å
¥èå¤è®°å½ */ |
| | |
| | | <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, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |