银川
1.员工管理页面开发、联调
2.薪资发放页面开发联调
3.薪酬管理页面开发联调
4.添加请假扣薪管理页面开发联调
| | |
| | | /** 导åºåå·¥ */ |
| | | export function exportEmployee(params: any) { |
| | | return requestClient.download('/hrm/employee/export-excel', { params }); |
| | | } |
| | | |
| | | /** å 餿è²ç»å */ |
| | | export function deleteEducation(id: number) { |
| | | return requestClient.delete(`/hrm/employee/education/delete?id=${id}`); |
| | | } |
| | | |
| | | /** å é¤å·¥ä½ç»å */ |
| | | export function deleteWorkHistory(id: number) { |
| | | return requestClient.delete(`/hrm/employee/work-history/delete?id=${id}`); |
| | | } |
| | | |
| | | /** å é¤ç´§æ¥è系人 */ |
| | | export function deleteEmergencyContact(id: number) { |
| | | return requestClient.delete(`/hrm/employee/emergency-contact/delete?id=${id}`); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageResult } from '#/packages/effects/request/src'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace HrmLeaveTypeConfigApi { |
| | | /** 请åç±»åæ£æ¬¾é
ç½® */ |
| | | export interface LeaveTypeConfig { |
| | | id?: number; |
| | | leaveType?: number; |
| | | name?: string; |
| | | deductionRatio?: number; |
| | | remark?: string; |
| | | status?: number; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** è·å请åç±»åé
ç½®å表 */ |
| | | export function getLeaveTypeConfigList() { |
| | | return requestClient.get<HrmLeaveTypeConfigApi.LeaveTypeConfig[]>( |
| | | '/hrm/leave-type-config/list', |
| | | ); |
| | | } |
| | | |
| | | /** æ´æ°è¯·åç±»åé
ç½® */ |
| | | export function updateLeaveTypeConfig(data: HrmLeaveTypeConfigApi.LeaveTypeConfig) { |
| | | return requestClient.put('/hrm/leave-type-config/update', data); |
| | | } |
| | |
| | | housingFundDeduction?: number; |
| | | taxDeduction?: number; |
| | | otherDeduction?: number; |
| | | leaveDeduction?: number; |
| | | actualSalary?: number; |
| | | workDays?: number; |
| | | overtimeHours?: number; |
| | |
| | | return requestClient.post('/hrm/salary/calculation/confirm', { ids }); |
| | | } |
| | | |
| | | /** æ¤éèªé
¬æ ¸ç®ç¡®è®¤ */ |
| | | export function revokeSalaryCalculation(ids: number[]) { |
| | | return requestClient.post('/hrm/salary/calculation/revoke', { ids }); |
| | | } |
| | | |
| | | /** å é¤èªé
¬æ ¸ç®è®°å½ */ |
| | | export function deleteSalaryCalculation(id: number) { |
| | | return requestClient.delete(`/hrm/salary/calculation/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºèªé
¬æ ¸ç®è¡¨ */ |
| | | export function exportSalaryCalculation(params: any) { |
| | | return requestClient.download('/hrm/salary/calculation/export-excel', { params }); |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace HrmSalaryPaymentApi { |
| | | /** èªèµåæ¾è®°å½ */ |
| | | /** èªèµåæ¾å°è´¦ */ |
| | | export interface SalaryPayment { |
| | | id?: number; |
| | | no?: string; |
| | | period?: string; |
| | | totalAmount?: number; |
| | | totalCount?: number; |
| | | paymentMethod?: number; |
| | | paymentTime?: string; |
| | | status?: number; |
| | | fileUrl?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | |
| | | /** èªèµåæ¾æç» */ |
| | | export interface SalaryPaymentDetail { |
| | | id?: number; |
| | | paymentId?: number; |
| | | calculationId?: number; |
| | | userId?: number; |
| | | userName?: string; |
| | | deptId?: number; |
| | | deptName?: string; |
| | | baseSalary?: number; |
| | | performanceSalary?: number; |
| | | overtimePay?: number; |
| | | mealAllowance?: number; |
| | | transportAllowance?: number; |
| | | otherAllowance?: number; |
| | | socialSecurityDeduction?: number; |
| | | housingFundDeduction?: number; |
| | | taxDeduction?: number; |
| | | otherDeduction?: number; |
| | | leaveDeduction?: number; |
| | | actualSalary?: number; |
| | | paymentMethod?: number; |
| | | paymentTime?: string; |
| | | paymentNo?: string; |
| | | status?: number; |
| | | paymentTime?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | |
| | | /** èªèµåæ¾æ¥è¯¢åæ° */ |
| | | export interface SalaryPaymentPageParam extends PageParam { |
| | | period?: string; |
| | | status?: number; |
| | | } |
| | | |
| | | /** èªèµåæ¾æç»æ¥è¯¢åæ° */ |
| | | export interface SalaryPaymentDetailPageParam extends PageParam { |
| | | paymentId?: number; |
| | | userId?: number; |
| | | deptId?: number; |
| | | status?: number; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢èªèµåæ¾å页å表 */ |
| | | /** æ¥è¯¢èªèµåæ¾å°è´¦å页å表 */ |
| | | export function getSalaryPaymentPage( |
| | | params: HrmSalaryPaymentApi.SalaryPaymentPageParam, |
| | | ) { |
| | |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢èªèµåæ¾è¯¦æ
*/ |
| | | /** æ¥è¯¢èªèµåæ¾å°è´¦è¯¦æ
*/ |
| | | export function getSalaryPayment(id: number) { |
| | | return requestClient.get<HrmSalaryPaymentApi.SalaryPayment>( |
| | | `/hrm/salary/payment/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ§è¡èªèµåæ¾ */ |
| | | export function paySalary(ids: number[]) { |
| | | return requestClient.post('/hrm/salary/payment/pay', { ids }); |
| | | /** æ¥è¯¢èªèµåæ¾æç»å表 */ |
| | | export function getSalaryPaymentDetailList(paymentId: number) { |
| | | return requestClient.get<HrmSalaryPaymentApi.SalaryPaymentDetail[]>( |
| | | `/hrm/salary/payment/detail/list?paymentId=${paymentId}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ¤éèªèµåæ¾ */ |
| | | export function revokeSalaryPayment(id: number) { |
| | | return requestClient.post(`/hrm/salary/payment/revoke?id=${id}`); |
| | | /** çæèªèµåæ¾å°è´¦ */ |
| | | export function generateSalaryPayment(period: string) { |
| | | return requestClient.post<number>( |
| | | `/hrm/salary/payment/generate?period=${period}`, |
| | | ); |
| | | } |
| | | |
| | | /** åæ¾èªèµï¼æ´ä¸ªå°è´¦ï¼ */ |
| | | export function paySalary(id: number) { |
| | | return requestClient.post(`/hrm/salary/payment/pay?id=${id}`); |
| | | } |
| | | |
| | | /** æ¹éåæ¾èªèµæç» */ |
| | | export function paySalaryDetail(detailIds: number[]) { |
| | | return requestClient.post('/hrm/salary/payment/pay-detail', { detailIds }); |
| | | } |
| | | |
| | | /** æ¤éèªèµåæ¾æç» */ |
| | | export function revokeSalaryPaymentDetail(detailIds: number[]) { |
| | | return requestClient.post('/hrm/salary/payment/revoke-detail', { detailIds }); |
| | | } |
| | | |
| | | /** å é¤èªèµåæ¾å°è´¦ */ |
| | | export function deleteSalaryPayment(id: number) { |
| | | return requestClient.delete(`/hrm/salary/payment/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºèªèµåæ¾è¡¨ */ |
| | |
| | | field: 'gender', |
| | | title: 'æ§å«', |
| | | width: 80, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.HRM_GENDER }, |
| | | slots: { |
| | | default: 'gender', |
| | | }, |
| | | }, |
| | | { field: 'phone', title: 'ææºå·ç ', minWidth: 120 }, |
| | |
| | | field: 'employeeStatus', |
| | | title: 'åå·¥ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.HRM_EMPLOYEE_STATUS }, |
| | | slots: { |
| | | default: 'employeeStatus', |
| | | }, |
| | | }, |
| | | { field: 'hireDate', title: 'å
¥èæ¥æ', width: 110 }, |
| | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { Button, message } from 'ant-design-vue'; |
| | | import { Button, message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictLabel } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { deleteEmployee, exportEmployee, getEmployeePage } from '#/api/hrm/employee'; |
| | |
| | | <template #employeeNo="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)">{{ row.employeeNo }}</Button> |
| | | </template> |
| | | <template #gender="{ row }"> |
| | | <Tag :color="row.gender === 1 ? 'blue' : 'pink'"> |
| | | {{ getDictLabel(DICT_TYPE.HRM_GENDER, row.gender) }} |
| | | </Tag> |
| | | </template> |
| | | <template #employeeStatus="{ row }"> |
| | | <Tag :color="row.employeeStatus === 1 ? 'success' : row.employeeStatus === 2 ? 'processing' : 'default'"> |
| | | {{ getDictLabel(DICT_TYPE.HRM_EMPLOYEE_STATUS, row.employeeStatus) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | field: 'leaveType', |
| | | title: '请åç±»å', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.HRM_LEAVE_TYPE }, |
| | | slots: { |
| | | default: 'leaveType', |
| | | }, |
| | | }, |
| | | { field: 'startTime', title: 'å¼å§æ¶é´', width: 160 }, |
| | |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictLabel } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 请åç±»åé¢è² */ |
| | | function getLeaveTypeColor(type: number): string { |
| | | switch (type) { |
| | | case 1: return 'green'; // å¹´å |
| | | case 2: return 'orange'; // äºå |
| | | case 3: return 'red'; // ç
å |
| | | case 4: return 'purple'; // å©å |
| | | case 5: return 'cyan'; // 产å |
| | | case 6: return 'magenta'; // 丧å |
| | | case 7: return 'geekblue'; // è°ä¼ |
| | | default: return 'default'; |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #leaveType="{ row }"> |
| | | <Tag :color="getLeaveTypeColor(row.leaveType)"> |
| | | {{ getDictLabel(DICT_TYPE.HRM_LEAVE_TYPE, row.leaveType) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | { field: 'housingFundDeduction', title: 'å
¬ç§¯éæ£æ¬¾', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'taxDeduction', title: 'ä¸ªç¨æ£æ¬¾', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'otherDeduction', title: 'å
¶ä»æ£æ¬¾', width: 80, formatter: 'formatAmount2' }, |
| | | { field: 'leaveDeduction', title: 'è¯·åæ£æ¬¾', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'actualSalary', title: 'å®åå·¥èµ', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'workDays', title: 'åºå¤å¤©æ°', width: 80 }, |
| | | { field: 'overtimeHours', title: 'å çæ¶é¿(h)', width: 100 }, |
| | |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.HRM_SALARY_STATUS }, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'status', |
| | | }, |
| | | }, |
| | | { |
| | |
| | | { title: 'å
¬ç§¯éæ£æ¬¾', dataIndex: 'housingFundDeduction', width: 110 }, |
| | | { title: 'ä¸ªç¨æ£æ¬¾', dataIndex: 'taxDeduction', width: 110 }, |
| | | { title: 'å
¶ä»æ£æ¬¾', dataIndex: 'otherDeduction', width: 100 }, |
| | | { title: 'å®åå·¥èµ', dataIndex: 'actualSalary', width: 100, fixed: 'right' }, |
| | | { title: 'è¯·åæ£æ¬¾', dataIndex: 'leaveDeduction', width: 110 }, |
| | | { title: 'åºå¤å¤©æ°', dataIndex: 'workDays', width: 90 }, |
| | | { title: 'å çæ¶é¿', dataIndex: 'overtimeHours', width: 100 }, |
| | | { title: 'å®åå·¥èµ', dataIndex: 'actualSalary', width: 100, fixed: 'right' }, |
| | | ]; |
| | | } |
| | |
| | | import { Page } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, InputNumber, Alert } from 'ant-design-vue'; |
| | | import { message, Modal, Form, FormItem, DatePicker, TreeSelect, Table, InputNumber, Alert, Tag } from 'ant-design-vue'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictLabel } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | const deduction = (record.socialSecurityDeduction || 0) + |
| | | (record.housingFundDeduction || 0) + |
| | | (record.taxDeduction || 0) + |
| | | (record.otherDeduction || 0); |
| | | (record.otherDeduction || 0) + |
| | | (record.leaveDeduction || 0); |
| | | return income - deduction; |
| | | } |
| | | |
| | |
| | | @change="(val: number) => updateFieldValue(record, 'otherDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'leaveDeduction'"> |
| | | <InputNumber |
| | | v-model:value="record.leaveDeduction" |
| | | :min="0" |
| | | :precision="2" |
| | | size="small" |
| | | style="width: 90px" |
| | | @change="(val: number) => updateFieldValue(record, 'leaveDeduction', val)" |
| | | /> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'actualSalary'"> |
| | | <span class="font-bold text-green-600">{{ record.actualSalary?.toFixed(2) }}</span> |
| | | </template> |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag :color="row.status === 0 ? 'warning' : row.status === 10 ? 'success' : 'processing'"> |
| | | {{ getDictLabel(DICT_TYPE.HRM_SALARY_STATUS, row.status) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config'; |
| | | |
| | | /** 表åç±»å */ |
| | | export type FormType = 'update'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹è¯·åæ£æ¬¾é
ç½®ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'name', |
| | | label: '请åç±»å', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: '', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'deductionRatio', |
| | | label: 'æ£æ¬¾æ¯ä¾', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æ£æ¬¾æ¯ä¾', |
| | | min: 0, |
| | | max: 100, |
| | | step: 10, |
| | | precision: 0, |
| | | formatter: (value: number) => `${value}%`, |
| | | parser: (value: string) => parseFloat(value.replace('%', '')), |
| | | style: { width: '100%' }, |
| | | }, |
| | | rules: 'required', |
| | | help: 'æ£æ¬¾æ¯ä¾ï¼å¦50%表示æä¸åæ£æ¬¾', |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: 'ç¶æ', |
| | | component: 'RadioGroup', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'å¯ç¨', value: 0 }, |
| | | { label: 'ç¦ç¨', value: 1 }, |
| | | ], |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 2, |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<HrmLeaveTypeConfigApi.LeaveTypeConfig>['columns'] { |
| | | return [ |
| | | { field: 'name', title: '请åç±»å', minWidth: 120 }, |
| | | { |
| | | field: 'deductionRatio', |
| | | title: 'æ£æ¬¾æ¯ä¾', |
| | | width: 120, |
| | | slots: { default: 'deductionRatio' }, |
| | | }, |
| | | { field: 'remark', title: '夿³¨', minWidth: 200 }, |
| | | { |
| | | field: 'status', |
| | | title: 'ç¶æ', |
| | | width: 80, |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config'; |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getLeaveTypeConfigList } from '#/api/hrm/leave-type-config'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** ç¼è¾é
ç½® */ |
| | | function handleEdit(row: HrmLeaveTypeConfigApi.LeaveTypeConfig) { |
| | | formModalApi.setData({ id: row.id, row }).open(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async () => await getLeaveTypeConfigList(), |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | pagerConfig: { |
| | | enabled: false, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | }, |
| | | } as VxeTableGridOptions<HrmLeaveTypeConfigApi.LeaveTypeConfig>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="è¯·åæ£æ¬¾é
ç½®å表"> |
| | | <template #deductionRatio="{ row }"> |
| | | <Tag :color="row.deductionRatio === 0 ? 'success' : row.deductionRatio === 1 ? 'error' : 'warning'"> |
| | | {{ row.deductionRatio === 0 ? '䏿£æ¬¾' : `${(row.deductionRatio * 100).toFixed(0)}%` }} |
| | | </Tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag :color="row.status === 0 ? 'success' : 'default'"> |
| | | {{ row.status === 0 ? 'å¯ç¨' : 'ç¦ç¨' }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['hrm:leave-type-config:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmLeaveTypeConfigApi } from '#/api/hrm/leave-type-config'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { updateLeaveTypeConfig } from '#/api/hrm/leave-type-config'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-1', |
| | | labelWidth: 100, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = await formApi.getValues(); |
| | | // è½¬æ¢æ¯ä¾ä¸ºå°æ° |
| | | data.deductionRatio = data.deductionRatio / 100; |
| | | try { |
| | | await updateLeaveTypeConfig(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<{ id: number; row: HrmLeaveTypeConfigApi.LeaveTypeConfig }>(); |
| | | // è½¬æ¢æ¯ä¾ä¸ºç¾åæ¯æ¾ç¤º |
| | | const row = { ...data.row, deductionRatio: (data.row.deductionRatio || 0) * 100 }; |
| | | await formApi.setValues(row); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="ç¼è¾æ£æ¬¾æ¯ä¾" class="w-[600px]"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { HrmSalaryPaymentApi } from '#/api/hrm/salary/payment'; |
| | | |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | import { getDictOptions } from '#/packages/effects/hooks/src'; |
| | | |
| | | import { getDeptList } from '#/api/system/dept'; |
| | | import { getEmployeeSimpleList } from '#/api/hrm/employee'; |
| | | import { handleTree } from '#/packages/utils/src'; |
| | | import { DICT_TYPE } from '#/packages/constants/src'; |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'userId', |
| | | label: 'åå·¥', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: getEmployeeSimpleList, |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | 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: [ |
| | | { label: 'å¾
åæ¾', value: 10 }, |
| | | { label: '已忾', value: 20 }, |
| | | { label: 'å·²æ¤é', value: 30 }, |
| | | { label: 'å¾
åæ¾', value: 0 }, |
| | | { label: '已忾', value: 10 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©åæ¾ç¶æ', |
| | | }, |
| | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<HrmSalaryPaymentApi.SalaryPayment>['columns'] { |
| | | return [ |
| | | { type: 'checkbox', width: 50 }, |
| | | { field: 'no', title: 'åæ¾åå·', minWidth: 140 }, |
| | | { field: 'period', title: '忾卿', width: 100 }, |
| | | { field: 'userName', title: 'åå·¥å§å', minWidth: 100 }, |
| | | { field: 'deptName', title: 'é¨é¨', minWidth: 120 }, |
| | | { field: 'actualSalary', title: 'å®åå·¥èµ', width: 100, formatter: 'formatAmount2' }, |
| | | { field: 'totalAmount', title: 'åæ¾æ»éé¢', width: 120, formatter: 'formatAmount2' }, |
| | | { field: 'totalCount', title: 'åæ¾äººæ°', width: 100 }, |
| | | { |
| | | field: 'paymentMethod', |
| | | title: 'åæ¾æ¹å¼', |
| | |
| | | }, |
| | | }, |
| | | { field: 'paymentTime', title: 'åæ¾æ¶é´', width: 160 }, |
| | | { field: 'paymentNo', title: 'åæ¾æµæ°´å·', minWidth: 140 }, |
| | | { |
| | | field: 'status', |
| | | title: 'åæ¾ç¶æ', |
| | | title: 'ç¶æ', |
| | | width: 100, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.HRM_SALARY_STATUS }, |
| | | }, |
| | | fixed: 'right', |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 150, |
| | | width: 180, |
| | | fixed: 'right', |
| | | slots: { |
| | | default: 'actions', |
| | | }, |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { HrmSalaryPaymentApi } from '#/api/hrm/salary/payment'; |
| | | |
| | | import { Page } from '#/packages/effects/common-ui/src'; |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart } from '#/packages/utils/src'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag, DatePicker } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | exportSalaryPayment, |
| | | generateSalaryPayment, |
| | | getSalaryPaymentPage, |
| | | paySalary, |
| | | revokeSalaryPayment, |
| | | } from '#/api/hrm/salary/payment'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import DetailModal from './modules/detail.vue'; |
| | | |
| | | const [DetailModalComp, detailModalApi] = useVbenModal({ |
| | | connectedComponent: DetailModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | // çæå°è´¦å¼¹çª |
| | | const generateVisible = ref(false); |
| | | const generatePeriod = ref<string>(); |
| | | const generateLoading = ref(false); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** æ§è¡èªèµåæ¾ */ |
| | | async function handlePay() { |
| | | const records = gridApi.grid.getCheckboxRecords() as HrmSalaryPaymentApi.SalaryPayment[]; |
| | | if (records.length === 0) { |
| | | message.warning('è¯·éæ©è¦åæ¾çè®°å½'); |
| | | return; |
| | | } |
| | | const unpaidRecords = records.filter((r) => r.status === 10); |
| | | if (unpaidRecords.length === 0) { |
| | | message.warning('æéè®°å½ä¸æ²¡æå¾
åæ¾ç¶æçè®°å½'); |
| | | return; |
| | | } |
| | | const ids = unpaidRecords.map((r) => r.id!); |
| | | await paySalary(ids); |
| | | message.success('åæ¾æå'); |
| | | handleRefresh(); |
| | | /** æå¼çæå°è´¦å¼¹çª */ |
| | | function openGenerateModal() { |
| | | generatePeriod.value = undefined; |
| | | generateVisible.value = true; |
| | | } |
| | | |
| | | /** æ¤éèªèµåæ¾ */ |
| | | async function handleRevoke(row: HrmSalaryPaymentApi.SalaryPayment) { |
| | | await revokeSalaryPayment(row.id!); |
| | | message.success('æ¤éæå'); |
| | | /** çæåæ¾å°è´¦ */ |
| | | async function handleGenerate() { |
| | | if (!generatePeriod.value) { |
| | | message.warning('è¯·éæ©åæ¾å¨æ'); |
| | | return; |
| | | } |
| | | generateLoading.value = true; |
| | | try { |
| | | await generateSalaryPayment(generatePeriod.value); |
| | | message.success('çæåæ¾å°è´¦æå'); |
| | | generateVisible.value = false; |
| | | handleRefresh(); |
| | | } finally { |
| | | generateLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | /** æ¥çæç» */ |
| | | function handleViewDetail(row: HrmSalaryPaymentApi.SalaryPayment) { |
| | | detailModalApi.setData({ paymentId: row.id, period: row.period, status: row.status }).open(); |
| | | } |
| | | |
| | | /** åæ¾èªèµ */ |
| | | async function handlePay(row: HrmSalaryPaymentApi.SalaryPayment) { |
| | | await paySalary(row.id!); |
| | | message.success('åæ¾æå'); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 导åºèªèµåæ¾ */ |
| | | async function handleExport() { |
| | | const data = await exportSalaryPayment(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'èªèµåæ¾è¡¨.xls', source: data }); |
| | | downloadFileFromBlobPart({ fileName: 'èªèµåæ¾å°è´¦.xls', source: data }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | checkboxConfig: { |
| | | highlight: true, |
| | | reserve: true, |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <Grid table-title="èªèµåæ¾å表"> |
| | | <DetailModalComp @success="handleRefresh" /> |
| | | |
| | | <!-- çæå°è´¦å¼¹çª --> |
| | | <Modal |
| | | v-model:open="generateVisible" |
| | | title="çæåæ¾å°è´¦" |
| | | width="400px" |
| | | :maskClosable="false" |
| | | > |
| | | <Form layout="vertical"> |
| | | <FormItem label="忾卿" required> |
| | | <DatePicker |
| | | v-model:value="generatePeriod" |
| | | picker="month" |
| | | format="YYYY-MM" |
| | | valueFormat="YYYY-MM" |
| | | placeholder="è¯·éæ©åæ¾å¨æ" |
| | | style="width: 100%" |
| | | /> |
| | | </FormItem> |
| | | </Form> |
| | | <template #footer> |
| | | <a-button @click="generateVisible = false">åæ¶</a-button> |
| | | <a-button type="primary" :loading="generateLoading" @click="handleGenerate"> |
| | | ç¡®å® |
| | | </a-button> |
| | | </template> |
| | | </Modal> |
| | | |
| | | <Grid table-title="èªèµåæ¾å°è´¦"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'åæ¾', |
| | | label: 'çæå°è´¦', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['hrm:salary-payment:pay'], |
| | | onClick: handlePay, |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['hrm:salary-payment:generate'], |
| | | onClick: openGenerateModal, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag :color="row.status === 0 ? 'warning' : 'success'"> |
| | | {{ row.status === 0 ? 'å¾
忾' : '已忾' }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'æ¤é', |
| | | label: 'æ¥çæç»', |
| | | type: 'link', |
| | | auth: ['hrm:salary-payment:revoke'], |
| | | ifShow: row.status === 20, |
| | | onClick: handleViewDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'åæ¾', |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['hrm:salary-payment:pay'], |
| | | ifShow: row.status === 0, |
| | | popConfirm: { |
| | | title: '确认æ¤éè¯¥åæ¾è®°å½åï¼', |
| | | confirm: handleRevoke.bind(null, row), |
| | | title: 'ç¡®è®¤åæ¾è¯¥å¨æçèªèµåï¼', |
| | | confirm: handlePay.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { HrmSalaryPaymentApi } from '#/api/hrm/salary/payment'; |
| | | |
| | | import { ref, computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '#/packages/effects/common-ui/src'; |
| | | |
| | | import { message, Table, Tag, InputNumber, Alert } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction } from '#/adapter/vxe-table'; |
| | | import { getSalaryPaymentDetailList, paySalaryDetail } from '#/api/hrm/salary/payment'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const paymentId = ref<number>(); |
| | | const period = ref<string>(); |
| | | const status = ref<number>(); |
| | | const detailList = ref<HrmSalaryPaymentApi.SalaryPaymentDetail[]>([]); |
| | | const loading = ref(false); |
| | | const selectedIds = ref<number[]>([]); |
| | | const payLoading = ref(false); |
| | | |
| | | const isPaid = computed(() => status.value === 10); |
| | | |
| | | const columns = [ |
| | | { title: 'åå·¥å§å', dataIndex: 'userName', width: 100, fixed: 'left' }, |
| | | { title: 'é¨é¨', dataIndex: 'deptName', width: 120 }, |
| | | { title: 'åºæ¬å·¥èµ', dataIndex: 'baseSalary', width: 100 }, |
| | | { title: '绩æå·¥èµ', dataIndex: 'performanceSalary', width: 100 }, |
| | | { title: 'ç¤¾ä¿æ£æ¬¾', dataIndex: 'socialSecurityDeduction', width: 100 }, |
| | | { title: 'å
¬ç§¯éæ£æ¬¾', dataIndex: 'housingFundDeduction', width: 100 }, |
| | | { title: 'ä¸ªç¨æ£æ¬¾', dataIndex: 'taxDeduction', width: 100 }, |
| | | { title: 'è¯·åæ£æ¬¾', dataIndex: 'leaveDeduction', width: 100 }, |
| | | { title: 'åæ¾æ¶é´', dataIndex: 'paymentTime', width: 160 }, |
| | | { title: 'å®åå·¥èµ', dataIndex: 'actualSalary', width: 100, fixed: 'right' }, |
| | | { |
| | | title: 'ç¶æ', |
| | | dataIndex: 'status', |
| | | width: 80, |
| | | fixed: 'right', |
| | | key: 'status', |
| | | }, |
| | | ]; |
| | | |
| | | /** æ¹éåæ¾ */ |
| | | async function handleBatchPay() { |
| | | if (selectedIds.value.length === 0) { |
| | | message.warning('è¯·éæ©è¦åæ¾çæç»'); |
| | | return; |
| | | } |
| | | payLoading.value = true; |
| | | try { |
| | | await paySalaryDetail(selectedIds.value); |
| | | message.success('åæ¾æå'); |
| | | emit('success'); |
| | | // å·æ°æç»å表 |
| | | detailList.value = await getSalaryPaymentDetailList(paymentId.value!); |
| | | selectedIds.value = []; |
| | | } finally { |
| | | payLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | /** 忡忾 */ |
| | | async function handlePayDetail(row: HrmSalaryPaymentApi.SalaryPaymentDetail) { |
| | | await paySalaryDetail([row.id!]); |
| | | message.success('åæ¾æå'); |
| | | emit('success'); |
| | | // å·æ°æç»å表 |
| | | detailList.value = await getSalaryPaymentDetailList(paymentId.value!); |
| | | } |
| | | |
| | | /** éæ©è¡ */ |
| | | function onSelectChange(selectedRowKeys: number[]) { |
| | | selectedIds.value = selectedRowKeys; |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | detailList.value = []; |
| | | selectedIds.value = []; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ paymentId: number; period: string; status: number }>(); |
| | | paymentId.value = data.paymentId; |
| | | period.value = data.period; |
| | | status.value = data.status; |
| | | loading.value = true; |
| | | try { |
| | | detailList.value = await getSalaryPaymentDetailList(data.paymentId); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="`${period} - èªèµåæ¾æç»`" class="w-[1100px]" :footer="null"> |
| | | <Alert |
| | | v-if="!isPaid" |
| | | message="å¾ééè¦åæ¾çåå·¥æç»ï¼ç¹å»æ¹éåæ¾æé®è¿è¡åæ¾" |
| | | type="info" |
| | | show-icon |
| | | style="margin-bottom: 16px" |
| | | /> |
| | | |
| | | <div v-if="!isPaid" class="mb-3"> |
| | | <a-button type="primary" :loading="payLoading" :disabled="selectedIds.length === 0" @click="handleBatchPay"> |
| | | æ¹éåæ¾ (å·²é {{ selectedIds.length }} æ¡) |
| | | </a-button> |
| | | </div> |
| | | |
| | | <Table |
| | | :columns="columns" |
| | | :data-source="detailList" |
| | | :loading="loading" |
| | | :pagination="false" |
| | | :scroll="{ x: 1200, y: 400 }" |
| | | :row-selection="!isPaid ? { selectedRowKeys: selectedIds, onChange: onSelectChange } : undefined" |
| | | size="small" |
| | | row-key="id" |
| | | bordered |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.key === 'status'"> |
| | | <Tag :color="record.status === 0 ? 'warning' : 'success'"> |
| | | {{ record.status === 0 ? 'å¾
忾' : '已忾' }} |
| | | </Tag> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'actualSalary'"> |
| | | <span class="font-bold text-green-600">{{ record.actualSalary?.toFixed(2) }}</span> |
| | | </template> |
| | | <template v-else-if="column.dataIndex && ['baseSalary', 'performanceSalary', 'socialSecurityDeduction', 'housingFundDeduction', 'taxDeduction', 'leaveDeduction'].includes(column.dataIndex)"> |
| | | {{ (record[column.dataIndex] || 0).toFixed(2) }} |
| | | </template> |
| | | </template> |
| | | </Table> |
| | | </Modal> |
| | | </template> |