From 7c1ed5b09faa760292cdd47441ade8a16099d8b8 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 14 九月 2026 15:28:39 +0800
Subject: [PATCH] feat: 售后工单按条绑定部门与原因类型;新增加班工资管理;员工附件留档
---
src/api/hrm/employee/index.ts | 6
src/views/aftersales/ticket/data.ts | 95 +++++---
src/views/hrm/salary/overtime-wage/modules/form.vue | 90 ++++++++
src/views/hrm/salary/overtime-wage/index.vue | 125 +++++++++++
src/views/hrm/employee/data.ts | 42 +++
src/views/aftersales/ticket/modules/form.vue | 27 -
src/api/hrm/salary/overtime-wage/index.ts | 62 +++++
src/views/hrm/employee/index.vue | 5
src/views/hrm/employee/modules/form.vue | 13 +
src/api/aftersales/ticket.ts | 10
src/views/hrm/employee/modules/detail.vue | 14 +
src/packages/constants/src/dict-enum.ts | 2
src/views/hrm/salary/overtime-wage/data.ts | 146 +++++++++++++
13 files changed, 578 insertions(+), 59 deletions(-)
diff --git a/src/api/aftersales/ticket.ts b/src/api/aftersales/ticket.ts
index 36120d0..b79998b 100644
--- a/src/api/aftersales/ticket.ts
+++ b/src/api/aftersales/ticket.ts
@@ -7,6 +7,8 @@
export interface TicketItem {
id?: number;
ticketId?: number;
+ /** 鏄庣粏鍞悗鍗曞彿锛堝伐鍗曞彿-搴忓彿锛� */
+ itemNo?: string;
saleOrderItemId?: number;
itemId?: number;
itemCode?: string;
@@ -17,6 +19,14 @@
returnQuantity?: number;
needRepair?: boolean;
needReturn?: boolean;
+ /** 閮ㄩ棬ID */
+ deptId?: number;
+ /** 閮ㄩ棬鍚嶇О */
+ deptName?: string;
+ /** 鍘熷洜绫诲瀷 */
+ reasonType?: number;
+ /** 鍏蜂綋鍘熷洜 */
+ reason?: string;
remark?: string;
}
diff --git a/src/api/hrm/employee/index.ts b/src/api/hrm/employee/index.ts
index 4c0360a..0afd4a7 100644
--- a/src/api/hrm/employee/index.ts
+++ b/src/api/hrm/employee/index.ts
@@ -27,6 +27,8 @@
regularDate?: string;
leaveDate?: string;
employeeStatus?: number;
+ /** 鍛樺伐绫诲埆锛�1-鏅�氬憳宸ワ紝2-鎶�宸ワ紝3-绠$悊 */
+ employeeCategory?: number;
annualLeaveBalance?: number;
sickLeaveBalance?: number;
salaryStructureId?: number;
@@ -44,6 +46,10 @@
workHistories?: WorkHistory[];
/** 绱ф�ヨ仈绯讳汉鍒楄〃 */
emergencyContacts?: EmergencyContact[];
+ /** 闄勪欢 blobId 鍒楄〃锛堜繚瀛樻椂浼犲叆锛� */
+ blobIds?: number[];
+ /** 闄勪欢鍒楄〃锛堣鎯呮椂杩斿洖锛� */
+ attachmentList?: { id: number; name?: string; url?: string }[];
}
/** 鏁欒偛缁忓巻 */
diff --git a/src/api/hrm/salary/overtime-wage/index.ts b/src/api/hrm/salary/overtime-wage/index.ts
new file mode 100644
index 0000000..daf9884
--- /dev/null
+++ b/src/api/hrm/salary/overtime-wage/index.ts
@@ -0,0 +1,62 @@
+import { requestClient } from '#/api/request';
+
+export namespace HrmOvertimeWageApi {
+ /** 宀椾綅鍔犵彮宸ヨ祫閰嶇疆 */
+ export interface OvertimeWageConfig {
+ id?: number;
+ /** 宀椾綅ID */
+ postId?: number;
+ /** 宀椾綅鍚嶇О */
+ postName?: string;
+ /** 鍔犵彮鏃惰柂锛堝厓/灏忔椂锛夛紝榛樿 20 */
+ hourlyRate?: number;
+ /** 姣忔湀鏈�澶у姞鐝椂闀夸笂闄愶紙灏忔椂锛夛紝涓虹┖琛ㄧず涓嶉檺 */
+ maxOvertimeHours?: number;
+ /** 鐘舵�侊細0-鍚敤锛�1-绂佺敤 */
+ status?: number;
+ remark?: string;
+ createTime?: string;
+ }
+
+ /** 鏌ヨ鍙傛暟 */
+ export interface OvertimeWageConfigQueryParam {
+ postId?: number;
+ status?: number;
+ }
+}
+
+/** 鏌ヨ宀椾綅鍔犵彮宸ヨ祫閰嶇疆鍒楄〃 */
+export function getOvertimeWageConfigList(
+ params?: HrmOvertimeWageApi.OvertimeWageConfigQueryParam,
+) {
+ return requestClient.get<HrmOvertimeWageApi.OvertimeWageConfig[]>(
+ '/hrm/salary/overtime-wage/list',
+ { params },
+ );
+}
+
+/** 鏌ヨ宀椾綅鍔犵彮宸ヨ祫閰嶇疆璇︽儏 */
+export function getOvertimeWageConfig(id: number) {
+ return requestClient.get<HrmOvertimeWageApi.OvertimeWageConfig>(
+ `/hrm/salary/overtime-wage/get?id=${id}`,
+ );
+}
+
+/** 鏂板宀椾綅鍔犵彮宸ヨ祫閰嶇疆 */
+export function createOvertimeWageConfig(
+ data: HrmOvertimeWageApi.OvertimeWageConfig,
+) {
+ return requestClient.post('/hrm/salary/overtime-wage/create', data);
+}
+
+/** 鏇存柊宀椾綅鍔犵彮宸ヨ祫閰嶇疆 */
+export function updateOvertimeWageConfig(
+ data: HrmOvertimeWageApi.OvertimeWageConfig,
+) {
+ return requestClient.put('/hrm/salary/overtime-wage/update', data);
+}
+
+/** 鍒犻櫎宀椾綅鍔犵彮宸ヨ祫閰嶇疆 */
+export function deleteOvertimeWageConfig(id: number) {
+ return requestClient.delete(`/hrm/salary/overtime-wage/delete?id=${id}`);
+}
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index 2a62e5a..c13f8c6 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -136,6 +136,7 @@
AFTERSALE_ISSUE_TYPE: 'aftersale_issue_type', // 鍞悗闂绫诲瀷
AFTERSALE_RETURN_TYPE: 'aftersale_return_type', // 鍞悗閫�璐х被鍨�
AFTERSALE_SEVERITY_LEVEL: 'aftersale_severity_level', // 鍞悗涓ラ噸绋嬪害
+ AFTERSALE_REASON_TYPE: 'aftersale_reason_type', // 鍞悗鍘熷洜绫诲瀷
} as const;
/** ========== ERP - 浼佷笟璧勬簮璁″垝妯″潡 ========== */
@@ -317,6 +318,7 @@
const HRM_DICT = {
HRM_DEGREE: 'hrm_degree', // HRM 瀛﹀巻
HRM_EMPLOYEE_STATUS: 'hrm_employee_status', // HRM 鍛樺伐鐘舵��
+ HRM_EMPLOYEE_CATEGORY: 'hrm_employee_category', // HRM 鍛樺伐绫诲埆锛堟櫘閫�/鎶�宸�/绠$悊锛�
HRM_GENDER: 'hrm_gender', // HRM 鎬у埆
HRM_AUDIT_STATUS: 'hrm_audit_status', // HRM 瀹℃壒鐘舵��
HRM_LEAVE_TYPE: 'hrm_leave_type', // HRM 璇峰亣绫诲瀷
diff --git a/src/views/aftersales/ticket/data.ts b/src/views/aftersales/ticket/data.ts
index 15c83cc..0a27a78 100644
--- a/src/views/aftersales/ticket/data.ts
+++ b/src/views/aftersales/ticket/data.ts
@@ -12,10 +12,11 @@
import { z } from '#/adapter/form';
import CrmCustomerSelect from '#/components/crm-customer-select.vue';
-import { ErpSaleOrderSelect } from '#/views/erp/sale/order/components';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { WmBatchSelect } from '#/views/wls/batch/components';
import { UserSelect } from '#/views/system/user/components';
+import { getDeptList } from '#/api/system/dept';
+import { handleTree } from '#/packages/utils/src';
/** 琛ㄥ崟绫诲瀷 */
export type TicketFormType = 'create' | 'detail' | 'update';
@@ -40,11 +41,7 @@
}
/** 宸ュ崟琛ㄥ崟 */
-export function useTicketFormSchema(
- formType: TicketFormType,
- formApi?: VbenFormApi,
- onSaleOrderChange?: (order: Record<string, unknown> | undefined) => void,
-): VbenFormSchema[] {
+export function useTicketFormSchema(formType: TicketFormType): VbenFormSchema[] {
const readonly = isHeaderReadonly(formType);
return [
{
@@ -71,32 +68,6 @@
placeholder: '璇疯緭鍏ュ伐鍗曚富棰�',
},
rules: 'required',
- },
- {
- fieldName: 'saleOrderId',
- label: '閿�鍞鍗曞彿',
- component: markRaw(ErpSaleOrderSelect),
- componentProps: {
- placeholder: '璇烽�夋嫨閿�鍞鍗�',
- outStatus: 3,
- onChange: (item: Record<string, unknown> | undefined) => {
- if (formApi) {
- formApi.setFieldValue('saleOrderNo', item?.no ?? '');
- if (item?.customerId) {
- formApi.setFieldValue('customerId', item.customerId);
- }
- }
- onSaleOrderChange?.(item);
- },
- },
- },
- {
- fieldName: 'saleOrderNo',
- component: 'Input',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
},
{
fieldName: 'customerId',
@@ -269,11 +240,6 @@
minWidth: 150,
},
{
- field: 'saleOrderNo',
- title: '閿�鍞鍗曞彿',
- minWidth: 150,
- },
- {
field: 'issueType',
title: '闂绫诲瀷',
width: 100,
@@ -322,6 +288,11 @@
): VxeTableGridOptions<AftersalesTicketApi.TicketItem>['columns'] {
return [
{
+ field: 'itemNo',
+ title: '鏄庣粏鍗曞彿',
+ minWidth: 170,
+ },
+ {
field: 'itemCode',
title: '鐗╂枡缂栫爜',
minWidth: 120,
@@ -357,6 +328,22 @@
title: '闇�瑕侀��璐�',
width: 90,
slots: { default: 'needReturn' },
+ },
+ {
+ field: 'deptName',
+ title: '閮ㄩ棬',
+ minWidth: 120,
+ },
+ {
+ field: 'reasonType',
+ title: '鍘熷洜绫诲瀷',
+ width: 120,
+ slots: { default: 'reasonType' },
+ },
+ {
+ field: 'reason',
+ title: '鍏蜂綋鍘熷洜',
+ minWidth: 160,
},
...(editable
? [
@@ -488,6 +475,40 @@
rules: z.boolean().default(false),
},
{
+ fieldName: 'deptId',
+ label: '閮ㄩ棬',
+ component: 'ApiTreeSelect',
+ componentProps: {
+ api: async () => handleTree(await getDeptList()),
+ labelField: 'name',
+ valueField: 'id',
+ childrenField: 'children',
+ placeholder: '璇烽�夋嫨閮ㄩ棬',
+ treeDefaultExpandAll: true,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'reasonType',
+ label: '鍘熷洜绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.AFTERSALE_REASON_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鍘熷洜绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'reason',
+ label: '鍏蜂綋鍘熷洜',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叿浣撳師鍥�',
+ rows: 2,
+ },
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
diff --git a/src/views/aftersales/ticket/modules/form.vue b/src/views/aftersales/ticket/modules/form.vue
index bdb3fab..47d677b 100644
--- a/src/views/aftersales/ticket/modules/form.vue
+++ b/src/views/aftersales/ticket/modules/form.vue
@@ -2,14 +2,13 @@
import type { TicketFormType } from '../data';
import type { AftersalesTicketApi } from '#/api/aftersales/ticket';
-import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { computed, nextTick, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useVbenModal } from '@vben/common-ui';
-import { AftersaleIssueTypeEnum, AftersaleTicketStatusEnum } from '@vben/constants';
+import { AftersaleIssueTypeEnum, AftersaleTicketStatusEnum, DICT_TYPE } from '@vben/constants';
import { Button, Divider, Input, message, Popconfirm, Select, Tag } from 'ant-design-vue';
@@ -24,7 +23,7 @@
resolveTicket,
updateTicket,
} from '#/api/aftersales/ticket';
-import { getSaleOrder } from '#/api/erp/sale/order';
+import { getDictLabel } from '#/packages/effects/hooks/src';
import { $t } from '#/locales';
import { useTicketFormSchema, useTicketItemFormSchema, useTicketItemGridColumns } from '../data';
@@ -75,17 +74,6 @@
return $t('ui.actionTitle.create', ['鍞悗宸ュ崟']);
});
-// 閿�鍞鍗曠墿鏂欒繃婊�
-const orderItems = ref<ErpSaleOrderApi.SaleOrderItem[]>([]);
-
-function handleSaleOrderChange(item: Record<string, unknown> | undefined) {
- if (!item?.id) {
- orderItems.value = [];
- return;
- }
- orderItems.value = (item.items as ErpSaleOrderApi.SaleOrderItem[]) || [];
-}
-
// 涓昏〃鍗�
const [Form, formApi] = useVbenForm({
commonConfig: { componentProps: { class: 'w-full' }, formItemClass: 'col-span-1', labelWidth: 110 },
@@ -131,11 +119,11 @@
// Modal锛堝伐鍗曡〃鍗曞脊妗嗭級蹇呴』鍦� ItemModal 涔嬪墠锛岀‘淇� connectedComponent 娉ㄥ叆鍒版纭殑寮规
const [Modal, modalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) {
- if (!isOpen) { formData.value = undefined; items.value = []; orderItems.value = []; showClassify.value = false; return; }
+ if (!isOpen) { formData.value = undefined; items.value = []; showClassify.value = false; return; }
const data = modalApi.getData<{ formType: TicketFormType; id?: number }>();
formType.value = data.formType;
itemsGridApi.setGridOptions({ columns: useTicketItemGridColumns(isEdit.value) });
- formApi.setState({ schema: useTicketFormSchema(formType.value, formApi, handleSaleOrderChange) });
+ formApi.setState({ schema: useTicketFormSchema(formType.value) });
formApi.setDisabled(!isEdit.value);
modalApi.setState({ showConfirmButton: false, showCancelButton: false });
if (data?.id) {
@@ -152,10 +140,6 @@
status: 'done', id: item.id,
}));
await formApi.setFieldValue('blobIds', blobIds);
- }
- if (formData.value?.saleOrderId) {
- const order = await getSaleOrder(formData.value.saleOrderId);
- orderItems.value = (order as Record<string, unknown>).items as ErpSaleOrderApi.SaleOrderItem[] || [];
}
} finally { modalApi.unlock(); }
} else {
@@ -183,7 +167,7 @@
onOpenChange(isOpen: boolean) {
if (!isOpen) { editingItemIndex.value = null; return; }
const d = itemModalApi.getData<AftersalesTicketApi.TicketItem | null>();
- itemFormApi.setState({ schema: useTicketItemFormSchema(formData.value?.customerId, formData.value?.saleOrderNo, orderItems.value, itemFormApi) });
+ itemFormApi.setState({ schema: useTicketItemFormSchema(formData.value?.customerId, undefined, [], itemFormApi) });
if (d) { itemFormApi.setValues(d as Record<string, unknown>); } else { itemFormApi.resetForm(); }
},
});
@@ -267,6 +251,7 @@
<ItemsGrid>
<template #needRepair="{ row }"><Tag :color="row.needRepair ? 'blue' : 'default'">{{ row.needRepair ? '鏄�' : '鍚�' }}</Tag></template>
<template #needReturn="{ row }"><Tag :color="row.needReturn ? 'orange' : 'default'">{{ row.needReturn ? '鏄�' : '鍚�' }}</Tag></template>
+ <template #reasonType="{ row }">{{ getDictLabel(DICT_TYPE.AFTERSALE_REASON_TYPE, row.reasonType) }}</template>
<template v-if="isEdit" #itemActions="{ row, rowIndex }">
<TableAction :actions="[
{ label: $t('common.edit'), type: 'link', icon: ACTION_ICON.EDIT, onClick: () => editItem(rowIndex) },
diff --git a/src/views/hrm/employee/data.ts b/src/views/hrm/employee/data.ts
index 7474ca7..1307eea 100644
--- a/src/views/hrm/employee/data.ts
+++ b/src/views/hrm/employee/data.ts
@@ -153,6 +153,18 @@
rules: z.number().default(1),
},
{
+ fieldName: 'employeeCategory',
+ label: '鍛樺伐绫诲埆',
+ component: 'RadioGroup',
+ componentProps: {
+ buttonStyle: 'solid',
+ optionType: 'button',
+ options: getDictOptions(DICT_TYPE.HRM_EMPLOYEE_CATEGORY, 'number'),
+ },
+ rules: z.number().default(1),
+ help: '鏅�氬憳宸ユ寜宀椾綅鍔犵彮鏃惰柂锛涙妧宸ュ姞鐝寜 1:1锛涚鐞嗕笉璁″姞鐝伐璧�',
+ },
+ {
fieldName: 'birthday',
label: '鍑虹敓鏃ユ湡',
component: 'DatePicker',
@@ -292,6 +304,18 @@
placeholder: '璇疯緭鍏ュ眳浣忓湴鍧�',
},
},
+ {
+ fieldName: 'blobIds',
+ label: '闄勪欢',
+ component: 'FileUpload',
+ componentProps: {
+ valueKey: 'id',
+ maxNumber: 10,
+ multiple: true,
+ help: '鍙笂浼犺韩浠借瘉銆佸鍘嗚瘉涔︺�佷綋妫�鎶ュ憡绛夎祫鏂欎綔涓虹暀妗�',
+ },
+ formItemClass: 'col-span-2',
+ },
];
}
@@ -343,6 +367,16 @@
placeholder: '璇烽�夋嫨鍛樺伐鐘舵��',
},
},
+ {
+ fieldName: 'employeeCategory',
+ label: '鍛樺伐绫诲埆',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.HRM_EMPLOYEE_CATEGORY, 'number'),
+ placeholder: '璇烽�夋嫨鍛樺伐绫诲埆',
+ },
+ },
];
}
@@ -380,6 +414,14 @@
default: 'employeeStatus',
},
},
+ {
+ field: 'employeeCategory',
+ title: '鍛樺伐绫诲埆',
+ width: 100,
+ slots: {
+ default: 'employeeCategory',
+ },
+ },
{ field: 'hireDate', title: '鍏ヨ亴鏃ユ湡', width: 110 },
{
field: 'createTime',
diff --git a/src/views/hrm/employee/index.vue b/src/views/hrm/employee/index.vue
index 9f5cb70..b1fba92 100644
--- a/src/views/hrm/employee/index.vue
+++ b/src/views/hrm/employee/index.vue
@@ -191,6 +191,11 @@
{{ getDictLabel(DICT_TYPE.HRM_EMPLOYEE_STATUS, row.employeeStatus) }}
</Tag>
</template>
+ <template #employeeCategory="{ row }">
+ <Tag :color="row.employeeCategory === 2 ? 'orange' : row.employeeCategory === 3 ? 'purple' : 'blue'">
+ {{ getDictLabel(DICT_TYPE.HRM_EMPLOYEE_CATEGORY, row.employeeCategory) }}
+ </Tag>
+ </template>
<template #actions="{ row }">
<TableAction
:actions="[
diff --git a/src/views/hrm/employee/modules/detail.vue b/src/views/hrm/employee/modules/detail.vue
index ccd7122..9cd2df2 100644
--- a/src/views/hrm/employee/modules/detail.vue
+++ b/src/views/hrm/employee/modules/detail.vue
@@ -78,6 +78,20 @@
<DescriptionsItem label="灞呬綇鍦板潃" :span="2">{{ employee.address || '-' }}</DescriptionsItem>
</Descriptions>
+ <!-- 闄勪欢鐣欐。 -->
+ <Descriptions title="闄勪欢鐣欐。" bordered :column="1" class="mb-4">
+ <DescriptionsItem label="闄勪欢">
+ <template v-if="employee.attachmentList && employee.attachmentList.length">
+ <div v-for="file in employee.attachmentList" :key="file.id" class="mb-1">
+ <a :href="file.url" target="_blank" rel="noopener">
+ {{ file.name || `闄勪欢${file.id}` }}
+ </a>
+ </div>
+ </template>
+ <span v-else>-</span>
+ </DescriptionsItem>
+ </Descriptions>
+
<!-- 澶囨敞 -->
<Descriptions title="鍏朵粬淇℃伅" bordered :column="1" class="mb-4">
<DescriptionsItem label="澶囨敞">{{ employee.remark || '-' }}</DescriptionsItem>
diff --git a/src/views/hrm/employee/modules/form.vue b/src/views/hrm/employee/modules/form.vue
index e24e6ce..dbae5ef 100644
--- a/src/views/hrm/employee/modules/form.vue
+++ b/src/views/hrm/employee/modules/form.vue
@@ -66,6 +66,10 @@
// 鎻愪氦琛ㄥ崟
const data = (await formApi.getValues()) as HrmEmployeeApi.Employee;
data.remark = remark.value;
+ // 闄勪欢锛氭妸 FileUpload 鐨勫�艰浆鎴� blobId 鏁扮粍
+ if (Array.isArray(data.blobIds)) {
+ data.blobIds = (data.blobIds as any[]).map((item) => item?.id ?? item);
+ }
// 鑾峰彇瀛愯〃鏁版嵁
data.educations = educationListRef.value?.getData();
data.workHistories = workHistoryListRef.value?.getData();
@@ -111,7 +115,14 @@
employeeData.value = formData;
remark.value = formData.remark;
if (data.formType !== 'detail') {
- await formApi.setValues(formData);
+ const blobIds = (formData.attachmentList || []).map((item) => ({
+ uid: String(item.id),
+ name: item.name || '',
+ url: item.url || '',
+ status: 'done',
+ id: item.id,
+ }));
+ await formApi.setValues({ ...formData, blobIds });
}
} finally {
modalApi.unlock();
diff --git a/src/views/hrm/salary/overtime-wage/data.ts b/src/views/hrm/salary/overtime-wage/data.ts
new file mode 100644
index 0000000..e4c9bb4
--- /dev/null
+++ b/src/views/hrm/salary/overtime-wage/data.ts
@@ -0,0 +1,146 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeWageApi } from '#/api/hrm/salary/overtime-wage';
+
+import { getSimplePostList } from '#/api/system/post';
+
+export type FormType = 'create' | 'update';
+
+const statusOptions = [
+ { label: '鍚敤', value: 0 },
+ { label: '绂佺敤', value: 1 },
+];
+
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'postId',
+ label: '宀椾綅',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getSimplePostList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨宀椾綅锛堝伐绉嶏級',
+ },
+ rules: 'selectRequired',
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'hourlyRate',
+ label: '鍔犵彮鏃惰柂',
+ component: 'InputNumber',
+ componentProps: {
+ addonAfter: '鍏�/灏忔椂',
+ min: 0,
+ precision: 2,
+ step: 1,
+ placeholder: '榛樿 20锛屾寜宀椾綅鍙笉鍚�',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'maxOvertimeHours',
+ label: '鏈�澶у姞鐝椂闀�',
+ component: 'InputNumber',
+ componentProps: {
+ addonAfter: '灏忔椂/鏈�',
+ min: 0,
+ precision: 1,
+ step: 1,
+ placeholder: '鐣欑┖琛ㄧず涓嶉檺',
+ style: { width: '100%' },
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: { options: statusOptions },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ maxlength: 500,
+ placeholder: '鍙~鍐欓�傜敤璇存槑锛屽銆屼粎鏅�氬憳宸ャ�嶃�屾妧宸ユ寜 1:1銆嶇瓑',
+ rows: 3,
+ showCount: true,
+ },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'postId',
+ label: '宀椾綅',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getSimplePostList,
+ labelField: 'name',
+ valueField: 'id',
+ allowClear: true,
+ placeholder: '璇烽�夋嫨宀椾綅',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: statusOptions,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ];
+}
+
+export function useGridColumns(): VxeTableGridOptions<HrmOvertimeWageApi.OvertimeWageConfig>['columns'] {
+ return [
+ { field: 'postName', title: '宀椾綅锛堝伐绉嶏級', minWidth: 160 },
+ {
+ field: 'hourlyRate',
+ title: '鍔犵彮鏃惰柂',
+ width: 130,
+ slots: { default: 'hourlyRate' },
+ },
+ {
+ field: 'maxOvertimeHours',
+ title: '鏈�澶у姞鐝椂闀�',
+ width: 140,
+ slots: { default: 'maxOvertimeHours' },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ slots: { default: 'status' },
+ },
+ { field: 'remark', title: '澶囨敞', minWidth: 180 },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'actions',
+ title: '鎿嶄綔',
+ fixed: 'right',
+ slots: { default: 'actions' },
+ width: 150,
+ },
+ ];
+}
diff --git a/src/views/hrm/salary/overtime-wage/index.vue b/src/views/hrm/salary/overtime-wage/index.vue
new file mode 100644
index 0000000..043c6e2
--- /dev/null
+++ b/src/views/hrm/salary/overtime-wage/index.vue
@@ -0,0 +1,125 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeWageApi } from '#/api/hrm/salary/overtime-wage';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { Alert, message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteOvertimeWageConfig,
+ getOvertimeWageConfigList,
+} from '#/api/hrm/salary/overtime-wage';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'HrmOvertimeWage' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleEdit(row: HrmOvertimeWageApi.OvertimeWageConfig) {
+ formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+async function handleDelete(row: HrmOvertimeWageApi.OvertimeWageConfig) {
+ await deleteOvertimeWageConfig(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', ['鍔犵彮宸ヨ祫閰嶇疆']));
+ handleRefresh();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: { schema: useGridFormSchema() },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ pagerConfig: { enabled: false },
+ proxyConfig: {
+ ajax: {
+ query: async (_params, formValues) =>
+ await getOvertimeWageConfigList(formValues),
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<HrmOvertimeWageApi.OvertimeWageConfig>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Alert
+ class="mb-4"
+ description="鎸夈�屽矖浣嶏紙宸ョ锛夈�嶉厤缃姞鐝椂钖細鏅�氬憳宸ユ寜姝ゅ鏃惰柂璁″姞鐝伐璧勶紝鍙缃瘡鏈堟渶澶у姞鐝椂闀夸笂闄愶紱鎶�宸ユ寜骞虫椂鏃惰柂 1:1锛岀鐞嗕笉璁″姞鐝伐璧勩�傚姞鐝伐璧勫嚟瀹℃壒閫氳繃鐨勫姞鐝崟鏍哥畻銆�"
+ message="鍔犵彮宸ヨ祫鎸夊矖浣嶅垝鍒�"
+ show-icon
+ type="info"
+ />
+ <Grid table-title="鍔犵彮宸ヨ祫绠$悊">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍔犵彮宸ヨ祫']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['hrm:overtime-wage:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #hourlyRate="{ row }">
+ <Tag color="blue">{{ row.hourlyRate ?? 0 }} 鍏�/灏忔椂</Tag>
+ </template>
+ <template #maxOvertimeHours="{ row }">
+ <span v-if="row.maxOvertimeHours === null || row.maxOvertimeHours === undefined">涓嶉檺</span>
+ <span v-else>{{ row.maxOvertimeHours }} 灏忔椂/鏈�</span>
+ </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:overtime-wage:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['hrm:overtime-wage:delete'],
+ popConfirm: {
+ title: '纭鍒犻櫎璇ュ矖浣嶇殑鍔犵彮宸ヨ祫閰嶇疆鍚楋紵',
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/hrm/salary/overtime-wage/modules/form.vue b/src/views/hrm/salary/overtime-wage/modules/form.vue
new file mode 100644
index 0000000..46db733
--- /dev/null
+++ b/src/views/hrm/salary/overtime-wage/modules/form.vue
@@ -0,0 +1,90 @@
+<script lang="ts" setup>
+import type { HrmOvertimeWageApi } from '#/api/hrm/salary/overtime-wage';
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { Alert, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOvertimeWageConfig,
+ getOvertimeWageConfig,
+ updateOvertimeWageConfig,
+} from '#/api/hrm/salary/overtime-wage';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+
+const getTitle = computed(() =>
+ formType.value === 'update' ? '淇敼鍔犵彮宸ヨ祫' : '鏂板鍔犵彮宸ヨ祫',
+);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ 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();
+ try {
+ const data =
+ (await formApi.getValues()) as HrmOvertimeWageApi.OvertimeWageConfig;
+ if (data.id) {
+ await updateOvertimeWageConfig(data);
+ } else {
+ await createOvertimeWageConfig(data);
+ }
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ await formApi.resetForm();
+ if (!data.id) {
+ await formApi.setValues({ hourlyRate: 20, status: 0 });
+ return;
+ }
+ modalApi.lock();
+ try {
+ await formApi.setValues(await getOvertimeWageConfig(data.id));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-[640px]">
+ <Alert
+ class="mx-4 mb-4"
+ description="鏅�氬憳宸ワ細鍔犵彮宸ヨ祫 = 鏈矖浣嶉厤缃殑鏃惰柂 脳 鍔犵彮鏃堕暱锛堣秴杩囨渶澶ф椂闀夸笂闄愮殑閮ㄥ垎涓嶈锛夛紱鎶�宸ユ寜骞虫椂鏃惰柂 1:1锛涚鐞嗕笉璁″姞鐝伐璧勩��"
+ message="鍔犵彮宸ヨ祫璁$畻璇存槑"
+ show-icon
+ type="info"
+ />
+ <Form class="mx-4" />
+ </Modal>
+</template>
--
Gitblit v1.9.3