From 6240f8d054b8dc53d81f352648d82533495cad0d Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期四, 20 八月 2026 10:30:19 +0800
Subject: [PATCH] 加班申请
---
src/views/hrm/overtime/index.vue | 200 +++++++++
src/api/hrm/employee/index.ts | 3
src/views/hrm/salary/structure/modules/form.vue | 95 ++++
src/views/hrm/overtime/detail/approve/index.vue | 83 +++
src/views/hrm/overtime/modules/form.vue | 159 +++++++
src/views/hrm/salary/structure/index.vue | 137 ++++++
src/api/hrm/overtime/index.ts | 89 ++++
src/api/hrm/salary/structure/index.ts | 50 ++
src/views/hrm/salary/structure/data.ts | 163 +++++++
src/views/hrm/overtime/data.ts | 320 ++++++++++++++
src/views/hrm/employee/data.ts | 14
11 files changed, 1,312 insertions(+), 1 deletions(-)
diff --git a/src/api/hrm/employee/index.ts b/src/api/hrm/employee/index.ts
index 0287f85..6a659ab 100644
--- a/src/api/hrm/employee/index.ts
+++ b/src/api/hrm/employee/index.ts
@@ -29,6 +29,7 @@
employeeStatus?: number;
annualLeaveBalance?: number;
sickLeaveBalance?: number;
+ salaryStructureId?: number;
baseSalary?: number;
roleIds?: number[];
roleNames?: string[];
@@ -167,4 +168,4 @@
/** 鍒犻櫎绱ф�ヨ仈绯讳汉 */
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/overtime/index.ts b/src/api/hrm/overtime/index.ts
new file mode 100644
index 0000000..37f5b15
--- /dev/null
+++ b/src/api/hrm/overtime/index.ts
@@ -0,0 +1,89 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace HrmOvertimeApi {
+ /** 鍔犵彮鐢宠 */
+ export interface Overtime {
+ id?: number;
+ employeeId?: number;
+ employeeName?: string;
+ employeeNo?: string;
+ deptName?: string;
+ postName?: string;
+ overtimeDate?: string;
+ startTime?: string;
+ endTime?: string;
+ duration?: number;
+ overtimeType?: number;
+ crossDay?: boolean;
+ mealDeduction?: boolean;
+ mealDeductionHours?: number;
+ reason?: string;
+ relatedType?: number;
+ relatedCode?: string;
+ workContent?: string;
+ status?: number;
+ processInstanceId?: string;
+ createTime?: string;
+ }
+
+ export interface ApproveProcess {
+ key: string;
+ name: string;
+ }
+}
+
+/** 鏌ヨ鍔犵彮鐢宠鍒嗛〉鍒楄〃 */
+export function getOvertimePage(params: PageParam) {
+ return requestClient.get<PageResult<HrmOvertimeApi.Overtime>>(
+ '/hrm/overtime/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鍔犵彮鐢宠璇︽儏 */
+export function getOvertime(id: number) {
+ return requestClient.get<HrmOvertimeApi.Overtime>(
+ `/hrm/overtime/get?id=${id}`,
+ );
+}
+
+/** 鑾峰彇褰撳墠鐢宠浜哄叧鑱旂殑鍛樺伐妗f */
+export function getCurrentOvertimeApplicant() {
+ return requestClient.get<HrmOvertimeApi.Overtime>('/hrm/overtime/current-applicant');
+}
+
+/** 鏂板鍔犵彮鐢宠锛堣崏绋匡級 */
+export function createOvertime(data: HrmOvertimeApi.Overtime) {
+ return requestClient.post('/hrm/overtime/create', data);
+}
+
+/** 鏇存柊鍔犵彮鐢宠 */
+export function updateOvertime(data: HrmOvertimeApi.Overtime) {
+ return requestClient.put('/hrm/overtime/update', data);
+}
+
+/** 鍒犻櫎鍔犵彮鐢宠 */
+export function deleteOvertime(id: number) {
+ return requestClient.delete(`/hrm/overtime/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍔犵彮鐢宠锛堝彂璧峰鎵癸級 */
+export function submitOvertime(id: number, processDefinitionKey: string) {
+ return requestClient.post('/hrm/overtime/submit', null, {
+ params: { id, processDefinitionKey },
+ });
+}
+
+/** 鎾ら攢鍔犵彮鐢宠 */
+export function cancelOvertime(id: number) {
+ return requestClient.post(`/hrm/overtime/cancel?id=${id}`);
+}
+
+/** 鑾峰彇鍗忓悓鍔炲叕涓殑鍙敤鍔犵彮瀹℃壒娴佺▼ */
+export function getOvertimeApproveProcessDefinitionList() {
+ return requestClient.get<HrmOvertimeApi.ApproveProcess[]>(
+ '/hrm/overtime/approve-process-definition-list',
+ );
+}
diff --git a/src/api/hrm/salary/structure/index.ts b/src/api/hrm/salary/structure/index.ts
new file mode 100644
index 0000000..20a25b8
--- /dev/null
+++ b/src/api/hrm/salary/structure/index.ts
@@ -0,0 +1,50 @@
+import { requestClient } from '#/api/request';
+
+export namespace HrmSalaryStructureApi {
+ export interface SalaryStructure {
+ id?: number;
+ name?: string;
+ overtimePayRatio?: number;
+ restDayOvertimePayRatio?: number;
+ legalHolidayOvertimePayRatio?: number;
+ status?: number;
+ remark?: string;
+ createTime?: string;
+ }
+
+ export interface SalaryStructureQueryParam {
+ name?: string;
+ status?: number;
+ }
+}
+
+export function getSalaryStructureList(
+ params?: HrmSalaryStructureApi.SalaryStructureQueryParam,
+) {
+ return requestClient.get<HrmSalaryStructureApi.SalaryStructure[]>(
+ '/hrm/salary/structure/list',
+ { params },
+ );
+}
+
+export function getSalaryStructure(id: number) {
+ return requestClient.get<HrmSalaryStructureApi.SalaryStructure>(
+ `/hrm/salary/structure/get?id=${id}`,
+ );
+}
+
+export function createSalaryStructure(
+ data: HrmSalaryStructureApi.SalaryStructure,
+) {
+ return requestClient.post('/hrm/salary/structure/create', data);
+}
+
+export function updateSalaryStructure(
+ data: HrmSalaryStructureApi.SalaryStructure,
+) {
+ return requestClient.put('/hrm/salary/structure/update', data);
+}
+
+export function deleteSalaryStructure(id: number) {
+ return requestClient.delete(`/hrm/salary/structure/delete?id=${id}`);
+}
diff --git a/src/views/hrm/employee/data.ts b/src/views/hrm/employee/data.ts
index 9affcfa..5e9404f 100644
--- a/src/views/hrm/employee/data.ts
+++ b/src/views/hrm/employee/data.ts
@@ -10,6 +10,7 @@
import { Button } from 'ant-design-vue';
import { z } from '#/adapter/form';
+import { getSalaryStructureList } from '#/api/hrm/salary/structure';
import { getDeptList } from '#/api/system/dept';
import { getSimplePostList } from '#/api/system/post';
import { getSimpleRoleList } from '#/api/system/role';
@@ -242,6 +243,19 @@
},
},
{
+ fieldName: 'salaryStructureId',
+ label: '钖叕缁撴瀯',
+ component: 'ApiSelect',
+ componentProps: {
+ api: () => getSalaryStructureList({ status: 0 }),
+ labelField: 'name',
+ valueField: 'id',
+ allowClear: true,
+ placeholder: '璇烽�夋嫨钖叕缁撴瀯',
+ },
+ help: '鍐冲畾宸ヤ綔鏃ュ姞鐝�嶇巼锛涙湭閫夋嫨鏃朵娇鐢ㄧ郴缁熼粯璁� 1.5 鍊�',
+ },
+ {
fieldName: 'baseSalary',
label: '鍩烘湰宸ヨ祫',
component: 'InputNumber',
diff --git a/src/views/hrm/overtime/data.ts b/src/views/hrm/overtime/data.ts
new file mode 100644
index 0000000..a4985b5
--- /dev/null
+++ b/src/views/hrm/overtime/data.ts
@@ -0,0 +1,320 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+
+import { DICT_TYPE } from '#/packages/constants/src';
+import { getDictOptions } from '#/packages/effects/hooks/src';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+export const OVERTIME_TYPE_OPTIONS = [
+ { label: '宸ヤ綔鏃ュ姞鐝�', value: 1 },
+ { label: '浼戞伅鏃ュ姞鐝�', value: 2 },
+ { label: '娉曞畾鑺傚亣鏃ュ姞鐝�', value: 3 },
+];
+
+export const RELATED_TYPE_OPTIONS = [
+ { label: '椤圭洰', value: 1 },
+ { label: '宸ュ崟', value: 2 },
+];
+
+export function getOvertimeTypeLabel(type?: number) {
+ return OVERTIME_TYPE_OPTIONS.find((item) => item.value === type)?.label || '-';
+}
+
+/** 鏂板/淇敼鍔犵彮鐢宠鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'employeeId',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'baseInfoDivider',
+ label: '鐢宠浜哄熀纭�淇℃伅',
+ component: 'Divider',
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'employeeName',
+ label: '鐢宠浜�',
+ component: 'Input',
+ componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+ },
+ {
+ fieldName: 'employeeNo',
+ label: '宸ュ彿',
+ component: 'Input',
+ componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+ },
+ {
+ fieldName: 'deptName',
+ label: '鎵�灞為儴闂�',
+ component: 'Input',
+ componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+ },
+ {
+ fieldName: 'postName',
+ label: '宀椾綅/鑱岀骇',
+ component: 'Input',
+ componentProps: { disabled: true, placeholder: '绯荤粺鑷姩甯﹀嚭' },
+ },
+ {
+ fieldName: 'overtimeInfoDivider',
+ label: '鍔犵彮鏍稿績淇℃伅',
+ component: 'Divider',
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'overtimeDate',
+ label: '鍔犵彮鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鍔犵彮鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'overtimeType',
+ label: '鍔犵彮绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: OVERTIME_TYPE_OPTIONS,
+ placeholder: '璇烽�夋嫨鍔犵彮绫诲瀷',
+ },
+ help: '涓嶅悓绫诲瀷瀵瑰簲涓嶅悓鐨勮柂璧勮绠楄鍒�',
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'startTime',
+ label: '寮�濮嬫椂闂�',
+ component: 'TimePicker',
+ componentProps: {
+ format: 'HH:mm',
+ minuteStep: 30,
+ placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+ valueFormat: 'HH:mm:ss',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'endTime',
+ label: '缁撴潫鏃堕棿',
+ component: 'TimePicker',
+ componentProps: {
+ format: 'HH:mm',
+ minuteStep: 30,
+ placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+ valueFormat: 'HH:mm:ss',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'crossDay',
+ label: '鏄惁璺ㄥぉ',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ disabled: true,
+ unCheckedChildren: '鍚�',
+ },
+ defaultValue: false,
+ help: '缁撴潫鏃堕棿鏃╀簬鎴栫瓑浜庡紑濮嬫椂闂存椂锛岀郴缁熻嚜鍔ㄦ爣璁颁负璺ㄥぉ',
+ },
+ {
+ fieldName: 'mealDeduction',
+ label: '鎵i櫎鐢ㄩ鏃堕棿',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鎵i櫎',
+ unCheckedChildren: '涓嶆墸闄�',
+ },
+ defaultValue: false,
+ },
+ {
+ fieldName: 'mealDeductionHours',
+ label: '鐢ㄩ鎵i櫎鏃堕暱',
+ component: 'InputNumber',
+ componentProps: {
+ min: 0.5,
+ max: 4,
+ precision: 1,
+ step: 0.5,
+ style: { width: '100%' },
+ },
+ defaultValue: 1,
+ suffix: '灏忔椂',
+ dependencies: {
+ triggerFields: ['mealDeduction'],
+ show: (values) => Boolean(values.mealDeduction),
+ },
+ },
+ {
+ fieldName: 'duration',
+ label: '鍔犵彮鏃堕暱',
+ component: 'InputNumber',
+ componentProps: {
+ disabled: true,
+ min: 0,
+ precision: 1,
+ step: 0.5,
+ style: { width: '100%' },
+ },
+ help: '鎸夊疄闄呮椂娈垫墸闄ょ敤椁愭椂闂村悗锛岃嚜鍔ㄥ彇鏁村埌 0.5 灏忔椂',
+ suffix: '灏忔椂',
+ rules: 'required',
+ },
+ {
+ fieldName: 'reasonInfoDivider',
+ label: '鍔犵彮浜嬬敱',
+ component: 'Divider',
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'reason',
+ label: '鍔犵彮鍘熷洜',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ maxlength: 500,
+ placeholder: '璇疯鏄庡姞鐝殑蹇呰鎬у拰鍘熷洜',
+ rows: 3,
+ showCount: true,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'relatedType',
+ label: '鍏宠仈绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: RELATED_TYPE_OPTIONS,
+ placeholder: '鍙�夛細椤圭洰鎴栧伐鍗�',
+ },
+ },
+ {
+ fieldName: 'relatedCode',
+ label: '椤圭洰/宸ュ崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ maxlength: 100,
+ placeholder: '璇疯緭鍏ュ叧鑱旈」鐩垨宸ュ崟缂栧彿',
+ },
+ dependencies: {
+ triggerFields: ['relatedType'],
+ componentProps: (values) => ({
+ allowClear: true,
+ maxlength: 100,
+ placeholder: values.relatedType === 1
+ ? '璇疯緭鍏ラ」鐩紪鍙�'
+ : values.relatedType === 2
+ ? '璇疯緭鍏ュ伐鍗曠紪鍙�'
+ : '璇峰厛閫夋嫨鍏宠仈绫诲瀷',
+ disabled: !values.relatedType,
+ }),
+ },
+ },
+ {
+ fieldName: 'workContent',
+ label: '宸ヤ綔鍐呭绠�杩�',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ maxlength: 1000,
+ placeholder: '璇风畝瑕佹弿杩板姞鐝湡闂磋鍒掑畬鎴愮殑鍏蜂綋宸ヤ綔',
+ rows: 4,
+ showCount: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鎼滅储琛ㄥ崟 */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'overtimeDate',
+ label: '鍔犵彮鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇烽�夋嫨鍔犵彮鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ },
+ {
+ fieldName: 'overtimeType',
+ label: '鍔犵彮绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: OVERTIME_TYPE_OPTIONS,
+ placeholder: '璇烽�夋嫨鍔犵彮绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '瀹℃壒鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.HRM_AUDIT_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨瀹℃壒鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃瀛楁 */
+export function useGridColumns(): VxeTableGridOptions<HrmOvertimeApi.Overtime>['columns'] {
+ return [
+ { field: 'id', title: '鐢宠缂栧彿', width: 90 },
+ { field: 'employeeName', title: '鐢宠浜�', minWidth: 100 },
+ { field: 'employeeNo', title: '宸ュ彿', minWidth: 110 },
+ { field: 'deptName', title: '鎵�灞為儴闂�', minWidth: 120 },
+ { field: 'overtimeDate', title: '鍔犵彮鏃ユ湡', width: 120 },
+ {
+ field: 'overtimeType',
+ title: '鍔犵彮绫诲瀷',
+ width: 130,
+ slots: { default: 'overtimeType' },
+ },
+ { field: 'startTime', title: '寮�濮嬫椂闂�', width: 100 },
+ { field: 'endTime', title: '缁撴潫鏃堕棿', width: 100 },
+ { field: 'duration', title: '鏃堕暱(灏忔椂)', width: 100 },
+ {
+ field: 'status',
+ title: '瀹℃壒鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.HRM_AUDIT_STATUS },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/hrm/overtime/detail/approve/index.vue b/src/views/hrm/overtime/detail/approve/index.vue
new file mode 100644
index 0000000..bfab940
--- /dev/null
+++ b/src/views/hrm/overtime/detail/approve/index.vue
@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, onMounted, ref } from 'vue';
+
+import { ContentWrap } from '@vben/common-ui';
+import { DICT_TYPE } from '@vben/constants';
+import { formatDateTime } from '@vben/utils';
+
+import { Spin, Tag } from 'ant-design-vue';
+
+import { getOvertime } from '#/api/hrm/overtime';
+import { DictTag } from '#/components/dict-tag';
+import { useDescription } from '#/components/description';
+import { getOvertimeTypeLabel } from '#/views/hrm/overtime/data';
+
+defineOptions({ name: 'HrmOvertimeApproveDetail' });
+
+const props = defineProps<{ id: string }>();
+const loading = ref(false);
+const formData = ref<HrmOvertimeApi.Overtime>();
+
+function textRender(value: unknown) {
+ return value === 0 || value ? String(value) : '-';
+}
+
+const [Descriptions] = useDescription({
+ bordered: true,
+ column: 2,
+ schema: [
+ { label: '鐢宠浜�', field: 'employeeName', render: textRender },
+ { label: '宸ュ彿', field: 'employeeNo', render: textRender },
+ { label: '鎵�灞為儴闂�', field: 'deptName', render: textRender },
+ { label: '宀椾綅/鑱岀骇', field: 'postName', render: textRender },
+ { label: '鍔犵彮鏃ユ湡', field: 'overtimeDate', render: textRender },
+ {
+ label: '鍔犵彮绫诲瀷',
+ field: 'overtimeType',
+ render: (value) => h(Tag, { color: value === 3 ? 'red' : value === 2 ? 'orange' : 'blue' }, () => getOvertimeTypeLabel(Number(value))),
+ },
+ { label: '寮�濮嬫椂闂�', field: 'startTime', render: textRender },
+ { label: '缁撴潫鏃堕棿', field: 'endTime', render: textRender },
+ { label: '鍔犵彮鏃堕暱', field: 'duration', render: (value) => `${textRender(value)} 灏忔椂` },
+ { label: '鏄惁璺ㄥぉ', field: 'crossDay', render: (value) => value ? '鏄�' : '鍚�' },
+ { label: '鐢ㄩ鎵i櫎', field: 'mealDeduction', render: (value) => value ? '鏄�' : '鍚�' },
+ { label: '鎵i櫎鏃堕暱', field: 'mealDeductionHours', render: (value) => value ? `${value} 灏忔椂` : '-' },
+ { label: '鍔犵彮鍘熷洜', field: 'reason', render: textRender },
+ { label: '鍏宠仈绫诲瀷', field: 'relatedType', render: (value) => value === 1 ? '椤圭洰' : value === 2 ? '宸ュ崟' : '-' },
+ { label: '椤圭洰/宸ュ崟缂栧彿', field: 'relatedCode', render: textRender },
+ { label: '宸ヤ綔鍐呭绠�杩�', field: 'workContent', render: textRender },
+ {
+ label: '瀹℃壒鐘舵��',
+ field: 'status',
+ render: (value) => h(DictTag, { type: DICT_TYPE.HRM_AUDIT_STATUS, value }),
+ },
+ { label: '鍒涘缓鏃堕棿', field: 'createTime', render: (value) => formatDateTime(value) as string },
+ ] as DescriptionItemSchema[],
+});
+
+onMounted(async () => {
+ loading.value = true;
+ try {
+ formData.value = await getOvertime(Number(props.id));
+ } finally {
+ loading.value = false;
+ }
+});
+</script>
+
+<template>
+ <ContentWrap class="m-2">
+ <Spin :spinning="loading" tip="鍔犺浇涓�...">
+ <div class="rounded-lg border border-border bg-background p-4">
+ <div class="mb-4 flex items-center gap-2">
+ <span class="inline-block h-4 w-1 rounded bg-primary" />
+ <span class="text-base font-semibold">鍔犵彮鐢宠淇℃伅</span>
+ </div>
+ <Descriptions :data="formData" />
+ </div>
+ </Spin>
+ </ContentWrap>
+</template>
diff --git a/src/views/hrm/overtime/index.vue b/src/views/hrm/overtime/index.vue
new file mode 100644
index 0000000..9b58111
--- /dev/null
+++ b/src/views/hrm/overtime/index.vue
@@ -0,0 +1,200 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { message, Modal, Select, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelOvertime,
+ deleteOvertime,
+ getOvertimeApproveProcessDefinitionList,
+ getOvertimePage,
+ submitOvertime,
+} from '#/api/hrm/overtime';
+import { $t } from '#/locales';
+
+import {
+ getOvertimeTypeLabel,
+ useGridColumns,
+ useGridFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleDetail(row: HrmOvertimeApi.Overtime) {
+ formModalApi.setData({ id: row.id, formType: 'detail' }).open();
+}
+
+function handleEdit(row: HrmOvertimeApi.Overtime) {
+ formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+async function handleDelete(row: HrmOvertimeApi.Overtime) {
+ const hideLoading = message.loading({ content: '姝e湪鍒犻櫎鍔犵彮鐢宠...', duration: 0 });
+ try {
+ await deleteOvertime(row.id!);
+ message.success('鍔犵彮鐢宠鍒犻櫎鎴愬姛');
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const submitModalVisible = ref(false);
+const currentRow = ref<HrmOvertimeApi.Overtime>();
+const processList = ref<HrmOvertimeApi.ApproveProcess[]>([]);
+const selectedProcessKey = ref<string>();
+const submitLoading = ref(false);
+
+async function handleSubmit(row: HrmOvertimeApi.Overtime) {
+ processList.value = await getOvertimeApproveProcessDefinitionList();
+ currentRow.value = row;
+ selectedProcessKey.value = undefined;
+ submitModalVisible.value = true;
+}
+
+async function confirmSubmit() {
+ if (!selectedProcessKey.value) {
+ message.warning('璇烽�夋嫨鍗忓悓鍔炲叕瀹℃壒娴佺▼');
+ return;
+ }
+ submitLoading.value = true;
+ try {
+ await submitOvertime(currentRow.value!.id!, selectedProcessKey.value);
+ message.success('宸叉彁浜よ嚦鍗忓悓鍔炲叕瀹℃壒');
+ submitModalVisible.value = false;
+ handleRefresh();
+ } finally {
+ submitLoading.value = false;
+ }
+}
+
+async function handleCancel(row: HrmOvertimeApi.Overtime) {
+ await cancelOvertime(row.id!);
+ message.success('鎾ら攢鎴愬姛');
+ handleRefresh();
+}
+
+function getOvertimeTypeColor(type?: number) {
+ return type === 1 ? 'blue' : type === 2 ? 'orange' : type === 3 ? 'red' : 'default';
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: { schema: useGridFormSchema() },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) =>
+ await getOvertimePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ }),
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<HrmOvertimeApi.Overtime>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Grid table-title="鍔犵彮鐢宠鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍔犵彮鐢宠']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['hrm:overtime:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #overtimeType="{ row }">
+ <Tag :color="getOvertimeTypeColor(row.overtimeType)">
+ {{ getOvertimeTypeLabel(row.overtimeType) }}
+ </Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ { label: '鏌ョ湅', type: 'link', onClick: handleDetail.bind(null, row) },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['hrm:overtime:update'],
+ ifShow: row.status === 0,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '鎻愪氦',
+ type: 'link',
+ auth: ['hrm:overtime:submit'],
+ ifShow: row.status === 0,
+ onClick: handleSubmit.bind(null, row),
+ },
+ {
+ label: '鎾ら攢',
+ type: 'link',
+ auth: ['hrm:overtime:cancel'],
+ ifShow: row.status === 10,
+ onClick: handleCancel.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['hrm:overtime:delete'],
+ ifShow: row.status === 0 || row.status === 40,
+ popConfirm: {
+ title: '纭鍒犻櫎璇ュ姞鐝敵璇峰悧锛�',
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ <Modal
+ v-model:open="submitModalVisible"
+ title="鎻愪氦鑷冲崗鍚屽姙鍏�"
+ width="420px"
+ :confirm-loading="submitLoading"
+ @ok="confirmSubmit"
+ >
+ <Select
+ v-model:value="selectedProcessKey"
+ class="w-full"
+ placeholder="璇烽�夋嫨鍔犵彮瀹℃壒娴佺▼"
+ :options="processList.map((item) => ({ label: item.name, value: item.key }))"
+ />
+ </Modal>
+ </Page>
+</template>
diff --git a/src/views/hrm/overtime/modules/form.vue b/src/views/hrm/overtime/modules/form.vue
new file mode 100644
index 0000000..4613de8
--- /dev/null
+++ b/src/views/hrm/overtime/modules/form.vue
@@ -0,0 +1,159 @@
+<script lang="ts" setup>
+import type { HrmOvertimeApi } from '#/api/hrm/overtime';
+import type { FormType } from '../data';
+
+import { computed, ref } from 'vue';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOvertime,
+ getCurrentOvertimeApplicant,
+ getOvertime,
+ updateOvertime,
+} from '#/api/hrm/overtime';
+import { $t } from '#/locales';
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+
+const isDetail = computed(() => formType.value === 'detail');
+const getTitle = computed(() => {
+ if (isDetail.value) return '鏌ョ湅鍔犵彮鐢宠';
+ return formType.value === 'update' ? '淇敼鍔犵彮鐢宠' : '鏂板鍔犵彮鐢宠';
+});
+
+/** 鏍规嵁鏃舵銆佽法澶╂爣璇嗗拰鐢ㄩ鎵i櫎璁$畻鍔犵彮鏃堕暱锛屽苟鎸� 0.5 灏忔椂鍙栨暣 */
+function calculateDuration(values: Record<string, any>) {
+ const { startTime, endTime } = values;
+ if (!startTime || !endTime) return { crossDay: false, duration: 0 };
+
+ const toMinutes = (time: string) => {
+ const [hour = 0, minute = 0] = time.split(':').map(Number);
+ return hour * 60 + minute;
+ };
+ const startMinutes = toMinutes(startTime);
+ let endMinutes = toMinutes(endTime);
+ const crossDay = endMinutes <= startMinutes;
+ if (crossDay) endMinutes += 24 * 60;
+
+ const deductionMinutes = values.mealDeduction
+ ? Number(values.mealDeductionHours || 0) * 60
+ : 0;
+ const actualHours = Math.max(0, (endMinutes - startMinutes - deductionMinutes) / 60);
+ return {
+ crossDay,
+ duration: Math.round(actualHours * 2) / 2,
+ };
+}
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-1',
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'horizontal',
+ schema: [],
+ scrollToFirstError: true,
+ showDefaultActions: false,
+ handleValuesChange: (values, changedFields) => {
+ const durationFields = [
+ 'startTime',
+ 'endTime',
+ 'mealDeduction',
+ 'mealDeductionHours',
+ ];
+ if (changedFields.some((field) => durationFields.includes(field))) {
+ const result = calculateDuration(values);
+ void formApi.setValues(result);
+ }
+ if (changedFields.includes('relatedType') && !values.relatedType) {
+ void formApi.setFieldValue('relatedCode', undefined);
+ }
+ },
+});
+
+async function setCurrentEmployee() {
+ const employee = await getCurrentOvertimeApplicant();
+ await formApi.setValues({
+ employeeId: employee.employeeId,
+ employeeName: employee.employeeName,
+ employeeNo: employee.employeeNo,
+ deptName: employee.deptName,
+ postName: employee.postName,
+ mealDeduction: false,
+ mealDeductionHours: 1,
+ crossDay: false,
+ duration: 0,
+ });
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (isDetail.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+
+ const data = (await formApi.getValues()) as HrmOvertimeApi.Overtime;
+ if (!data.employeeId) {
+ message.warning('褰撳墠璐﹀彿鏈叧鑱斿憳宸ユ。妗堬紝鏆傛椂鏃犳硶鎻愪氦鍔犵彮鐢宠');
+ return;
+ }
+ if (!data.duration || data.duration <= 0) {
+ message.warning('鍔犵彮鏃堕暱蹇呴』澶т簬 0 灏忔椂锛岃妫�鏌ユ椂娈垫垨鐢ㄩ鎵i櫎');
+ return;
+ }
+ if (data.relatedType && !data.relatedCode?.trim()) {
+ message.warning('璇烽�夋嫨鍏宠仈绫诲瀷鍚庡~鍐欏搴旂殑椤圭洰鎴栧伐鍗曠紪鍙�');
+ return;
+ }
+
+ modalApi.lock();
+ try {
+ if (data.id) await updateOvertime(data);
+ else await createOvertime(data);
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema() });
+ formApi.setDisabled(isDetail.value);
+ modalApi.setState({ showConfirmButton: !isDetail.value });
+
+ if (!data.id) {
+ await setCurrentEmployee();
+ return;
+ }
+
+ modalApi.lock();
+ try {
+ await formApi.setValues(await getOvertime(data.id));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-[900px]">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/hrm/salary/structure/data.ts b/src/views/hrm/salary/structure/data.ts
new file mode 100644
index 0000000..68d1ddf
--- /dev/null
+++ b/src/views/hrm/salary/structure/data.ts
@@ -0,0 +1,163 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+
+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: 'name',
+ label: '缁撴瀯鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ maxlength: 100,
+ placeholder: '渚嬪锛氭爣鍑嗗憳宸ヨ柂閰�佺鐞嗗眰钖叕',
+ showCount: true,
+ },
+ rules: 'required',
+ formItemClass: 'col-span-2',
+ },
+ {
+ fieldName: 'overtimePayRatio',
+ label: '宸ヤ綔鏃ュ�嶇巼',
+ component: 'InputNumber',
+ componentProps: {
+ addonAfter: '鍊�',
+ max: 10,
+ min: 0,
+ precision: 2,
+ step: 0.1,
+ placeholder: '0 琛ㄧず涓嶈鍔犵彮宸ヨ祫',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'restDayOvertimePayRatio',
+ label: '浼戞伅鏃ュ�嶇巼',
+ component: 'InputNumber',
+ componentProps: {
+ addonAfter: '鍊�',
+ max: 10,
+ min: 0,
+ precision: 2,
+ step: 0.1,
+ placeholder: '0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'legalHolidayOvertimePayRatio',
+ label: '娉曞畾鑺傚亣鏃ュ�嶇巼',
+ component: 'InputNumber',
+ componentProps: {
+ addonAfter: '鍊�',
+ max: 10,
+ min: 0,
+ precision: 2,
+ step: 0.1,
+ placeholder: '0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�',
+ style: { width: '100%' },
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: { options: statusOptions },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ maxlength: 500,
+ placeholder: '濉啓閫傜敤宀椾綅銆佽亴绾ф垨鐗规畩璁¤柂璇存槑',
+ rows: 3,
+ showCount: true,
+ },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'name',
+ label: '缁撴瀯鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ョ粨鏋勫悕绉�',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: statusOptions,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ];
+}
+
+export function useGridColumns(): VxeTableGridOptions<HrmSalaryStructureApi.SalaryStructure>['columns'] {
+ return [
+ { field: 'name', title: '缁撴瀯鍚嶇О', minWidth: 180 },
+ {
+ field: 'overtimePayRatio',
+ title: '宸ヤ綔鏃ュ姞鐝�嶇巼',
+ width: 145,
+ slots: { default: 'overtimePayRatio' },
+ },
+ {
+ field: 'restDayOvertimePayRatio',
+ title: '浼戞伅鏃ュ姞鐝�嶇巼',
+ width: 145,
+ slots: { default: 'restDayOvertimePayRatio' },
+ },
+ {
+ field: 'legalHolidayOvertimePayRatio',
+ title: '娉曞畾鑺傚亣鏃ュ�嶇巼',
+ width: 165,
+ slots: { default: 'legalHolidayOvertimePayRatio' },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'actions',
+ title: '鎿嶄綔',
+ fixed: 'right',
+ slots: { default: 'actions' },
+ width: 150,
+ },
+ ];
+}
diff --git a/src/views/hrm/salary/structure/index.vue b/src/views/hrm/salary/structure/index.vue
new file mode 100644
index 0000000..4427175
--- /dev/null
+++ b/src/views/hrm/salary/structure/index.vue
@@ -0,0 +1,137 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+
+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 {
+ deleteSalaryStructure,
+ getSalaryStructureList,
+} from '#/api/hrm/salary/structure';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'HrmSalaryStructure' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleEdit(row: HrmSalaryStructureApi.SalaryStructure) {
+ formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+async function handleDelete(row: HrmSalaryStructureApi.SalaryStructure) {
+ await deleteSalaryStructure(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', ['钖叕缁撴瀯']));
+ handleRefresh();
+}
+
+function formatRatio(value?: number) {
+ return value === undefined || value === null ? '-' : `${value.toFixed(2)} 鍊峘;
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: { schema: useGridFormSchema() },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ pagerConfig: { enabled: false },
+ proxyConfig: {
+ ajax: {
+ query: async (_params, formValues) =>
+ await getSalaryStructureList(formValues),
+ },
+ },
+ rowConfig: { keyField: 'id', isHover: true },
+ toolbarConfig: { refresh: true, search: true },
+ } as VxeTableGridOptions<HrmSalaryStructureApi.SalaryStructure>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <Alert
+ class="mb-4"
+ description="鍩烘湰宸ヨ祫鍙栧憳宸ユ。妗堬紱绀句繚鍜屽叕绉噾鍩烘暟鍙栧憳宸ョぞ淇濇。妗堛�傝繖閲屼粎缁存姢宸ヤ綔鏃ャ�佷紤鎭棩鍜屾硶瀹氳妭鍋囨棩鍔犵彮鍊嶇巼銆�"
+ 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:salary-structure:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #overtimePayRatio="{ row }">
+ <Tag :color="(row.overtimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+ {{ (row.overtimePayRatio ?? 0) > 0 ? formatRatio(row.overtimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+ </Tag>
+ </template>
+ <template #restDayOvertimePayRatio="{ row }">
+ <Tag :color="(row.restDayOvertimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+ {{ (row.restDayOvertimePayRatio ?? 0) > 0 ? formatRatio(row.restDayOvertimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+ </Tag>
+ </template>
+ <template #legalHolidayOvertimePayRatio="{ row }">
+ <Tag :color="(row.legalHolidayOvertimePayRatio ?? 0) > 0 ? 'blue' : 'default'">
+ {{ (row.legalHolidayOvertimePayRatio ?? 0) > 0 ? formatRatio(row.legalHolidayOvertimePayRatio) : '涓嶈鍔犵彮宸ヨ祫' }}
+ </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:salary-structure:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['hrm:salary-structure:delete'],
+ popConfirm: {
+ title: '纭鍒犻櫎璇ヨ柂閰粨鏋勫悧锛熷凡琚憳宸ヤ娇鐢ㄧ殑缁撴瀯涓嶅彲鍒犻櫎銆�',
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/hrm/salary/structure/modules/form.vue b/src/views/hrm/salary/structure/modules/form.vue
new file mode 100644
index 0000000..0330821
--- /dev/null
+++ b/src/views/hrm/salary/structure/modules/form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { HrmSalaryStructureApi } from '#/api/hrm/salary/structure';
+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 {
+ createSalaryStructure,
+ getSalaryStructure,
+ updateSalaryStructure,
+} from '#/api/hrm/salary/structure';
+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 HrmSalaryStructureApi.SalaryStructure;
+ if (data.id) {
+ await updateSalaryStructure(data);
+ } else {
+ await createSalaryStructure(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({
+ legalHolidayOvertimePayRatio: 3,
+ overtimePayRatio: 1.5,
+ restDayOvertimePayRatio: 2,
+ status: 0,
+ });
+ return;
+ }
+ modalApi.lock();
+ try {
+ await formApi.setValues(await getSalaryStructure(data.id));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-[680px]">
+ <Alert
+ class="mx-4 mb-4"
+ description="涓夌鍔犵彮绫诲瀷鍒嗗埆璁剧疆鍊嶇巼锛涙煇涓�椤硅涓� 0 鏃讹紝浠呰绫诲瀷涓嶈绠楀姞鐝伐璧勶紝瀹℃壒閫氳繃鐨勫姞鐝椂闀夸粛姝e父缁熻銆�"
+ message="鍔犵彮宸ヨ祫璁$畻璇存槑"
+ show-icon
+ type="info"
+ />
+ <Form class="mx-4" />
+ </Modal>
+</template>
--
Gitblit v1.9.3