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