Files
shz-admin/src/pages/Company/List/index.tsx
lapuda ed0ac7c4c2 feat: Enhance Company and Job Portal functionality
- 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.
2026-07-20 11:53:43 +08:00

487 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;