Commit bf1ac72a authored by 刘斌's avatar 刘斌

feat: 增加人事管理-预览

parent 3ed4afd8
import type { BaseModel, PageQuery } from '#/api/baseModel';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
export namespace EmployeeInfoApi {
export interface Employee extends BaseModel {
/**
* 板块
*/
plate?: string;
/**
* 项目
*/
project?: string;
/**
* 一级部门
*/
firstLevelDepartment?: string;
/**
* 二级部门
*/
secondLevelDepartment?: string;
/**
* 工号
*/
employeeId?: number;
/**
* 职级
*/
jobLevel?: string;
/**
* 岗位
*/
position?: string;
/**
* 职务
*/
post?: string;
/**
* 姓名
*/
name?: string;
/**
* 性别
*/
gender?: string;
/**
* 身份证号码
*/
idCardNumber?: number;
/**
* 出生日期
*/
birthDate?: string;
/**
* 年龄
*/
age?: number;
/**
* 年龄段
*/
ageGroup?: string;
/**
* 籍贯
*/
nativePlace?: string;
/**
* 民族
*/
ethnicity?: string;
/**
* 婚姻状况
*/
maritalStatus?: string;
/**
* 政治面貌
*/
politicalStatus?: string;
/**
* 手机号码
*/
phoneNumber?: string;
/**
* 紧急联系人
*/
emergencyContact?: string;
/**
* 紧急联系人电话
*/
emergencyContactPhone?: string;
/**
* 家庭地址
*/
homeAddress?: string;
/**
* 户口所在地
*/
householdRegistrationAddress?: string;
/**
* 参加工作时间
*/
workStartDate?: string;
/**
* 入职时间
*/
entryDate?: string;
/**
* 工龄
*/
yearsOfService?: number;
/**
* 工龄段
*/
yearsOfServiceSegment?: string;
/**
* 学历
*/
education?: string;
/**
* 学位
*/
degree?: string;
/**
* 毕业时间
*/
graduationDate?: string;
/**
* 专业
*/
major?: string;
/**
* 毕业院校
*/
graduateSchool?: string;
/**
* 员工类型
*/
employeeType?: string;
/**
* 职称情况
*/
professionalTitle?: string;
/**
* 简历
*/
resume?: string;
/**
* 用工形式
*/
employmentForm?: string;
/**
* 劳动合同期限
*/
contractTerm?: string;
/**
* 劳动合同开始时间
*/
contractStartDate?: string;
/**
* 劳动合同截止时间
*/
contractEndDate?: string;
/**
* 合同到期提醒
*/
contractExpirationReminder?: string;
/**
* 劳动合同签订情况
*/
contractSigningStatus?: string;
/**
* 合同主体
*/
contractEntity?: string;
/**
* 转正时间
*/
regularizationDate?: string;
/**
* 异动情况
*/
transferStatus?: string;
/**
* 奖惩情况
*/
rewardPunishmentStatus?: string;
/**
* 备注
*/
remarks?: string;
/**
* 离职时间
*/
resignationDate?: string;
/**
* 离职原因
*/
resignationReason?: string;
}
}
/**
* 查询员工信息列表
* @param params
* @returns {*} page
*/
export function apiPage(params: PageQuery) {
return requestClient.get('/employee/info/page', { params });
}
/**
* 查询员工信息详细
* @param id
*/
export function apiDetail(id: number) {
return requestClient.get(`/employee/info/${id}`);
}
/**
* 新增员工信息
* @param data
*/
export function apiAdd(data: EmployeeInfoApi.Employee) {
return requestClient.post('/employee/info', data);
}
/**
* 修改员工信息
* @param data
*/
export function apiUpdate(data: EmployeeInfoApi.Employee) {
return requestClient.put('/employee/info', data);
}
/**
* 删除员工信息
* @param id
*/
export function apiDelete(id: Array<number> | number) {
return requestClient.delete(`/employee/info/${id}`);
}
/**
* 导出员工信息
* @param params
*/
export function apiExport(id: number) {
return commonExport(`/employee/info/export/${id}`, {});
}
/**
* 下载用户导入模板
* @returns blob
*/
export function downloadImportTemplate() {
return commonExport('/employee/info/importTemplate', {});
}
/**
* 从excel导入用户
* @param file
* @returns void
*/
export function employeeImportData(file: Blob) {
return requestClient.post<{ errMessage: string; success: boolean }>(
'/employee/info/excel/import',
{ file },
{
headers: {
'Content-Type': 'multipart/form-data;charset=UTF-8',
},
isTransformResponse: false,
responseReturn: 'body',
},
);
}
......@@ -79,7 +79,7 @@ function hasPermission(menuSet: Set<string>, route: RouteRecordRaw) {
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
console.warn('[pageMap]', pageMap);
// console.warn('[pageMap]', pageMap);
const layoutMap: ComponentRecordType = {
BasicLayout,
......
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
// 人事管理
{
path: '/hr',
name: 'HrManagement',
meta: {
title: '人事管理',
keepAlive: true,
icon: 'streamline-ultimate:human-resources-hierarchy-1',
permission: ['dashboard'],
},
children: [
{
path: '/hr/employee/list',
name: 'EmployeeInfoList',
component: () => import('#/views/hr/employeeInfo/list.vue'),
meta: {
title: '员工信息列表',
icon: 'clarity:employee-line',
keepAlive: true,
permission: ['dashboard'],
componentPath: '#/views/hr/employeeInfo/list.vue',
},
},
],
},
];
export default routes;
export const HrDictEnum = {
HR_USER_SEX: 'hr_user_sex',
HR_AGE_GROUP: 'hr_age_group',
HR_MARITAL_STATUS: 'hr_marital_status', // 设备类型
HR_POLITICAL_STATUS: 'hr_political_status', // 授权类型
HR_YEARS_SERVICE_SEGMENT: 'hr_years_service_segment',
HR_EDUCATION: 'hr_education', // 通知状态
HR_EMPLOYEE_TYPE: 'hr_employee_type', // 通知类型
HR_EMPLOYMENT_FORM: 'hr_employment_form', // 操作类型
// SYS_JOB_TYPE: 'sys_job_type', // 定时任务类型
// SYS_JOB_STATUS: 'sys_job_status', // 定时任务状态
// SYS_OSS_ACCESS_POLICY: 'oss_access_policy', // oss权限桶类型
// SYS_SHOW_HIDE: 'sys_show_hide', // 显示状态
// SYS_USER_SEX: 'sys_user_sex', // 性别
// SYS_YES_NO: 'sys_yes_no', // 是否
// WF_BUSINESS_STATUS: 'wf_business_status', // 业务状态
// WF_FORM_TYPE: 'wf_form_type', // 表单类型
// WF_TASK_STATUS: 'wf_task_status', // 任务状态
} as const;
export type HrDictEnumKey = keyof typeof HrDictEnum;
This diff is collapsed.
<script setup lang="ts">
import type { UploadFile } from 'ant-design-vue/es/upload/interface';
import { h, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ExcelIcon, InBoxIcon } from '@vben/icons';
import { Button, Modal, Upload } from 'ant-design-vue';
import {
downloadImportTemplate,
employeeImportData,
} from '#/api/hr/employeeInfo';
import { commonDownloadExcel } from '#/utils/file/download';
const emit = defineEmits<{ reload: [] }>();
const UploadDragger = Upload.Dragger;
const [BasicModal, modalApi] = useVbenModal({
onCancel: handleCancel,
onConfirm: handleSubmit,
});
const fileList = ref<UploadFile[]>([]);
// const checked = ref(false);
async function handleSubmit() {
try {
modalApi.modalLoading(true);
if (fileList.value.length !== 1) {
handleCancel();
return;
}
// const data = {
// file: fileList.value[0]!.originFileObj as Blob,
// updateSupport: unref(checked),
// };
const { success, errMessage } = await employeeImportData(
fileList.value[0]!.originFileObj as Blob,
);
let modal = Modal.success;
if (success) {
emit('reload');
} else {
modal = Modal.error;
}
handleCancel();
modal({
content: h('div', {
class: 'max-h-[260px] overflow-y-auto',
innerHTML: errMessage, // 后台已经处理xss问题
}),
title: '提示',
});
} catch (error) {
console.warn(error);
modalApi.close();
} finally {
modalApi.modalLoading(false);
}
}
function handleCancel() {
modalApi.close();
fileList.value = [];
// checked.value = false;
}
</script>
<template>
<BasicModal
:close-on-click-modal="false"
:fullscreen-button="false"
title="员工信息导入"
>
<!-- z-index不设置会遮挡模板下载loading -->
<!-- 手动处理 而不是放入文件就上传 -->
<UploadDragger
v-model:file-list="fileList"
:before-upload="() => false"
:max-count="1"
:show-upload-list="true"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
>
<p class="ant-upload-drag-icon flex items-center justify-center">
<InBoxIcon class="text-primary size-[48px]" />
</p>
<p class="ant-upload-text">点击或者拖拽到此处上传文件</p>
</UploadDragger>
<div class="mt-2 flex flex-col gap-2">
<div class="flex items-center gap-2">
<span>允许导入xlsx, xls文件</span>
<Button
type="link"
@click="
commonDownloadExcel(downloadImportTemplate, '员工信息导入模板')
"
>
<div class="flex items-center gap-[4px]">
<ExcelIcon />
<span>下载模板</span>
</div>
</Button>
</div>
<!-- <div class="flex items-center gap-2">
<span :class="{ 'text-red-500': checked }">
是否更新/覆盖已存在的用户数据
</span>
<Switch v-model:checked="checked" />
</div> -->
</div>
</BasicModal>
</template>
This diff is collapsed.
<script lang="ts" setup>
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
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 { useVbenVxeGrid } from '#/adapter/vxe-table';
import { apiDelete, apiExport, apiPage } from '#/api/hr/employeeInfo';
import { GhostButton } from '#/components/global/button';
import { commonDownloadExcel } from '#/utils/file/download';
import { querySchema, useColumns } from './data';
import employeeImportModal from './employee-import-modal.vue';
import Form from './form.vue';
/**
* 导入
*/
const [EmployeeImportModal, employeeImportModalApi] = useVbenModal({
connectedComponent: employeeImportModal,
});
function handleImport() {
employeeImportModalApi.open();
}
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',
// 日期选择格式化
fieldMappingTime: [
[
'entryDate',
['params[entryBeginTime]', 'params[entryEndTime]'],
['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
],
],
};
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,
});
// 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(row).open();
}
function onCreate() {
formDrawerApi.setData({}).open();
}
function onResign(row: EmployeeInfoApi.Employee) {
const hideLoading = message.loading({
content: `正在处理${row.name}的离职...`,
duration: 0,
key: 'action_process_msg',
});
apiDelete(row.id || 0)
.then(() => {
message.success({
content: `${row.name}离职申请成功`,
key: 'action_process_msg',
});
onRefresh();
})
.catch(() => {
hideLoading();
});
}
function handleDownloadExcel(row: EmployeeInfoApi.Employee) {
commonDownloadExcel(apiExport, '员工信息', row.id);
}
</script>
<template>
<Page auto-content-height>
<FormDrawer @success="onRefresh" />
<Grid table-title="员工信息列表">
<template #toolbar-tools>
<Space>
<Button
v-access:code="['employee:info:import']"
@click="handleImport"
>
导入
</Button>
<!-- <Button
v-access:code="['employee:info:export']"
@click="handleDownloadExcel"
>
导出
</Button> -->
<Button
v-access:code="['employee:info:add']"
type="primary"
@click="onCreate"
>
<Plus class="size-5" />
入职
</Button>
</Space>
</template>
<template #action="{ row }">
<!-- 租户管理员不可修改admin角色 防止误操作 -->
<!-- 超级管理员可通过租户切换来操作租户管理员角色 -->
<!-- -->
<!-- <template
v-if="!row.superAdmin && (row.roleKey !== 'admin' || isSuperAdmin)"
> -->
<Space>
<GhostButton
v-access:code="['employee:info:export']"
@click.stop="handleDownloadExcel(row)"
>
导出简历
</GhostButton>
<GhostButton
v-access:code="['employee:info:edit']"
@click.stop="onEdit(row)"
>
编辑
</GhostButton>
<!-- <GhostButton
v-access:code="['auth:role:edit']"
@click.stop="onAssignRole(row)"
>
分配
</GhostButton> -->
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
:title="`确认离职【${row.name}】?`"
@confirm="onResign(row)"
>
<GhostButton
danger
v-access:code="['employee:info:resign']"
@click.stop=""
>
离职
</GhostButton>
</Popconfirm>
</Space>
<!-- </template> -->
</template>
</Grid>
<EmployeeImportModal @reload="onRefresh" />
</Page>
</template>
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