- Added company nature selection to ManagementList and JobDetailPage. - Implemented detailed tags for job benefits and schedules in JobDetailPage and JobListPage. - Introduced CompanyInfoPage for managing company details, including nature, scale, and contacts. - Enhanced ManagementList to support salary composition, welfare benefits, and work schedule fields. - Created utility functions for parsing and serializing comma-separated tags. - Updated API services to fetch and update current company information. - Added tests for new utility functions to ensure correct parsing and serialization of tags.
487 lines
15 KiB
TypeScript
487 lines
15 KiB
TypeScript
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||
import { FormattedMessage, useAccess } from '@umijs/max';
|
||
import { Button, FormInstance, message, Modal, Tag } from 'antd';
|
||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||
import { DeleteOutlined, FormOutlined, PlusOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
|
||
import EditCompanyListRow, { CompanyDetailView } from './edit';
|
||
import {
|
||
addCmsCompanyList,
|
||
delCmsCompanyList,
|
||
exportCmsCompanyList,
|
||
getCmsCompanyList,
|
||
getCmsCompanyDetail,
|
||
parseCmsCompanyDetailResponse,
|
||
putCmsCompanyList,
|
||
} from '@/services/company/list';
|
||
import { getDictValueEnum } from '@/services/system/dict';
|
||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||
import DictTag, { DictValueEnumObj } from '@/components/DictTag';
|
||
import { postCompanyApproval } from '@/services/company/review';
|
||
|
||
const buildListSearchParams = (values: Record<string, any>): API.CompanyList.Params => {
|
||
const { dateRange, ...rest } = values || {};
|
||
const params = { ...rest } as API.CompanyList.Params;
|
||
if (dateRange?.[0] && dateRange?.[1]) {
|
||
params.startDate = dateRange[0];
|
||
params.endDate = dateRange[1];
|
||
}
|
||
return params;
|
||
};
|
||
|
||
const loadCompanyDetail = async (record: API.CompanyList.Company) => {
|
||
const resp = await getCmsCompanyDetail(record.companyId);
|
||
const detail = parseCmsCompanyDetailResponse(resp);
|
||
if (!detail) {
|
||
return { ...record };
|
||
}
|
||
return { ...record, ...detail };
|
||
};
|
||
|
||
/** 将行业树数据展平为 DictValueEnumObj */
|
||
const flattenIndustryTree = (tree: { id: number; label: string; children?: any[] }[]): DictValueEnumObj => {
|
||
const map: DictValueEnumObj = {};
|
||
const walk = (nodes: { id: number; label: string; children?: any[] }[]) => {
|
||
nodes?.forEach((node) => {
|
||
map[node.id] = {
|
||
text: node.label,
|
||
label: node.label,
|
||
value: node.id,
|
||
};
|
||
if (node.children?.length) {
|
||
walk(node.children);
|
||
}
|
||
});
|
||
};
|
||
walk(tree);
|
||
return map;
|
||
};
|
||
|
||
function ManagementList() {
|
||
const access = useAccess();
|
||
|
||
const formTableRef = useRef<FormInstance>();
|
||
const actionRef = useRef<ActionType>();
|
||
|
||
const [currentRow, setCurrentRow] = useState<API.CompanyList.Company>();
|
||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
||
const [detailLoading, setDetailLoading] = useState<boolean>(false);
|
||
const [detailData, setDetailData] = useState<API.CompanyList.Company | undefined>();
|
||
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
||
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||
const [industryEnum, setIndustryEnum] = useState<DictValueEnumObj>({});
|
||
const [approvalVisible, setApprovalVisible] = useState<boolean>(false);
|
||
const [approvalStatus, setApprovalStatus] = useState<1 | 2>(1);
|
||
const [approvalReason, setApprovalReason] = useState<string>('');
|
||
|
||
const handleRemoveOne = async (jobId: string) => {
|
||
const hide = message.loading('正在删除');
|
||
if (!jobId) return true;
|
||
try {
|
||
const resp = await delCmsCompanyList(jobId);
|
||
hide();
|
||
if (resp.code === 200) {
|
||
message.success('删除成功,即将刷新');
|
||
} else {
|
||
message.error(resp.msg);
|
||
}
|
||
return true;
|
||
} catch (error) {
|
||
hide();
|
||
message.error('删除失败,请重试');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
const handleExport = async (values: API.CompanyList.Params) => {
|
||
const hide = message.loading('正在导出');
|
||
try {
|
||
await exportCmsCompanyList(values);
|
||
hide();
|
||
message.success('导出成功');
|
||
return true;
|
||
} catch (error) {
|
||
hide();
|
||
message.error('导出失败,请重试');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
getDictValueEnum('scale', true, true).then((data) => {
|
||
setScaleEnum(data);
|
||
});
|
||
getDictValueEnum('company_nature', false, true).then((data) => {
|
||
setCompanyNatureEnum(data);
|
||
});
|
||
getCmsIndustryTreeList().then((res) => {
|
||
if (res?.data) {
|
||
setIndustryEnum(flattenIndustryTree(res.data));
|
||
}
|
||
});
|
||
}, []);
|
||
|
||
const columns: ProColumns<API.CompanyList.Company>[] = [
|
||
{
|
||
title: '时间范围',
|
||
dataIndex: 'dateRange',
|
||
hideInTable: true,
|
||
valueType: 'dateRange',
|
||
fieldProps: {
|
||
format: 'YYYY-MM-DD',
|
||
},
|
||
search: {
|
||
transform: (value) => ({
|
||
startDate: value[0],
|
||
endDate: value[1],
|
||
}),
|
||
},
|
||
},
|
||
{
|
||
title: '公司名称',
|
||
dataIndex: 'name',
|
||
valueType: 'text',
|
||
align: 'center',
|
||
},
|
||
{
|
||
title: '公司行业',
|
||
dataIndex: 'industry',
|
||
valueType: 'text',
|
||
align: 'center',
|
||
render: (_, record) => {
|
||
return <DictTag enums={industryEnum} value={record.industry} />;
|
||
},
|
||
},
|
||
{
|
||
title: '公司规模',
|
||
dataIndex: 'scale',
|
||
valueType: 'select',
|
||
align: 'center',
|
||
valueEnum: scaleEnum,
|
||
render: (_, record) => {
|
||
return <DictTag enums={scaleEnum} value={record.scale} />;
|
||
},
|
||
},
|
||
{
|
||
title: '企业性质',
|
||
dataIndex: 'companyNature',
|
||
valueType: 'select',
|
||
align: 'center',
|
||
valueEnum: companyNatureEnum,
|
||
render: (_, record) => <DictTag enums={companyNatureEnum} value={record.companyNature} />,
|
||
},
|
||
{
|
||
title: '公司位置',
|
||
dataIndex: 'location',
|
||
valueType: 'text',
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
valueType: 'select',
|
||
align: 'center',
|
||
valueEnum: {
|
||
0: { text: '待审核', status: 'Processing' },
|
||
1: { text: '通过', status: 'Success' },
|
||
2: { text: '驳回', status: 'Error' },
|
||
},
|
||
render: (_, record) => {
|
||
if (record.status === 1) {
|
||
return <Tag color="success">通过</Tag>;
|
||
} else if (record.status === 2) {
|
||
return <Tag color="error">驳回</Tag>;
|
||
}
|
||
return <Tag color="orange">待审核</Tag>;
|
||
},
|
||
},
|
||
{
|
||
title: '驳回原因',
|
||
dataIndex: 'notPassReason',
|
||
valueType: 'text',
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
render: (_, record) => {
|
||
return record.notPassReason || '-';
|
||
},
|
||
},
|
||
{
|
||
title: '驳回时间',
|
||
dataIndex: 'rejectTime',
|
||
valueType: 'dateTime',
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
render: (_, record) => {
|
||
return record.updateTime || '-';
|
||
},
|
||
},
|
||
{
|
||
title: '操作',
|
||
hideInSearch: true,
|
||
align: 'center',
|
||
dataIndex: 'companyId',
|
||
width: 300,
|
||
render: (companyId, record) => [
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
key="detail"
|
||
icon={<AlignLeftOutlined />}
|
||
hidden={!access.hasPerms('cms:company:query')}
|
||
onClick={async () => {
|
||
setDetailData({ ...record });
|
||
setDetailVisible(true);
|
||
setDetailLoading(true);
|
||
try {
|
||
const merged = await loadCompanyDetail(record);
|
||
setDetailData(merged);
|
||
} catch (e) {
|
||
message.error('获取详情失败');
|
||
} finally {
|
||
setDetailLoading(false);
|
||
}
|
||
}}
|
||
>
|
||
详情
|
||
</Button>,
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
key="edit"
|
||
icon={<FormOutlined />}
|
||
hidden={!access.hasPerms('cms:company:edit')}
|
||
onClick={async () => {
|
||
const hide = message.loading('正在加载详情');
|
||
try {
|
||
const merged = await loadCompanyDetail(record);
|
||
setCurrentRow(merged);
|
||
setModalVisible(true);
|
||
} catch (e) {
|
||
message.error('获取详情失败');
|
||
} finally {
|
||
hide();
|
||
}
|
||
}}
|
||
>
|
||
编辑
|
||
</Button>,
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
key="approval"
|
||
hidden={!access.hasPerms('sys:company:approval')}
|
||
icon={<AuditOutlined />}
|
||
onClick={() => {
|
||
setCurrentRow(record);
|
||
setApprovalStatus(1);
|
||
setApprovalReason('');
|
||
setApprovalVisible(true);
|
||
}}
|
||
>
|
||
审核
|
||
</Button>,
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
danger
|
||
key="batchRemove"
|
||
icon={<DeleteOutlined />}
|
||
hidden={!access.hasPerms('cms:company:remove')}
|
||
onClick={async () => {
|
||
Modal.confirm({
|
||
title: '删除',
|
||
content: '确定删除该项吗?',
|
||
okText: '确认',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
const success = await handleRemoveOne(companyId as string);
|
||
if (success) {
|
||
if (actionRef.current) {
|
||
actionRef.current.reload();
|
||
}
|
||
}
|
||
},
|
||
});
|
||
}}
|
||
>
|
||
删除
|
||
</Button>,
|
||
],
|
||
},
|
||
];
|
||
|
||
return (
|
||
<Fragment>
|
||
<div style={{ width: '100%', float: 'right' }}>
|
||
<ProTable<API.CompanyList.Company>
|
||
actionRef={actionRef}
|
||
formRef={formTableRef}
|
||
rowKey="companyId"
|
||
key="index"
|
||
columns={columns}
|
||
request={(params) =>
|
||
getCmsCompanyList({ ...params } as API.CompanyList.Params).then((res) => {
|
||
return {
|
||
data: res.rows,
|
||
total: res.total,
|
||
success: true,
|
||
};
|
||
})
|
||
}
|
||
toolBarRender={() => [
|
||
<Button
|
||
type="primary"
|
||
key="add"
|
||
hidden={!access.hasPerms('cms:company:add')}
|
||
onClick={async () => {
|
||
setCurrentRow(undefined);
|
||
setModalVisible(true);
|
||
}}
|
||
>
|
||
<PlusOutlined /> 新建
|
||
</Button>,
|
||
<Button
|
||
type="primary"
|
||
key="export"
|
||
hidden={!access.hasPerms('cms:company:export')}
|
||
onClick={async () => {
|
||
const searchVal = formTableRef.current?.getFieldsValue();
|
||
handleExport(buildListSearchParams(searchVal || {}));
|
||
}}
|
||
>
|
||
<PlusOutlined />
|
||
<FormattedMessage id="pages.searchTable.export" defaultMessage="导出" />
|
||
</Button>,
|
||
]}
|
||
/>
|
||
</div>
|
||
|
||
<EditCompanyListRow
|
||
open={modalVisible}
|
||
onSubmit={async (values) => {
|
||
let resData;
|
||
if (values.companyId) {
|
||
resData = await putCmsCompanyList({
|
||
...values,
|
||
companyNature: values.companyNature || '',
|
||
});
|
||
} else {
|
||
resData = await addCmsCompanyList({
|
||
...values,
|
||
companyNature: values.companyNature || '',
|
||
});
|
||
}
|
||
if (resData.code === 200) {
|
||
setModalVisible(false);
|
||
setCurrentRow(undefined);
|
||
message.success(values.companyId ? '修改成功' : '新增成功');
|
||
actionRef.current?.reload();
|
||
}
|
||
}}
|
||
onCancel={() => {
|
||
setModalVisible(false);
|
||
setCurrentRow(undefined);
|
||
}}
|
||
values={currentRow}
|
||
scaleEnum={scaleEnum}
|
||
companyNatureEnum={companyNatureEnum}
|
||
/>
|
||
|
||
<CompanyDetailView
|
||
open={detailVisible}
|
||
loading={detailLoading}
|
||
onCancel={() => {
|
||
setDetailVisible(false);
|
||
setDetailData(undefined);
|
||
}}
|
||
record={detailData}
|
||
scaleEnum={scaleEnum}
|
||
companyNatureEnum={companyNatureEnum}
|
||
industryEnum={industryEnum}
|
||
/>
|
||
|
||
<Modal
|
||
title="企业资质审核"
|
||
open={approvalVisible}
|
||
width={520}
|
||
onCancel={() => {
|
||
setApprovalVisible(false);
|
||
setApprovalReason('');
|
||
}}
|
||
footer={null}
|
||
>
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||
<div>
|
||
<span style={{ marginRight: 12 }}>审核结果:</span>
|
||
<label style={{ marginRight: 16 }}>
|
||
<input
|
||
type="radio"
|
||
name="approvalStatus"
|
||
checked={approvalStatus === 1}
|
||
onChange={() => setApprovalStatus(1)}
|
||
/>
|
||
<span style={{ marginLeft: 6 }}>通过</span>
|
||
</label>
|
||
<label>
|
||
<input
|
||
type="radio"
|
||
name="approvalStatus"
|
||
checked={approvalStatus === 2}
|
||
onChange={() => setApprovalStatus(2)}
|
||
/>
|
||
<span style={{ marginLeft: 6 }}>驳回</span>
|
||
</label>
|
||
</div>
|
||
{approvalStatus === 2 && (
|
||
<div>
|
||
<div style={{ marginBottom: 6 }}>驳回原因(必填):</div>
|
||
<textarea
|
||
style={{ width: '100%', minHeight: 100 }}
|
||
placeholder="请输入驳回原因"
|
||
value={approvalReason}
|
||
onChange={(e) => setApprovalReason(e.target.value)}
|
||
/>
|
||
</div>
|
||
)}
|
||
<div style={{ textAlign: 'right', marginTop: 8 }}>
|
||
<Button onClick={() => setApprovalVisible(false)} style={{ marginRight: 8 }}>取消</Button>
|
||
<Button type="primary" onClick={async () => {
|
||
if (!currentRow?.companyId) {
|
||
message.error('缺少企业ID');
|
||
return;
|
||
}
|
||
if (approvalStatus === 2 && !approvalReason.trim()) {
|
||
message.warning('请填写驳回原因');
|
||
return;
|
||
}
|
||
const hide = message.loading('正在提交审核');
|
||
try {
|
||
const resp = await postCompanyApproval({
|
||
companyId: currentRow.companyId,
|
||
status: approvalStatus,
|
||
notPassReason: approvalStatus === 2 ? approvalReason.trim() : undefined,
|
||
});
|
||
hide();
|
||
if (resp?.code === 200) {
|
||
message.success('提交成功');
|
||
setApprovalVisible(false);
|
||
setApprovalReason('');
|
||
if (actionRef.current) actionRef.current.reload();
|
||
} else {
|
||
message.error(resp?.msg || '提交失败');
|
||
}
|
||
} catch (e) {
|
||
hide();
|
||
message.error('提交失败');
|
||
}
|
||
}}>确认</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
</Fragment>
|
||
);
|
||
}
|
||
|
||
export default ManagementList;
|