Commit 3dad53ac authored by 刘斌's avatar 刘斌

fix: 修改家庭成员3

parent 645fabe9
......@@ -393,6 +393,11 @@ export namespace EmployeeInfoApi {
*/
antaiCommunityInfo?: string;
/**
* 员工家庭成员
*/
familyMembers?: EmployeeFamilyMembersVo[];
/**
* 离职类型
*/
......@@ -620,7 +625,6 @@ export namespace EmployeeInfoApi {
/**
* 审核记录
*/
export interface AuditLogBo {
/**
* 字段名称
......@@ -638,6 +642,38 @@ export interface AuditLogBo {
afterVal: string;
}
// export interface EmployeeResign extends EmployeeInfoApi.Employee {
// }
export interface EmployeeFamilyMembersVo {
/**
* 序号
*/
id?: number;
/**
* 员工信息ID
*/
employeeId?: number;
/**
* 关系
*/
relation?: string;
/**
* 姓名
*/
name?: string;
/**
* 出生年月
*/
birthDate?: string;
/**
* 工作单位及职务
*/
companyAndJob?: string;
/**
* 联系方式
*/
contact?: string;
/**
* 是否为紧急联系人
*/
emergencyFlag?: string;
emergencyFlagName?: string;
}
......@@ -329,7 +329,7 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '出生日期',
field: 'birthDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......@@ -420,13 +420,13 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '参加工作时间',
field: 'workStartDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
title: '入职时间',
field: 'entryDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......@@ -457,7 +457,7 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '全日制毕业日期',
field: 'fulltimeGraduationDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......@@ -483,7 +483,7 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '非全日制毕业日期',
field: 'nonFulltimeGraduationDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......@@ -539,19 +539,19 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '劳动合同开始时间',
field: 'contractStartDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
title: '劳动合同截止时间',
field: 'contractEndDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
title: '合同到期提醒',
field: 'contractExpirationReminder',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......@@ -597,13 +597,13 @@ export function useColumns(): VxeTableGridOptions<EmployeeInfoApi.Employee>['col
{
title: '转正时间',
field: 'regularizationDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
title: '预计转正时间',
field: 'expectedRegularDate',
formatter: 'formatDate',
// formatter: 'formatDate',
width: 100,
},
{
......
......@@ -65,7 +65,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
......@@ -127,7 +127,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
......@@ -189,10 +189,10 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="紧急联系人电话">
{{ currentEmployee.emergencyContactPhone }}
</DescriptionsItem>
<DescriptionsItem label="家庭地址">
<DescriptionsItem label="家庭地址" :span="2">
{{ currentEmployee.homeAddress }}
</DescriptionsItem>
<DescriptionsItem label="户口所在地">
<DescriptionsItem label="户口所在地" :span="2">
{{ currentEmployee.householdRegistrationAddress }}
</DescriptionsItem>
</Descriptions>
......@@ -202,7 +202,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
......@@ -252,7 +252,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
......@@ -286,13 +286,13 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="职称情况">
{{ currentEmployee.professionalTitle }}
</DescriptionsItem>
<DescriptionsItem label="证书情况">
<DescriptionsItem label="证书情况" :span="2">
{{ currentEmployee.certificateStatus }}
</DescriptionsItem>
<DescriptionsItem label="外部个人履历">
<DescriptionsItem label="外部个人履历" :span="2">
{{ currentEmployee.externalResume }}
</DescriptionsItem>
<DescriptionsItem label="内部个人履历">
<DescriptionsItem label="内部个人履历" :span="2">
{{ currentEmployee.internalResume }}
</DescriptionsItem>
</Descriptions>
......@@ -302,7 +302,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '150px',
fontWeight: 500,
......@@ -330,9 +330,6 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="合同主体">
{{ currentEmployee.contractEntity }}
</DescriptionsItem>
<DescriptionsItem label="劳动合同签订情况">
{{ currentEmployee.contractSigningStatus }}
</DescriptionsItem>
<DescriptionsItem label="社保主体">
{{ currentEmployee.socialSecurityEntity }}
</DescriptionsItem>
......@@ -351,6 +348,9 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="试用期">
{{ currentEmployee.probationPeriod }}
</DescriptionsItem>
<DescriptionsItem label="劳动合同签订情况" :span="2">
{{ currentEmployee.contractSigningStatus }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
......@@ -358,7 +358,7 @@ async function handleOpenChange(open: boolean) {
size="small"
bordered
:column="2"
class="mb-1"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
......@@ -429,6 +429,46 @@ async function handleOpenChange(open: boolean) {
{{ currentEmployee.remarks }}
</DescriptionsItem>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="currentEmployee && currentEmployee.familyMembers"
size="small"
layout="vertical"
bordered
:column="6"
class="mb-2"
:label-style="{
width: '140px',
fontWeight: 500,
}"
>
<template #title>
<div class="text-center">家庭成员信息</div>
</template>
<template
v-for="member in currentEmployee.familyMembers"
:key="member.id"
>
<DescriptionsItem label="关系">
{{ member.relation }}
</DescriptionsItem>
<DescriptionsItem label="姓名">
{{ member.name }}
</DescriptionsItem>
<DescriptionsItem label="出生年月">
{{ member.birthDate }}
</DescriptionsItem>
<DescriptionsItem label="工作单位及职务">
{{ member.companyAndJob }}
</DescriptionsItem>
<DescriptionsItem label="联系方式">
{{ member.contact }}
</DescriptionsItem>
<DescriptionsItem label="是否为紧急联系人">
{{ member.emergencyFlagName }}
</DescriptionsItem>
</template>
</Descriptions>
<Descriptions
v-show="!loading"
v-if="isResign && currentEmployee"
......@@ -460,3 +500,9 @@ async function handleOpenChange(open: boolean) {
</Descriptions>
</BasicDrawer>
</template>
<style lang="scss" scoped>
:deep(.ant-descriptions-item-label) {
background-color: #75e0e0 !important;
}
</style>
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { EmployeeFamilyMembersVo } from '#/api/hr/employeeModel';
import { nextTick, ref, watch } from 'vue';
import { cloneDeep } from '@vben/utils';
import { Button, Space } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
defineOptions({ name: 'FamilyMembersTable', inheritAttrs: false });
const props = withDefaults(
defineProps<{
modelValue?: EmployeeFamilyMembersVo[];
}>(),
{
modelValue: () => [],
},
);
const emit = defineEmits<{
change: [EmployeeFamilyMembersVo[]];
'update:modelValue': [EmployeeFamilyMembersVo[]];
}>();
type FamilyRow = EmployeeFamilyMembersVo & { _rid: number };
const relationOptions = [
{ label: '父亲', value: '父亲' },
{ label: '母亲', value: '母亲' },
{ label: '夫妻', value: '夫妻' },
{ label: '儿子', value: '儿子' },
{ label: '女儿', value: '女儿' },
{ label: '哥哥', value: '哥哥' },
{ label: '弟弟', value: '弟弟' },
{ label: '姐姐', value: '姐姐' },
{ label: '妹妹', value: '妹妹' },
];
// const yesNoTagOptions = [
// { color: 'success', label: '是', value: 'Y' },
// { color: 'default', label: '否', value: 'N' },
// ];
const tableData = ref<FamilyRow[]>(
props.modelValue.map((r) => ({ ...r, _rid: Date.now() + Math.random() })),
);
let ridSeed = Date.now();
const skipReload = ref(false);
watch(
() => props.modelValue,
(val) => {
if (skipReload.value) return;
const rows = (val || []).map((r) => ({ ...r, _rid: ++ridSeed }));
tableData.value = rows;
reloadGridAndSyncRadio();
},
);
function emitChange() {
skipReload.value = true;
const sanitized = tableData.value.map(({ _rid, ...rest }) => rest);
emit('update:modelValue', sanitized as EmployeeFamilyMembersVo[]);
emit('change', sanitized as EmployeeFamilyMembersVo[]);
nextTick(() => {
skipReload.value = false;
});
}
function createDefaultRow(): FamilyRow {
ridSeed += 1;
return {
// 新增行不带后端ID,后端根据是否存在ID判断新增/编辑
id: undefined as unknown as number,
relation: '父亲',
name: '',
birthDate: '',
companyAndJob: '',
contact: '',
emergencyFlag: 'N',
_rid: ridSeed,
} as FamilyRow;
}
function reloadGridAndSyncRadio() {
gridApi.grid?.loadData(tableData.value);
if (tableData.value && tableData.value.length > 0) {
const emergency = tableData.value.find((r) => r.emergencyFlag === 'Y');
if (emergency) {
nextTick(() => gridApi.grid?.setRadioRow(emergency));
} else {
nextTick(() => gridApi.grid?.clearRadioRow());
}
}
}
const gridOptions: VxeGridProps<FamilyRow> = {
columns: [
{ title: '序号', type: 'seq', width: 60 },
{
type: 'radio',
width: 110,
title: '是否紧急联系人',
},
{
editRender: { name: 'select', options: relationOptions },
field: 'relation',
title: '关系',
minWidth: 120,
},
{
editRender: { name: 'input' },
field: 'name',
title: '姓名',
minWidth: 140,
},
{
editRender: {
name: 'input',
props: { placeholder: 'YYYY-MM-DD' },
},
field: 'birthDate',
title: '出生日期',
minWidth: 140,
},
{
editRender: { name: 'input' },
field: 'companyAndJob',
title: '工作单位职务',
minWidth: 200,
},
{
editRender: {
name: 'input',
props: { placeholder: '手机或座机' },
},
field: 'contact',
title: '联系方式',
minWidth: 160,
},
// {
// field: 'emergencyFlag',
// title: '是否紧急联系人',
// cellRender: { name: 'CellTag', options: yesNoTagOptions },
// minWidth: 140,
// },
{ slots: { default: 'action' }, title: '操作', width: 120 },
],
data: tableData.value,
editConfig: { mode: 'row', trigger: 'click' },
// editRules: {
// name: [{ required: true, message: '姓名不能为空' }],
// relation: [{ required: true, message: '关系不能为空' }],
// contact: [
// {
// validator({ cellValue }) {
// if (isValidContact(cellValue)) {
// return new Error('联系方式格式不正确');
// }
// },
// },
// ],
// },
height: 'auto',
keepSource: true,
radioConfig: { strict: true },
rowConfig: { keyField: '_rid' },
pagerConfig: { enabled: false },
toolbarConfig: { enabled: true, refresh: false, custom: false, zoom: false },
showOverflow: true,
};
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions,
gridEvents: {
radioChange: onRadioChange,
editClosed: onEditClosed,
},
});
function onRadioChange() {
const record = gridApi.grid.getRadioRecord();
const rid = record?._rid;
tableData.value.forEach((r) => {
r.emergencyFlag = r._rid === rid ? 'Y' : 'N';
});
emitChange();
}
// function isValidContact(value: string) {
// const mobile = /^1[3-9]\d{9}$/;
// const landline = /^0\d{2,3}-\d{7,8}$/;
// return mobile.test(value) || landline.test(value);
// }
async function onEditClosed() {
// const row = params?.row;
// if (row) {
// const errors: string[] = [];
// if (!row.relation) errors.push('请填写关系');
// if (!row.name) errors.push('请填写姓名');
// if (row.contact && !isValidContact(row.contact))
// errors.push('联系方式格式不正确');
// if (errors.length > 0) {
// message.warning(errors.join('、'));
// nextTick(() => gridApi.grid?.setEditRow(row));
// return;
// }
// }
// const errMap = await gridApi.grid?.validate();
// if (errMap) {
// message.warning('校验不通过');
// }
tableData.value = await gridApi.grid.getFullData();
emitChange();
}
async function handleAdd() {
await gridApi.grid?.clearEdit();
const row = createDefaultRow();
tableData.value.push(row);
gridApi.grid?.loadData(tableData.value);
nextTick(() => gridApi.grid?.setEditRow(row));
emitChange();
}
function handleDelete(row: FamilyRow) {
tableData.value = tableData.value.filter((r) => r._rid !== row._rid);
gridApi.grid?.loadData(tableData.value);
emitChange();
}
defineExpose({
getData: () => {
const sanitized = tableData.value.map(({ _rid, ...rest }) => rest);
return cloneDeep(sanitized) as EmployeeFamilyMembersVo[];
},
setData: (rows: EmployeeFamilyMembersVo[]) => {
const mapped = (rows || []).map((r) => ({ ...r, _rid: ++ridSeed }));
tableData.value = mapped;
reloadGridAndSyncRadio();
emitChange();
},
resetData: () => {
tableData.value = [];
reloadGridAndSyncRadio();
emitChange();
},
});
</script>
<template>
<div class="flex h-full flex-col" id="family-members-table">
<Grid table-title="家庭成员信息">
<template #toolbar-tools>
<Space>
<Button type="primary" @click="handleAdd">新增成员</Button>
</Space>
</template>
<template #action="{ row }">
<Button danger @click.stop="handleDelete(row)">删除</Button>
</template>
</Grid>
</div>
</template>
......@@ -18,6 +18,7 @@ import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { idCardSchema } from '../../../utils/validator';
import { HrDictEnum } from '../dict-enum';
import FamilyMembersTable from './familyMembersTable.vue';
const emit = defineEmits<{
success: [];
......@@ -102,10 +103,12 @@ const formSchema: VbenFormSchema[] = [
// rules: 'required',
},
{
component: 'Select',
component: 'RadioGroup',
componentProps: {
getVxePopupContainer,
buttonStyle: 'solid',
optionType: 'button',
options: getDictOptions(HrDictEnum.HR_POSITION_TYPE),
getVxePopupContainer,
},
fieldName: 'positionType',
label: '岗位类型',
......@@ -125,10 +128,12 @@ const formSchema: VbenFormSchema[] = [
// rules: 'required',
},
{
component: 'Select',
component: 'RadioGroup',
componentProps: {
getVxePopupContainer,
buttonStyle: 'solid',
optionType: 'button',
options: getDictOptions(HrDictEnum.HR_POSITION_TYPE),
getVxePopupContainer,
},
fieldName: 'concurrentPositionType',
label: '兼岗岗位类型',
......@@ -204,6 +209,12 @@ const formSchema: VbenFormSchema[] = [
label: '年龄',
rules: z.number().gt(0, '年龄不能小于0').lt(100, '年龄不能大于100'),
},
{
component: 'Input',
fieldName: 'homeAddress',
label: '家庭地址',
rules: 'required',
},
// {
// component: 'Input',
// fieldName: 'ageGroup',
......@@ -244,6 +255,12 @@ const formSchema: VbenFormSchema[] = [
label: '政治面貌',
// rules: 'selectRequired',
},
{
component: 'Input',
fieldName: 'householdRegistrationAddress',
label: '户口所在地',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'emergencyContact',
......@@ -260,18 +277,6 @@ const formSchema: VbenFormSchema[] = [
.optional()
.or(z.literal('')),
},
{
component: 'Input',
fieldName: 'homeAddress',
label: '家庭地址',
rules: 'required',
},
{
component: 'Input',
fieldName: 'householdRegistrationAddress',
label: '户口所在地',
// rules: 'required',
},
{
component: 'Divider',
componentProps: {
......@@ -292,49 +297,43 @@ const formSchema: VbenFormSchema[] = [
},
{
component: 'Input',
fieldName: 'fulltimeSchool',
label: '全日制毕业院校',
fieldName: 'nonFulltimeEducation',
label: '非全日制学历',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'fulltimeMajor',
label: '全日制专业',
// rules: 'required',
},
{
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
getVxePopupContainer,
},
fieldName: 'fulltimeGraduationDate',
label: '全日制毕业日期',
fieldName: 'fulltimeSchool',
label: '全日制毕业院校',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'fulltimeDegree',
label: '全日制学位',
fieldName: 'nonFulltimeSchool',
label: '非全日制毕业院校',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'nonFulltimeEducation',
label: '非全日制学历',
fieldName: 'fulltimeMajor',
label: '全日制专业',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'nonFulltimeSchool',
label: '非全日制毕业院校',
fieldName: 'nonFulltimeMajor',
label: '非全日制专业',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'nonFulltimeMajor',
label: '非全日制专业',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
getVxePopupContainer,
},
fieldName: 'fulltimeGraduationDate',
label: '全日制毕业日期',
// rules: 'required',
},
{
......@@ -348,6 +347,12 @@ const formSchema: VbenFormSchema[] = [
label: '非全日制毕业日期',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'fulltimeDegree',
label: '全日制学位',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'nonFulltimeDegree',
......@@ -424,9 +429,12 @@ const formSchema: VbenFormSchema[] = [
// rules: 'required',
// },
{
component: 'Select',
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
optionType: 'button',
options: getDictOptions(HrDictEnum.HR_EMPLOYEE_TYPE),
getVxePopupContainer,
},
fieldName: 'employeeType',
label: '员工类型',
......@@ -633,13 +641,6 @@ const formSchema: VbenFormSchema[] = [
label: '处罚情况',
// rules: 'required',
},
{
component: 'Textarea',
fieldName: 'remarks',
formItemClass: 'col-span-2',
label: '备注',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'officePhone',
......@@ -760,6 +761,34 @@ const formSchema: VbenFormSchema[] = [
label: '小区具体信息',
// rules: 'required',
},
{
component: 'Textarea',
fieldName: 'remarks',
formItemClass: 'col-span-2',
label: '备注',
// rules: 'required',
},
{
component: 'Divider',
componentProps: {
orientation: 'center',
},
fieldName: 'divider1',
formItemClass: 'col-span-2',
hideLabel: true,
renderComponentContent: () => ({
default: () => '家庭成员信息',
}),
},
{
component: 'Input',
defaultValue: [],
fieldName: 'familyMembers',
formItemClass: 'col-span-2',
labelWidth: 0,
// label: '家庭成员',
// rules: 'required',
},
// {
// component: 'DatePicker',
// fieldName: 'resignationDate',
......@@ -788,6 +817,8 @@ const [BasicForm, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2',
});
const familyMembersRef = ref<InstanceType<typeof FamilyMembersTable>>();
async function setupDeptSelect() {
const deptArray = await treeList();
// const deptTree = genDeptTree(deptArray);
......@@ -886,9 +917,11 @@ const [Drawer, drawerApi] = useVbenDrawer({
const data = await apiDetail(id);
data.ossId = data.ossId ? `${data.ossId}` : data.ossId;
await formApi.setValues(data);
familyMembersRef.value?.setData(data.familyMembers || []);
// await setupDeptLevel2Options(data.firstLevelDepartment);
} else {
formApi.resetForm();
await formApi.resetForm();
familyMembersRef.value?.resetData();
}
await markInitialized();
drawerApi.drawerLoading(false);
......@@ -915,8 +948,9 @@ async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
drawerApi.lock();
const familyMembers = familyMembersRef.value?.getData() ?? [];
const data = await formApi.getValues<EmployeeInfoApi.Employee>();
// console.log('[data]', data);
data.familyMembers = familyMembers;
try {
await (isUpdate.value ? apiUpdate(data) : apiAdd(data));
resetInitialized();
......@@ -940,6 +974,13 @@ const getDrawerTitle = computed(() =>
<template>
<Drawer class="w-full max-w-[900px]" :title="getDrawerTitle">
<Skeleton v-if="loading" active />
<BasicForm v-show="!loading" class="mx-4" />
<BasicForm v-show="!loading" class="mx-4">
<template #familyMembers>
<div class="h-[300px] w-full">
<!-- association为readonly 不能通过v-model绑定 -->
<FamilyMembersTable ref="familyMembersRef" />
</div>
</template>
</BasicForm>
</Drawer>
</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