Commit a336722d authored by 刘斌's avatar 刘斌

feat: 增加人事审批流程

parent 3905e5fc
import type { BaseModel, PageQuery } from '#/api/baseModel';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
export namespace EmployeeFlowApi {
export interface EmployeeFlow extends BaseModel {
/**
* 申请编号
*/
applyCode?: string;
/**
* 审批类型
*/
flowType?: string;
/**
* 员工信息ID
*/
employeeId?: number;
/**
* 开始时间
*/
startDate?: string;
/**
* 结束时间
*/
endDate?: string;
/**
* 状态
*/
status?: string;
/**
* 备注
*/
remark?: string;
}
export interface EmployeeFlowVo {
/**
* 序号
*/
id?: number;
/**
* 申请编号
*/
applyCode?: string;
/**
* 审批类型
*/
flowType?: string;
/**
* 员工信息ID
*/
employeeId?: number;
/**
* 审计内容
*/
auditLogList?: Array<AuditLogVo>;
/**
* 状态
*/
status?: string;
/**
* 备注
*/
remark?: string;
}
export interface AuditLogVo {
/**
* 审计名称(入职、调职、离职)
*/
auditName?: string;
/**
* 字段名称
*/
auditField?: string;
/**
* 字段注释名称
*/
auditFieldName?: string;
/**
* 变更前值
*/
beforeVal?: string;
/**
* 变更后值
*/
afterVal?: string;
}
}
/**
* 查询人事审批对象列表
* @param params
* @returns {*} page
*/
export function apiPage(params: PageQuery) {
return requestClient.get('/employee/flow/page', { params });
}
/**
* 查询人事审批对象详细
* @param id
*/
export function apiDetail(id: number) {
return requestClient.get(`/employee/flow/${id}`);
}
/**
* 新增人事审批对象
* @param data
*/
export function apiAdd(data: EmployeeFlowApi.EmployeeFlow) {
return requestClient.post('/employee/flow', data);
}
/**
* 修改人事审批对象
* @param data
*/
export function apiUpdate(data: EmployeeFlowApi.EmployeeFlow) {
return requestClient.put('/employee/flow', data);
}
/**
* 删除人事审批对象
* @param id
*/
export function apiDelete(id: Array<number> | number) {
return requestClient.delete(`/employee/flow/${id}`);
}
/**
* 导出人事审批对象
* @param params
*/
export function apiExport(params: PageQuery) {
return commonExport('/employee/flow/export', params);
}
/**
* 提交 & 发起流程(后端发起)
* @param data data
* @returns void
*/
export function submitAndStartWorkflow(data: EmployeeFlowApi.EmployeeFlow) {
return requestClient.post('/employee/flow/submitAndFlowStart', data, {
successMessageMode: 'message',
});
}
......@@ -46,6 +46,7 @@ export namespace EmployeeInfoApi {
* 性别
*/
gender?: string;
genderName?: string;
/**
* 身份证号码
*/
......@@ -76,10 +77,12 @@ export namespace EmployeeInfoApi {
* 婚姻状况
*/
maritalStatus?: string;
maritalStatusName?: string;
/**
* 政治面貌
*/
politicalStatus?: string;
politicalStatusName?: string;
/**
* 手机号码
*/
......@@ -117,6 +120,7 @@ export namespace EmployeeInfoApi {
* 工龄段
*/
yearsOfServiceSegment?: string;
yearsOfServiceSegmentName?: string;
/**
* 学历
*/
......@@ -141,6 +145,7 @@ export namespace EmployeeInfoApi {
* 员工类型
*/
employeeType?: string;
employeeTypeName?: string;
/**
* 职称情况
*/
......@@ -153,6 +158,7 @@ export namespace EmployeeInfoApi {
* 用工形式
*/
employmentForm?: string;
employmentFormName?: string;
/**
* 劳动合同期限
*/
......@@ -202,6 +208,39 @@ export namespace EmployeeInfoApi {
*/
resignationReason?: string;
}
export interface EmployeeApplyBo {
/**
* 序号
*/
id?: number;
/**
* 备注
*/
remark?: string;
}
export interface EmployeeTransferApplyBo extends EmployeeApplyBo {
/**
* 一级部门
*/
firstLevelDepartment?: string;
/**
* 二级部门
*/
secondLevelDepartment?: string;
}
export interface EmployeeResignApplyBo extends EmployeeApplyBo {
/**
* 离职时间
*/
resignDate?: string;
/**
* 离职原因
*/
resignReason?: string;
}
}
/**
......@@ -219,6 +258,13 @@ export function apiPage(params: PageQuery) {
export function apiDetail(id: number) {
return requestClient.get(`/employee/info/${id}`);
}
/**
* 展示员工信息详细
* @param id
*/
export function apiInfoDetail(id: number) {
return requestClient.get(`/employee/info/detail/${id}`);
}
/**
* 新增员工信息
* @param data
......@@ -248,12 +294,26 @@ export function apiExport(id: number) {
return commonExport(`/employee/info/export/${id}`, {});
}
/**
* 申请员工入职
* @param data
*/
export function applyEntry(data: EmployeeInfoApi.EmployeeApplyBo) {
return requestClient.post('/employee/info/applyEntry', data);
}
/**
* 申请员工离职
* @param data
*/
export function applyResign(data: EmployeeInfoApi.EmployeeResignApplyBo) {
return requestClient.post('/employee/info/applyResign', data);
}
/**
* 申请员工调职
* @param id
*/
export function applyResign(id: number) {
return requestClient.put(`/employee/info/applyResign/${id}`);
export function applyTransfer(data: EmployeeInfoApi.EmployeeTransferApplyBo) {
return requestClient.post('/employee/info/applyTransfer', data);
}
/**
......
......@@ -24,6 +24,19 @@ const routes: RouteRecordRaw[] = [
componentPath: '#/views/hr/employeeInfo/list.vue',
},
},
{
name: 'EmployeeFlowList',
path: '/hr/employee/flow',
// hidden: false,
component: () => import('#/views/workflow/hrFlow/list.vue'),
meta: {
title: '人事申请',
icon: 'fluent-mdl2:leave-user',
noCache: false,
componentPath: '#/views/workflow/hrFlow/list.vue',
// link: null,
},
},
],
},
];
......
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { EmployeeInfoApi } from '#/api/hr/employeeInfo';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { applyEntry } from '#/api/hr/employeeInfo';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{
success: [];
}>();
const formData = ref<EmployeeInfoApi.EmployeeApplyBo>();
const formSchema: VbenFormSchema[] = [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
component: 'Input',
fieldName: 'name',
label: '姓名',
disabled: true,
},
// {
// component: 'Input',
// fieldName: 'dictType',
// label: '字典类型',
// rules: 'required',
// },
// {
// component: 'Input',
// fieldName: 'description',
// label: '描述',
// rules: 'required',
// },
{
component: 'Textarea',
fieldName: 'remark',
label: '入职申请备注',
formItemClass: 'items-start',
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 90,
},
schema: formSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: onSubmit,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const data = modalApi.getData() as { id?: number; name?: string };
if (data) {
formData.value = data;
await formApi.setValues(formData.value);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
modalApi.lock();
const data = await formApi.getValues<EmployeeInfoApi.EmployeeApplyBo>();
try {
await applyEntry(data);
resetInitialized();
emit('success');
modalApi.close();
} finally {
modalApi.unlock();
}
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
// const getModalTitle = computed(() =>
// formData.value?.id ? '修改字典类型' : '新增字典类型',
// );
</script>
<template>
<BasicModal title="入职申请">
<BasicForm class="mx-4" />
</BasicModal>
</template>
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { EmployeeInfoApi } from '#/api/hr/employeeInfo';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { applyResign } from '#/api/hr/employeeInfo';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{
success: [];
}>();
const formData = ref<EmployeeInfoApi.EmployeeResignApplyBo>();
const formSchema: VbenFormSchema[] = [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
component: 'Input',
fieldName: 'name',
label: '姓名',
disabled: true,
},
{
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
getVxePopupContainer,
},
fieldName: 'resignDate',
label: '离职日期',
rules: 'required',
},
{
component: 'Input',
fieldName: 'resignReason',
label: '离职原因',
// rules: 'required',
},
// {
// component: 'Input',
// fieldName: 'description',
// label: '描述',
// rules: 'required',
// },
{
component: 'Textarea',
fieldName: 'remark',
label: '离职申请备注',
formItemClass: 'items-start',
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 90,
},
schema: formSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: onSubmit,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const data = modalApi.getData() as { id?: number; name?: string };
if (data) {
formData.value = data;
await formApi.setValues(formData.value);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
modalApi.lock();
const data =
await formApi.getValues<EmployeeInfoApi.EmployeeResignApplyBo>();
try {
await applyResign(data);
resetInitialized();
emit('success');
modalApi.close();
} finally {
modalApi.unlock();
}
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
// const getModalTitle = computed(() =>
// formData.value?.id ? '修改字典类型' : '新增字典类型',
// );
</script>
<template>
<BasicModal title="离职申请">
<BasicForm class="mx-4" />
</BasicModal>
</template>
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { EmployeeInfoApi } from '#/api/hr/employeeInfo';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import {
selectDeptNamesByLevel,
selectDeptNamesByParent,
} from '#/api/hr/employeeDept';
import { applyTransfer } from '#/api/hr/employeeInfo';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{
success: [];
}>();
const formData = ref<EmployeeInfoApi.EmployeeTransferApplyBo>();
const formSchema: VbenFormSchema[] = [
{
label: '主键',
fieldName: 'id',
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
component: 'Input',
fieldName: 'name',
label: '姓名',
disabled: true,
},
{
component: 'ApiSelect',
componentProps: (formModel) => ({
api: async () => {
const data: string[] = await selectDeptNamesByLevel(1);
return data.map((item) => ({
label: item,
value: item,
}));
},
async onSelect(name: string) {
/** 根据部门ID加载岗位 */
await setupDeptLevel2Options(name);
/** 变化后需要重新选择岗位 */
formModel.secondLevelDepartment = [];
},
getVxePopupContainer,
}),
fieldName: 'firstLevelDepartment',
label: '一级部门',
rules: 'required',
},
{
component: 'Select',
componentProps: {
getVxePopupContainer,
placeholder: '请先选择一级部门',
},
fieldName: 'secondLevelDepartment',
label: '二级部门',
rules: 'required',
},
// {
// component: 'Input',
// fieldName: 'description',
// label: '描述',
// rules: 'required',
// },
{
component: 'Textarea',
fieldName: 'remark',
label: '调配申请备注',
formItemClass: 'items-start',
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 90,
},
schema: formSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: onSubmit,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const data = modalApi.getData() as { id?: number; name?: string };
if (data) {
formData.value = data;
await formApi.setValues(formData.value);
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
modalApi.lock();
const data =
await formApi.getValues<EmployeeInfoApi.EmployeeTransferApplyBo>();
try {
await applyTransfer(data);
resetInitialized();
emit('success');
modalApi.close();
} finally {
modalApi.unlock();
}
}
}
async function setupDeptLevel2Options(name: string) {
const deptNameList: string[] = await selectDeptNamesByParent(name);
const options = deptNameList.map((item) => ({
label: item,
value: item,
}));
const placeholder = options.length > 0 ? '请选择' : '该部门下暂无岗位';
formApi.updateSchema([
{
componentProps: { options, placeholder },
fieldName: 'secondLevelDepartment',
},
]);
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
// const getModalTitle = computed(() =>
// formData.value?.id ? '修改字典类型' : '新增字典类型',
// );
</script>
<template>
<BasicModal title="调配申请">
<BasicForm class="mx-4" />
</BasicModal>
</template>
<script setup lang="ts">
import type { EmployeeInfoApi } from '#/api/hr/employeeInfo';
import { ref, shallowRef } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Descriptions, DescriptionsItem, Skeleton } from 'ant-design-vue';
import { apiInfoDetail } from '#/api/hr/employeeInfo';
const [BasicDrawer, drawerApi] = useVbenDrawer({
onOpenChange: handleOpenChange,
onClosed() {
currentEmployee.value = null;
},
});
const currentEmployee = shallowRef<EmployeeInfoApi.Employee | null>(null);
const loading = ref(false);
async function handleOpenChange(open: boolean) {
if (!open) {
return null;
}
const { id } = drawerApi.getData() as { id?: number };
if (!id) {
return null;
}
const data = await apiInfoDetail(id);
currentEmployee.value = data;
}
// const actionInfo = computed(() => {
// if (!currentLog.value) {
// return '-';
// }
// const data = currentLog.value;
// // return `账号: ${data.operName} / ${data.deptName} / ${data.ip} / ${data.operLocation}`;
// return `账号: ${data.operName} / ${data.ip}`;
// });
</script>
<template>
<BasicDrawer :footer="false" class="w-[900px]" title="员工信息">
<Skeleton v-if="loading" active />
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="2"
class="mb-1"
:label-style="{
width: '140px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">部门信息</div>
</template>
<DescriptionsItem label="板块">
{{ currentEmployee.plate }}
</DescriptionsItem>
<DescriptionsItem label="项目">
{{ currentEmployee.project }}
</DescriptionsItem>
<DescriptionsItem label="一级部门">
{{ currentEmployee.firstLevelDepartment }}
</DescriptionsItem>
<DescriptionsItem label="二级部门">
{{ currentEmployee.secondLevelDepartment }}
</DescriptionsItem>
<DescriptionsItem label="工号">
{{ currentEmployee.employeeId }}
</DescriptionsItem>
<DescriptionsItem label="职级">
{{ currentEmployee.jobLevel }}
</DescriptionsItem>
<DescriptionsItem label="岗位">
{{ currentEmployee.position }}
</DescriptionsItem>
<DescriptionsItem label="职务">
{{ currentEmployee.post }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="2"
class="mb-1"
:label-style="{
width: '140px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">员工信息</div>
</template>
<DescriptionsItem label="姓名">
{{ currentEmployee.name }}
</DescriptionsItem>
<DescriptionsItem label="性别">
{{ currentEmployee.genderName }}
</DescriptionsItem>
<DescriptionsItem label="身份证号码">
{{ currentEmployee.idCardNumber }}
</DescriptionsItem>
<DescriptionsItem label="手机号">
{{ currentEmployee.phoneNumber }}
</DescriptionsItem>
<DescriptionsItem label="出生日期">
{{ currentEmployee.birthDate }}
</DescriptionsItem>
<DescriptionsItem label="年龄">
{{ currentEmployee.age }}
</DescriptionsItem>
<DescriptionsItem label="籍贯">
{{ currentEmployee.nativePlace }}
</DescriptionsItem>
<DescriptionsItem label="民族">
{{ currentEmployee.ethnicity }}
</DescriptionsItem>
<DescriptionsItem label="婚姻状况">
{{ currentEmployee.maritalStatusName }}
</DescriptionsItem>
<DescriptionsItem label="政治面貌">
{{ currentEmployee.politicalStatusName }}
</DescriptionsItem>
<DescriptionsItem label="紧急联系人">
{{ currentEmployee.emergencyContact }}
</DescriptionsItem>
<DescriptionsItem label="紧急联系人电话">
{{ currentEmployee.emergencyContactPhone }}
</DescriptionsItem>
<DescriptionsItem label="家庭地址">
{{ currentEmployee.homeAddress }}
</DescriptionsItem>
<DescriptionsItem label="户口所在地">
{{ currentEmployee.householdRegistrationAddress }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="2"
class="mb-1"
:label-style="{
width: '140px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">教育信息</div>
</template>
<DescriptionsItem label="学历">
{{ currentEmployee.education }}
</DescriptionsItem>
<DescriptionsItem label="学位">
{{ currentEmployee.degree }}
</DescriptionsItem>
<DescriptionsItem label="毕业时间">
{{ currentEmployee.graduationDate }}
</DescriptionsItem>
<DescriptionsItem label="专业">
{{ currentEmployee.major }}
</DescriptionsItem>
<DescriptionsItem label="毕业院校">
{{ currentEmployee.graduateSchool }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="2"
class="mb-1"
:label-style="{
width: '140px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">职业信息</div>
</template>
<DescriptionsItem label="参加工作时间">
{{ currentEmployee.workStartDate }}
</DescriptionsItem>
<DescriptionsItem label="入职时间">
{{ currentEmployee.entryDate }}
</DescriptionsItem>
<DescriptionsItem label="转正时间">
{{ currentEmployee.regularizationDate }}
</DescriptionsItem>
<DescriptionsItem label="工龄">
{{ currentEmployee.yearsOfService }}
</DescriptionsItem>
<DescriptionsItem label="工龄段">
{{ currentEmployee.yearsOfServiceSegmentName }}
</DescriptionsItem>
<DescriptionsItem label="员工类型">
{{ currentEmployee.employeeTypeName }}
</DescriptionsItem>
<DescriptionsItem label="用工形式">
{{ currentEmployee.employmentFormName }}
</DescriptionsItem>
<DescriptionsItem label="职称情况">
{{ currentEmployee.professionalTitle }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="2"
class="mb-1"
:label-style="{
width: '150px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">合同信息</div>
</template>
<DescriptionsItem label="劳动合同期限">
{{ currentEmployee.contractTerm }}
</DescriptionsItem>
<DescriptionsItem label="劳动合同开始时间">
{{ currentEmployee.contractStartDate }}
</DescriptionsItem>
<DescriptionsItem label="劳动合同截止时间">
{{ currentEmployee.contractEndDate }}
</DescriptionsItem>
<DescriptionsItem label="合同到期提醒">
{{ currentEmployee.contractExpirationReminder }}
</DescriptionsItem>
<DescriptionsItem label="合同主体">
{{ currentEmployee.contractEntity }}
</DescriptionsItem>
<DescriptionsItem label="劳动合同签订情况">
{{ currentEmployee.contractSigningStatus }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee"
size="small"
bordered
:column="1"
class="mb-1"
:label-style="{
width: '140px',
fontWeight: 500,
backgroundColor: '#75e0e0',
}"
>
<template #title>
<div class="text-center">其他信息</div>
</template>
<DescriptionsItem label="异动情况">
{{ currentEmployee.transferStatus }}
</DescriptionsItem>
<DescriptionsItem label="奖惩情况">
{{ currentEmployee.rewardPunishmentStatus }}
</DescriptionsItem>
<DescriptionsItem label="备注">
{{ currentEmployee.remarks }}
</DescriptionsItem>
<!-- <DescriptionsItem label="请求参数">
<div class="max-h-[300px] overflow-y-auto">
<component :is="renderJsonPreview(currentLog.params)" />
</div>
</DescriptionsItem>
<DescriptionsItem v-if="currentLog.result" label="响应参数">
<div class="max-h-[300px] overflow-y-auto">
<component :is="renderJsonPreview(currentLog.result)" />
</div>
</DescriptionsItem>
<DescriptionsItem label="请求耗时">
{{ `${currentLog.costTime} ms` }}
</DescriptionsItem>
<DescriptionsItem label="操作时间">
{{ `${currentLog.createTime}` }}
</DescriptionsItem> -->
</Descriptions>
</BasicDrawer>
</template>
......@@ -6,16 +6,19 @@ import type { EmployeeInfoApi } from '#/api/hr/employeeInfo';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { getVxePopupContainer } from '@vben/utils';
import { Button, message, Popconfirm, Space } from 'ant-design-vue';
import { Button, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { apiExport, apiPage, applyResign } from '#/api/hr/employeeInfo';
import { apiExport, apiPage } from '#/api/hr/employeeInfo';
import { GhostButton } from '#/components/global/button';
import { commonDownloadExcel } from '#/utils/file/download';
import applyEntryModel from './apply-entry-model.vue';
import applyResignModel from './apply-resign-model.vue';
import applyTransferModel from './apply-transfer-model.vue';
import { querySchema, useColumns } from './data';
import employeeDetailDrawer from './employee-detail-drawer.vue';
import employeeImportModal from './employee-import-modal.vue';
import Form from './form.vue';
......@@ -29,6 +32,16 @@ function handleImport() {
employeeImportModalApi.open();
}
const [ApplyEntryModel, applyEntryModelApi] = useVbenModal({
connectedComponent: applyEntryModel,
});
const [ApplyTransferModel, applyTransferModelApi] = useVbenModal({
connectedComponent: applyTransferModel,
});
const [ApplyResignModel, applyResignModelApi] = useVbenModal({
connectedComponent: applyResignModel,
});
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
......@@ -51,6 +64,9 @@ const formOptions: VbenFormProps = {
const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form,
});
const [EmployeeDetailDrawer, employeeDetailDrawerApi] = useVbenDrawer({
connectedComponent: employeeDetailDrawer,
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
......@@ -80,55 +96,26 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions,
});
// function onActionClick({
// code,
// row,
// }: OnActionClickParams<EmployeeInfoApi.Employee>) {
// switch (code) {
// case 'delete': {
// onResign(row);
// break;
// }
// case 'edit': {
// onEdit(row);
// break;
// }
// case 'export': {
// handleDownloadExcel(row);
// break;
// }
// default: {
// break;
// }
// }
// }
function onRefresh() {
gridApi.query();
}
function onEdit(row: EmployeeInfoApi.Employee) {
formDrawerApi.setData({ id: row.id }).open();
// formDrawerApi.setData({ id: row.id }).open();
employeeDetailDrawerApi.setData({ id: row.id }).open();
}
function onCreate() {
formDrawerApi.setData({}).open();
}
function onEntryApply(row: EmployeeInfoApi.Employee) {
applyEntryModelApi.setData({ id: row.id, name: row.name }).open();
}
function onResign(row: EmployeeInfoApi.Employee) {
const hideLoading = message.loading({
content: `正在处理${row.name}的离职...`,
duration: 0,
key: 'action_process_msg',
});
applyResign(row.id || 0)
.then(() => {
message.success({
content: `${row.name}离职申请成功`,
key: 'action_process_msg',
});
onRefresh();
})
.catch(() => {
hideLoading();
});
applyResignModelApi.setData({ id: row.id, name: row.name }).open();
}
function onTransfer(row: EmployeeInfoApi.Employee) {
applyTransferModelApi.setData({ id: row.id, name: row.name }).open();
}
function handleDownloadExcel(row: EmployeeInfoApi.Employee) {
commonDownloadExcel(apiExport, '员工信息', row.id);
......@@ -137,6 +124,7 @@ function handleDownloadExcel(row: EmployeeInfoApi.Employee) {
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<EmployeeDetailDrawer />
<Grid table-title="员工信息列表">
<template #toolbar-tools>
<Space>
......@@ -146,12 +134,6 @@ function handleDownloadExcel(row: EmployeeInfoApi.Employee) {
>
导入
</Button>
<!-- <Button
v-access:code="['employee:info:export']"
@click="handleDownloadExcel"
>
导出
</Button> -->
<Button
v-access:code="['employee:info:add']"
type="primary"
......@@ -180,32 +162,34 @@ function handleDownloadExcel(row: EmployeeInfoApi.Employee) {
v-access:code="['employee:info:edit']"
@click.stop="onEdit(row)"
>
编辑
查看
</GhostButton>
<GhostButton
v-access:code="['employee:info:add']"
@click.stop="onEntryApply(row)"
>
申请入职
</GhostButton>
<!-- <GhostButton
v-access:code="['auth:role:edit']"
@click.stop="onAssignRole(row)"
<GhostButton
v-access:code="['employee:info:edit']"
@click.stop="onTransfer(row)"
>
分配
</GhostButton> -->
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
:title="`确认申请离职【${row.name}】?`"
@confirm="onResign(row)"
申请调配
</GhostButton>
<GhostButton
danger
v-access:code="['employee:info:resign']"
@click.stop="onResign(row)"
>
<GhostButton
danger
v-access:code="['employee:info:resign']"
@click.stop=""
>
申请离职
</GhostButton>
</Popconfirm>
申请离职
</GhostButton>
</Space>
<!-- </template> -->
</template>
</Grid>
<EmployeeImportModal @reload="onRefresh" />
<ApplyEntryModel @reload="onRefresh" />
<ApplyTransferModel @reload="onRefresh" />
<ApplyResignModel @reload="onRefresh" />
</Page>
</template>
......@@ -7,7 +7,7 @@ import { useWarmflowIframe } from './hook';
defineOptions({ name: 'FlowPreview' });
const props = defineProps<{ instanceId: string }>();
const props = defineProps<{ instanceId: number }>();
const { clientId } = useAppConfig(import.meta.env, import.meta.env.PROD);
......
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { EmployeeFlowApi } from '#/api/hr/employeeFlow';
import { DictEnum } from '@vben/constants';
import { getTagDicts } from '#/utils/dict';
export const flowTypeOptions = [
{ label: '入职', value: '1' },
{ label: '离职', value: '2' },
{ label: '调配', value: '3' },
];
export const querySchema: VbenFormSchema[] = [
{
component: 'Input',
fieldName: 'applyCode',
label: '申请编号',
},
{
component: 'Select',
componentProps: {
options: flowTypeOptions,
},
fieldName: 'flowType',
label: '审批类型',
},
// {
// component: 'DatePicker',
// fieldName: 'startDate',
// label: '开始时间',
// },
// {
// component: 'DatePicker',
// fieldName: 'endDate',
// label: '结束时间',
// },
];
export function useColumns(): VxeTableGridOptions<EmployeeFlowApi.EmployeeFlow>['columns'] {
// onActionClick: OnActionClickFn<EmployeeFlowApi.EmployeeFlow>,
return [
{
title: '申请编号',
field: 'applyCode',
},
{
title: '审批类型',
field: 'flowType',
cellRender: { name: 'CellTag', options: flowTypeOptions },
},
// {
// title: '员工信息ID',
// field: 'employeeId',
// },
{
title: '开始时间',
field: 'startDate',
formatter: 'formatDateTime',
},
{
title: '结束时间',
field: 'endDate',
formatter: 'formatDateTime',
},
{
title: '状态',
field: 'status',
cellRender: {
name: 'CellTag',
options: [getTagDicts(DictEnum.WF_BUSINESS_STATUS)],
},
},
{
title: '备注',
field: 'remark',
},
{
align: 'right',
slots: { default: 'action' },
// cellRender: {
// attrs: {
// nameField: 'name',
// nameTitle: '人事审批对象',
// onClick: onActionClick,
// },
// name: 'CellOperation',
// options: [
// {
// code: 'edit',
// accessCode: ['employee:flow:edit'],
// }, // 默认的编辑按钮
// {
// code: 'delete',
// accessCode: ['employee:flow:remove'],
// }, // 默认的删除按钮
// ],
// },
field: 'action',
fixed: 'right',
headerAlign: 'center',
resizable: false,
showOverflow: false,
title: '操作',
width: 'auto',
},
];
}
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { EmployeeFlowApi } from '#/api/hr/employeeFlow';
import { computed, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { apiAdd, apiUpdate } from '#/api/hr/employeeFlow';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
const emit = defineEmits<{
success: [];
}>();
const formData = ref<EmployeeFlowApi.EmployeeFlow>();
const formSchema: VbenFormSchema[] = [
{
component: 'Input',
fieldName: 'applyCode',
label: '申请编号',
rules: 'required',
},
{
component: 'Select',
componentProps: {},
fieldName: 'flowType',
label: '审批类型',
rules: 'selectRequired',
},
{
component: 'Input',
fieldName: 'employeeId',
label: '员工信息ID',
rules: 'required',
},
{
component: 'DatePicker',
fieldName: 'startDate',
label: '开始时间',
rules: 'required',
},
{
component: 'DatePicker',
fieldName: 'endDate',
label: '结束时间',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
optionType: 'button',
},
fieldName: 'status',
label: '状态',
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'remark',
label: '备注',
rules: 'required',
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 90,
},
schema: formSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [Drawer, drawerApi] = useVbenDrawer({
onBeforeClose,
onClosed: handleClosed,
onConfirm: onSubmit,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
const data = drawerApi.getData<EmployeeFlowApi.EmployeeFlow>();
if (data) {
formData.value = data;
await formApi.setValues(formData.value);
} else {
formApi.resetForm();
}
await markInitialized();
},
});
async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
drawerApi.lock();
const data = await formApi.getValues<EmployeeFlowApi.EmployeeFlow>();
try {
await (formData.value?.id
? apiUpdate({ id: formData.value.id, ...data })
: apiAdd(data));
resetInitialized();
emit('success');
drawerApi.close();
} finally {
drawerApi.unlock();
}
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
const getDrawerTitle = computed(() =>
formData.value?.id ? '修改人事审批对象' : '新增人事审批对象',
);
</script>
<template>
<Drawer class="w-full max-w-[800px]" :title="getDrawerTitle">
<BasicForm class="mx-4" />
</Drawer>
</template>
<script setup lang="ts">
import type { EmployeeFlowApi } from '#/api/hr/employeeFlow';
import { computed, onMounted, shallowRef } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import {
Button,
Descriptions,
DescriptionsItem,
Skeleton,
} from 'ant-design-vue';
import { apiDetail } from '#/api/hr/employeeFlow';
import employeeDetailDrawer from '#/views/hr/employeeInfo/employee-detail-drawer.vue';
import { flowTypeOptions } from './data';
defineOptions({
name: 'HrDescription',
inheritAttrs: false,
});
const props = defineProps<{ businessId: number | string }>();
const data = shallowRef<EmployeeFlowApi.EmployeeFlowVo>();
onMounted(async () => {
const resp = await apiDetail(props.businessId);
data.value = resp;
});
const [EmployeeDetailDrawer, employeeDetailDrawerApi] = useVbenDrawer({
connectedComponent: employeeDetailDrawer,
});
const flowType = computed(() => {
return (
flowTypeOptions.find((item) => item.value === data.value?.flowType)
?.label ?? '未知'
);
});
function showEmployeeDetail() {
// formDrawerApi.setData({ id: row.id }).open();
employeeDetailDrawerApi.setData({ id: data.value?.employeeId }).open();
}
// function formatDate(date: string) {
// return dayjs(date).format('YYYY-MM-DD');
// }
</script>
<template>
<div class="rounded-[6px] border p-2">
<EmployeeDetailDrawer />
<Descriptions
v-if="data"
:column="1"
bordered
size="middle"
:label-style="{ width: '150px' }"
>
<DescriptionsItem label="审批类型">
{{ flowType }}
</DescriptionsItem>
<!-- <DescriptionsItem label="请假时间">
{{ formatDate(data.startDate) }} - {{ formatDate(data.endDate) }}
</DescriptionsItem> -->
<DescriptionsItem label="员工信息详情">
<Button type="link" @click="showEmployeeDetail()">
点击查看员工信息
</Button>
</DescriptionsItem>
<template v-if="data.auditLogList">
<DescriptionsItem
v-for="(auditLog, index) in data.auditLogList"
:label="auditLog.auditFieldName"
:key="index"
>
{{ auditLog.afterVal }}
</DescriptionsItem>
</template>
<DescriptionsItem label="内容">
{{ data.remark || '无' }}
</DescriptionsItem>
</Descriptions>
<Skeleton active v-else />
</div>
</template>
<script lang="ts" setup>
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { EmployeeFlowApi } from '#/api/hr/employeeFlow';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { getVxePopupContainer } from '@vben/utils';
import { Button, message, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { apiDelete, apiExport, apiPage } from '#/api/hr/employeeFlow';
import { cancelProcessApply } from '#/api/workflow/instance/index';
import { commonDownloadExcel } from '#/utils/file/download';
import { applyModal, flowInfoModal } from '../components';
import { querySchema, useColumns } from './data';
import Form from './form.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema,
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const [ApplyModal, applyModalApi] = useVbenModal({
connectedComponent: applyModal,
});
const [FlowInfoModal, flowInfoModalApi] = useVbenModal({
connectedComponent: flowInfoModal,
});
const [FormDrawer, formDrawerApi] = useVbenDrawer({
connectedComponent: Form,
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions: {
columns: useColumns(),
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: true,
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await apiPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
// 高亮当前行
isCurrent: true,
},
} as VxeTableGridOptions,
gridEvents: {
cellClick: ({ row, column }) => {
// 草稿状态 不做处理
// 操作列 不做处理
if (row.status === 'draft' || column.field === 'action') {
return;
}
// 查看详情
handleInfo(row);
},
},
});
function onRefresh() {
gridApi.query();
}
function onEdit(row: EmployeeFlowApi.EmployeeFlow) {
formDrawerApi.setData(row).open();
}
function onCreate() {
formDrawerApi.setData({}).open();
}
function onDelete(row: EmployeeFlowApi.EmployeeFlow) {
const hideLoading = message.loading({
content: `正在删除${row.applyCode}...`,
duration: 0,
key: 'action_process_msg',
});
apiDelete(row.id || 0)
.then(() => {
message.success({
content: `${row.applyCode}删除成功`,
key: 'action_process_msg',
});
onRefresh();
})
.catch(() => {
hideLoading();
});
}
function handleDownloadExcel() {
commonDownloadExcel(apiExport, '人事审批列表', gridApi.formApi.form.values);
}
async function handleRevoke(row: Required<EmployeeFlowApi.EmployeeFlow>) {
await cancelProcessApply({
businessId: row.id,
message: '申请人撤销流程!',
});
await gridApi.query();
}
async function handleCompleteOrCancel() {
formDrawerApi.close();
gridApi.query();
}
function handleInfo(row: Required<EmployeeFlowApi.EmployeeFlow>) {
flowInfoModalApi.setData({ businessId: row.id });
flowInfoModalApi.open();
}
</script>
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<Grid table-title="人事审批列表">
<template #toolbar-tools>
<Space>
<Button
v-access:code="['employee:flow:export']"
@click="handleDownloadExcel"
>
导出
</Button>
<Button
v-access:code="['employee:flow:add']"
type="primary"
@click="onCreate"
>
<Plus class="size-5" />
新增
</Button>
</Space>
</template>
<template #action="{ row }">
<Button
size="small"
type="link"
:disabled="!['draft', 'cancel', 'back'].includes(row.status)"
v-access:code="['employee:flow:edit']"
@click.stop="onEdit(row)"
>
修改
</Button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认撤销?"
:disabled="!['waiting'].includes(row.status)"
@confirm.stop="handleRevoke(row)"
@cancel.stop=""
>
<Button
size="small"
type="link"
:disabled="!['waiting'].includes(row.status)"
v-access:code="['employee:flow:edit']"
@click.stop=""
>
撤销
</Button>
</Popconfirm>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
:disabled="!['draft', 'cancel', 'back'].includes(row.status)"
@confirm.stop="onDelete(row)"
@cancel.stop=""
>
<Button
size="small"
type="link"
:disabled="!['draft', 'cancel', 'back'].includes(row.status)"
danger
v-access:code="['employee:flow:remove']"
@click.stop=""
>
删除
</Button>
</Popconfirm>
</template>
</Grid>
<FlowInfoModal />
<ApplyModal
@complete="handleCompleteOrCancel"
@cancel="handleCompleteOrCancel"
/>
</Page>
</template>
......@@ -45,6 +45,7 @@ async function loadTree() {
const treeData = await categoryTree();
categoryTreeArray.value = treeData;
console.log('[categoryTreeArray]', categoryTreeArray);
showTreeSkeleton.value = false;
}
......
......@@ -8,6 +8,10 @@ const LeaveDescription = defineAsyncComponent(
() => import('#/views/workflow/leave/leave-description.vue'),
);
const EmployeeDescription = defineAsyncComponent(
() => import('#/views/workflow/hrFlow/hr-description.vue'),
);
/**
* key为流程的路径(task.formPath) value为要显示的组件
*/
......@@ -16,6 +20,7 @@ export const flowComponentsMap = {
* 请假申请 详情
*/
'/workflow/leaveEdit/index': markRaw(LeaveDescription),
'/hr/employee/flow': markRaw(EmployeeDescription),
};
export type FlowComponentsMapMapKey = keyof typeof flowComponentsMap;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment