From b74d628be8ff57a22e4e4a5a164e755a509820ec Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 07 七月 2026 16:50:18 +0800
Subject: [PATCH] 银川 1.员工管理页面开发、联调 2.薪资发放页面开发联调 3.薪酬管理页面开发联调 4.添加请假扣薪管理页面开发联调
---
src/api/hrm/employee/index.ts | 15 +
src/views/hrm/salary/leave-type-config/data.ts | 101 +++++++
src/views/hrm/salary/calculation/data.ts | 10
src/views/hrm/salary/payment/data.ts | 61 ---
src/views/hrm/leave/data.ts | 5
src/views/hrm/salary/leave-type-config/modules/form.vue | 67 ++++
src/views/hrm/salary/payment/modules/detail.vue | 142 ++++++++++
src/views/hrm/employee/data.ts | 10
src/views/hrm/salary/payment/index.vue | 135 ++++++--
src/api/hrm/salary/calculation/index.ts | 11
src/views/hrm/employee/index.vue | 15 +
src/views/hrm/salary/leave-type-config/index.vue | 84 ++++++
src/api/hrm/salary/payment/index.ts | 78 ++++
src/views/hrm/leave/index.vue | 24 +
src/api/hrm/leave-type-config/index.ts | 28 ++
src/views/hrm/salary/calculation/index.vue | 23 +
16 files changed, 691 insertions(+), 118 deletions(-)
diff --git a/src/api/hrm/employee/index.ts b/src/api/hrm/employee/index.ts
index d5deea4..9aeea4d 100644
--- a/src/api/hrm/employee/index.ts
+++ b/src/api/hrm/employee/index.ts
@@ -124,4 +124,19 @@
/** 瀵煎嚭鍛樺伐 */
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}`);
}
\ No newline at end of file
diff --git a/src/api/hrm/leave-type-config/index.ts b/src/api/hrm/leave-type-config/index.ts
new file mode 100644
index 0000000..4c58a3f
--- /dev/null
+++ b/src/api/hrm/leave-type-config/index.ts
@@ -0,0 +1,28 @@
+import type { PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace HrmLeaveTypeConfigApi {
+ /** 璇峰亣绫诲瀷鎵f閰嶇疆 */
+ 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);
+}
\ No newline at end of file
diff --git a/src/api/hrm/salary/calculation/index.ts b/src/api/hrm/salary/calculation/index.ts
index 1e129ab..4aa6f4c 100644
--- a/src/api/hrm/salary/calculation/index.ts
+++ b/src/api/hrm/salary/calculation/index.ts
@@ -22,6 +22,7 @@
housingFundDeduction?: number;
taxDeduction?: number;
otherDeduction?: number;
+ leaveDeduction?: number;
actualSalary?: number;
workDays?: number;
overtimeHours?: number;
@@ -103,6 +104,16 @@
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 });
diff --git a/src/api/hrm/salary/payment/index.ts b/src/api/hrm/salary/payment/index.ts
index c98af96..624558e 100644
--- a/src/api/hrm/salary/payment/index.ts
+++ b/src/api/hrm/salary/payment/index.ts
@@ -3,20 +3,44 @@
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;
}
@@ -24,13 +48,19 @@
/** 钖祫鍙戞斁鏌ヨ鍙傛暟 */
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,
) {
@@ -40,21 +70,45 @@
);
}
-/** 鏌ヨ钖祫鍙戞斁璇︽儏 */
+/** 鏌ヨ钖祫鍙戞斁鍙拌处璇︽儏 */
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}`);
}
/** 瀵煎嚭钖祫鍙戞斁琛� */
diff --git a/src/views/hrm/employee/data.ts b/src/views/hrm/employee/data.ts
index 532286b..b9e0233 100644
--- a/src/views/hrm/employee/data.ts
+++ b/src/views/hrm/employee/data.ts
@@ -304,9 +304,8 @@
field: 'gender',
title: '鎬у埆',
width: 80,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.HRM_GENDER },
+ slots: {
+ default: 'gender',
},
},
{ field: 'phone', title: '鎵嬫満鍙风爜', minWidth: 120 },
@@ -316,9 +315,8 @@
field: 'employeeStatus',
title: '鍛樺伐鐘舵��',
width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.HRM_EMPLOYEE_STATUS },
+ slots: {
+ default: 'employeeStatus',
},
},
{ field: 'hireDate', title: '鍏ヨ亴鏃ユ湡', width: 110 },
diff --git a/src/views/hrm/employee/index.vue b/src/views/hrm/employee/index.vue
index 3cc7d6b..98a6237 100644
--- a/src/views/hrm/employee/index.vue
+++ b/src/views/hrm/employee/index.vue
@@ -5,7 +5,10 @@
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';
@@ -117,6 +120,16 @@
<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="[
diff --git a/src/views/hrm/leave/data.ts b/src/views/hrm/leave/data.ts
index 75786f4..79fc8a1 100644
--- a/src/views/hrm/leave/data.ts
+++ b/src/views/hrm/leave/data.ts
@@ -138,9 +138,8 @@
field: 'leaveType',
title: '璇峰亣绫诲瀷',
width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.HRM_LEAVE_TYPE },
+ slots: {
+ default: 'leaveType',
},
},
{ field: 'startTime', title: '寮�濮嬫椂闂�', width: 160 },
diff --git a/src/views/hrm/leave/index.vue b/src/views/hrm/leave/index.vue
index 029edf5..e7ae028 100644
--- a/src/views/hrm/leave/index.vue
+++ b/src/views/hrm/leave/index.vue
@@ -4,7 +4,10 @@
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 {
@@ -72,6 +75,20 @@
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(),
@@ -119,6 +136,11 @@
]"
/>
</template>
+ <template #leaveType="{ row }">
+ <Tag :color="getLeaveTypeColor(row.leaveType)">
+ {{ getDictLabel(DICT_TYPE.HRM_LEAVE_TYPE, row.leaveType) }}
+ </Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/hrm/salary/calculation/data.ts b/src/views/hrm/salary/calculation/data.ts
index 2aa50f5..17860e1 100644
--- a/src/views/hrm/salary/calculation/data.ts
+++ b/src/views/hrm/salary/calculation/data.ts
@@ -85,6 +85,7 @@
{ field: 'housingFundDeduction', title: '鍏Н閲戞墸娆�', width: 100, formatter: 'formatAmount2' },
{ field: 'taxDeduction', title: '涓◣鎵f', width: 100, formatter: 'formatAmount2' },
{ field: 'otherDeduction', title: '鍏朵粬鎵f', width: 80, formatter: 'formatAmount2' },
+ { field: 'leaveDeduction', title: '璇峰亣鎵f', width: 100, formatter: 'formatAmount2' },
{ field: 'actualSalary', title: '瀹炲彂宸ヨ祫', width: 100, formatter: 'formatAmount2' },
{ field: 'workDays', title: '鍑哄嫟澶╂暟', width: 80 },
{ field: 'overtimeHours', title: '鍔犵彮鏃堕暱(h)', width: 100 },
@@ -92,9 +93,9 @@
field: 'status',
title: '鐘舵��',
width: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.HRM_SALARY_STATUS },
+ fixed: 'right',
+ slots: {
+ default: 'status',
},
},
{
@@ -123,8 +124,9 @@
{ title: '鍏Н閲戞墸娆�', dataIndex: 'housingFundDeduction', width: 110 },
{ title: '涓◣鎵f', dataIndex: 'taxDeduction', width: 110 },
{ title: '鍏朵粬鎵f', dataIndex: 'otherDeduction', width: 100 },
- { title: '瀹炲彂宸ヨ祫', dataIndex: 'actualSalary', width: 100, fixed: 'right' },
+ { title: '璇峰亣鎵f', dataIndex: 'leaveDeduction', width: 110 },
{ title: '鍑哄嫟澶╂暟', dataIndex: 'workDays', width: 90 },
{ title: '鍔犵彮鏃堕暱', dataIndex: 'overtimeHours', width: 100 },
+ { title: '瀹炲彂宸ヨ祫', dataIndex: 'actualSalary', width: 100, fixed: 'right' },
];
}
diff --git a/src/views/hrm/salary/calculation/index.vue b/src/views/hrm/salary/calculation/index.vue
index 59551ab..8536a41 100644
--- a/src/views/hrm/salary/calculation/index.vue
+++ b/src/views/hrm/salary/calculation/index.vue
@@ -7,7 +7,10 @@
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 {
@@ -102,7 +105,8 @@
const deduction = (record.socialSecurityDeduction || 0) +
(record.housingFundDeduction || 0) +
(record.taxDeduction || 0) +
- (record.otherDeduction || 0);
+ (record.otherDeduction || 0) +
+ (record.leaveDeduction || 0);
return income - deduction;
}
@@ -365,6 +369,16 @@
@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>
@@ -425,6 +439,11 @@
]"
/>
</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="[
diff --git a/src/views/hrm/salary/leave-type-config/data.ts b/src/views/hrm/salary/leave-type-config/data.ts
new file mode 100644
index 0000000..f8e53f1
--- /dev/null
+++ b/src/views/hrm/salary/leave-type-config/data.ts
@@ -0,0 +1,101 @@
+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';
+
+/** 鏂板/淇敼璇峰亣鎵f閰嶇疆鐨勮〃鍗� */
+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: '鎵f姣斾緥',
+ 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: '鎵f姣斾緥锛屽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: '鎵f姣斾緥',
+ 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' },
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/hrm/salary/leave-type-config/index.vue b/src/views/hrm/salary/leave-type-config/index.vue
new file mode 100644
index 0000000..a22d4c7
--- /dev/null
+++ b/src/views/hrm/salary/leave-type-config/index.vue
@@ -0,0 +1,84 @@
+<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="璇峰亣鎵f閰嶇疆鍒楄〃">
+ <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>
\ No newline at end of file
diff --git a/src/views/hrm/salary/leave-type-config/modules/form.vue b/src/views/hrm/salary/leave-type-config/modules/form.vue
new file mode 100644
index 0000000..f016033
--- /dev/null
+++ b/src/views/hrm/salary/leave-type-config/modules/form.vue
@@ -0,0 +1,67 @@
+<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="缂栬緫鎵f姣斾緥" class="w-[600px]">
+ <Form class="mx-4" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/hrm/salary/payment/data.ts b/src/views/hrm/salary/payment/data.ts
index c93e181..b9e1873 100644
--- a/src/views/hrm/salary/payment/data.ts
+++ b/src/views/hrm/salary/payment/data.ts
@@ -2,12 +2,8 @@
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[] {
@@ -26,44 +22,14 @@
},
},
{
- 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: '璇烽�夋嫨鍙戞斁鐘舵��',
},
@@ -74,12 +40,10 @@
/** 鍒楄〃鐨勫瓧娈� */
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: '鍙戞斁鏂瑰紡',
@@ -90,15 +54,12 @@
},
},
{ 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',
@@ -108,11 +69,9 @@
},
{
title: '鎿嶄綔',
- width: 150,
+ width: 180,
fixed: 'right',
- slots: {
- default: 'actions',
- },
+ slots: { default: 'actions' },
},
];
}
\ No newline at end of file
diff --git a/src/views/hrm/salary/payment/index.vue b/src/views/hrm/salary/payment/index.vue
index 3933c41..4520c4b 100644
--- a/src/views/hrm/salary/payment/index.vue
+++ b/src/views/hrm/salary/payment/index.vue
@@ -2,56 +2,79 @@
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({
@@ -62,10 +85,6 @@
columns: useGridColumns(),
height: 'auto',
keepSource: true,
- checkboxConfig: {
- highlight: true,
- reserve: true,
- },
proxyConfig: {
ajax: {
query: async ({ page }, formValues) =>
@@ -90,16 +109,45 @@
<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'),
@@ -111,17 +159,28 @@
]"
/>
</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),
},
},
]"
diff --git a/src/views/hrm/salary/payment/modules/detail.vue b/src/views/hrm/salary/payment/modules/detail.vue
new file mode 100644
index 0000000..cb65405
--- /dev/null
+++ b/src/views/hrm/salary/payment/modules/detail.vue
@@ -0,0 +1,142 @@
+<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: '绀句繚鎵f', dataIndex: 'socialSecurityDeduction', width: 100 },
+ { title: '鍏Н閲戞墸娆�', dataIndex: 'housingFundDeduction', width: 100 },
+ { title: '涓◣鎵f', dataIndex: 'taxDeduction', width: 100 },
+ { title: '璇峰亣鎵f', 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>
\ No newline at end of file
--
Gitblit v1.9.3