Compare commits
2 Commits
e4c413baad
...
eaa0db5587
| Author | SHA1 | Date | |
|---|---|---|---|
| eaa0db5587 | |||
| ed0ac7c4c2 |
@@ -223,6 +223,11 @@ export default [
|
|||||||
name: 'company',
|
name: 'company',
|
||||||
path: '/company',
|
path: '/company',
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
name: '企业信息',
|
||||||
|
path: '/company/info/index',
|
||||||
|
component: './Company/Info',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '企业资质审核详情',
|
name: '企业资质审核详情',
|
||||||
path: '/company/qualification-review/detail/:id',
|
path: '/company/qualification-review/detail/:id',
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ const JobComplaintModal: React.FC<JobComplaintModalProps> = ({
|
|||||||
open={open}
|
open={open}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
width={600}
|
width={600}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
footer={
|
footer={
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
|||||||
174
src/pages/Company/Info/index.tsx
Normal file
174
src/pages/Company/Info/index.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Button, Card, Form, Input, message, Select, Space, Spin, TreeSelect } from 'antd';
|
||||||
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
|
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||||
|
import { getCmsCurrentCompany, putCmsCurrentCompany } from '@/services/company/list';
|
||||||
|
import type { DictValueEnumObj } from '@/components/DictTag';
|
||||||
|
|
||||||
|
const CompanyInfoPage: React.FC = () => {
|
||||||
|
const [form] = Form.useForm<API.CompanyList.Company>();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
||||||
|
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||||||
|
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCompanyInfo = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const [companyRes, scaleData, companyNatureData, industryRes] = await Promise.all([
|
||||||
|
getCmsCurrentCompany(),
|
||||||
|
getDictValueEnum('scale', false, true),
|
||||||
|
getDictValueEnum('company_nature', false, true),
|
||||||
|
getCmsIndustryTreeList(),
|
||||||
|
]);
|
||||||
|
if (companyRes.code !== 200 || !companyRes.data) {
|
||||||
|
message.error(companyRes.msg || '获取企业信息失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.setFieldsValue(companyRes.data);
|
||||||
|
setScaleEnum(scaleData);
|
||||||
|
setCompanyNatureEnum(companyNatureData);
|
||||||
|
if (industryRes?.code === 200 && industryRes.data) {
|
||||||
|
setIndustryTree(industryRes.data);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
message.error('获取企业信息失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void loadCompanyInfo();
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
|
const handleFinish = async (values: API.CompanyList.Company) => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const companyContactList = (values.companyContactList || [])
|
||||||
|
.filter((contact) => contact.contactPerson || contact.contactPersonPhone)
|
||||||
|
.map((contact) => ({
|
||||||
|
contactPerson: contact.contactPerson,
|
||||||
|
contactPersonPhone: contact.contactPersonPhone,
|
||||||
|
}));
|
||||||
|
const response = await putCmsCurrentCompany({
|
||||||
|
...values,
|
||||||
|
companyNature: values.companyNature || '',
|
||||||
|
companyContactList,
|
||||||
|
});
|
||||||
|
if (response.code === 200) {
|
||||||
|
message.success('企业信息保存成功');
|
||||||
|
form.setFieldsValue({ ...values, companyContactList });
|
||||||
|
} else {
|
||||||
|
message.error(response.msg || '保存失败');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
message.error('保存失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="企业信息" style={{ margin: 24 }}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Form<API.CompanyList.Company>
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleFinish}
|
||||||
|
style={{ maxWidth: 1100 }}
|
||||||
|
>
|
||||||
|
<Space align="start" size={24} style={{ display: 'flex' }}>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="单位名称"
|
||||||
|
rules={[{ required: true, message: '请输入单位名称' }]}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入单位名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="companyNature" label="企业性质" style={{ flex: 1 }}>
|
||||||
|
<Select allowClear options={Object.values(companyNatureEnum)} placeholder="请选择企业性质" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="scale"
|
||||||
|
label="单位规模"
|
||||||
|
rules={[{ required: true, message: '请选择单位规模' }]}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<Select options={Object.values(scaleEnum)} placeholder="请选择单位规模" />
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space align="start" size={24} style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="code" label="信用代码" style={{ flex: 1 }}>
|
||||||
|
<Input placeholder="请输入信用代码" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="industry" label="主要行业" style={{ flex: 1 }}>
|
||||||
|
<TreeSelect
|
||||||
|
allowClear
|
||||||
|
showSearch
|
||||||
|
treeData={industryTree}
|
||||||
|
treeNodeFilterProp="label"
|
||||||
|
fieldNames={{ label: 'label', value: 'id', children: 'children' }}
|
||||||
|
placeholder="请选择主要行业"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="location" label="单位地点" style={{ flex: 1 }}>
|
||||||
|
<Input placeholder="请输入单位地点" />
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Form.Item name="registeredAddress" label="注册地址">
|
||||||
|
<Input placeholder="请输入注册地址" />
|
||||||
|
</Form.Item>
|
||||||
|
<Space align="start" size={24} style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="legalPerson" label="法人姓名" style={{ flex: 1 }}>
|
||||||
|
<Input placeholder="请输入法人姓名" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="legalPhone" label="法人联系方式" style={{ flex: 1 }}>
|
||||||
|
<Input placeholder="请输入法人联系方式" />
|
||||||
|
</Form.Item>
|
||||||
|
</Space>
|
||||||
|
<Form.Item name="description" label="单位介绍">
|
||||||
|
<Input.TextArea rows={6} placeholder="请输入单位介绍" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="企业联系人">
|
||||||
|
<Form.List name="companyContactList">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<Space key={key} align="baseline" style={{ display: 'flex', marginBottom: 8 }}>
|
||||||
|
<Form.Item {...restField} name={[name, 'contactPerson']}>
|
||||||
|
<Input placeholder="联系人姓名" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...restField} name={[name, 'contactPersonPhone']}>
|
||||||
|
<Input placeholder="联系电话" />
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
||||||
|
添加联系人
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" loading={saving}>
|
||||||
|
保存企业信息
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Spin>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CompanyInfoPage;
|
||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
MinusCircleOutlined,
|
MinusCircleOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
IdcardOutlined,
|
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -34,6 +33,7 @@ export type ListFormProps = {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
values?: Partial<API.CompanyList.Company>;
|
values?: Partial<API.CompanyList.Company>;
|
||||||
scaleEnum?: DictValueEnumObj;
|
scaleEnum?: DictValueEnumObj;
|
||||||
|
companyNatureEnum?: DictValueEnumObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListEdit: React.FC<ListFormProps> = (props) => {
|
const ListEdit: React.FC<ListFormProps> = (props) => {
|
||||||
@@ -80,7 +80,6 @@ const ListEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
name="name"
|
name="name"
|
||||||
label="单位名称"
|
label="单位名称"
|
||||||
placeholder="请输入单位名称"
|
placeholder="请输入单位名称"
|
||||||
disabled={!!props.values}
|
|
||||||
/>
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
width="md"
|
width="md"
|
||||||
@@ -89,16 +88,22 @@ const ListEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
valueEnum={props.scaleEnum}
|
valueEnum={props.scaleEnum}
|
||||||
placeholder="请选择单位规模"
|
placeholder="请选择单位规模"
|
||||||
rules={[{ required: true, message: '请选择单位规模!' }]}
|
rules={[{ required: true, message: '请选择单位规模!' }]}
|
||||||
disabled={!!props.values}
|
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
|
<ProFormSelect
|
||||||
|
width="md"
|
||||||
|
name="companyNature"
|
||||||
|
label="企业性质"
|
||||||
|
valueEnum={props.companyNatureEnum}
|
||||||
|
placeholder="请选择企业性质"
|
||||||
|
fieldProps={{ allowClear: true }}
|
||||||
|
/>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
width="md"
|
width="md"
|
||||||
name="code"
|
name="code"
|
||||||
label="信用代码"
|
label="信用代码"
|
||||||
placeholder="请输入信用代码"
|
placeholder="请输入信用代码"
|
||||||
disabled={!!props.values}
|
|
||||||
/>
|
/>
|
||||||
<ProFormTreeSelect
|
<ProFormTreeSelect
|
||||||
name="industry"
|
name="industry"
|
||||||
@@ -106,7 +111,6 @@ const ListEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
placeholder="请输入主要行业"
|
placeholder="请输入主要行业"
|
||||||
allowClear
|
allowClear
|
||||||
width="md"
|
width="md"
|
||||||
disabled={!!props.values}
|
|
||||||
request={async () => {
|
request={async () => {
|
||||||
return getCmsIndustryTreeList().then((res) => {
|
return getCmsIndustryTreeList().then((res) => {
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -135,16 +139,34 @@ const ListEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
name="location"
|
name="location"
|
||||||
label="单位地点"
|
label="单位地点"
|
||||||
placeholder="请输入单位地点"
|
placeholder="请输入单位地点"
|
||||||
disabled={!!props.values}
|
|
||||||
/>
|
/>
|
||||||
|
<ProFormText
|
||||||
|
width="xl"
|
||||||
|
name="registeredAddress"
|
||||||
|
label="注册地址"
|
||||||
|
placeholder="请输入注册地址"
|
||||||
|
/>
|
||||||
|
</ProForm.Group>
|
||||||
|
<ProForm.Group>
|
||||||
|
<ProFormText
|
||||||
|
width="md"
|
||||||
|
name="legalPerson"
|
||||||
|
label="法人姓名"
|
||||||
|
placeholder="请输入法人姓名"
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
width="md"
|
||||||
|
name="legalPhone"
|
||||||
|
label="法人联系方式"
|
||||||
|
placeholder="请输入法人联系方式"
|
||||||
|
/>
|
||||||
|
</ProForm.Group>
|
||||||
<ProFormTextArea
|
<ProFormTextArea
|
||||||
width="xl"
|
width="xl"
|
||||||
name="description"
|
name="description"
|
||||||
label="单位介绍"
|
label="单位介绍"
|
||||||
placeholder="请输入单位介绍"
|
placeholder="请输入单位介绍"
|
||||||
disabled={!!props.values}
|
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
|
||||||
|
|
||||||
{/* 企业联系人动态表单 */}
|
{/* 企业联系人动态表单 */}
|
||||||
<ProForm.Item label="企业联系人">
|
<ProForm.Item label="企业联系人">
|
||||||
@@ -206,6 +228,7 @@ export const CompanyDetailView = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
record,
|
record,
|
||||||
scaleEnum,
|
scaleEnum,
|
||||||
|
companyNatureEnum,
|
||||||
industryEnum,
|
industryEnum,
|
||||||
loading,
|
loading,
|
||||||
}: {
|
}: {
|
||||||
@@ -213,11 +236,12 @@ export const CompanyDetailView = ({
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
record?: API.CompanyList.Company;
|
record?: API.CompanyList.Company;
|
||||||
scaleEnum?: DictValueEnumObj;
|
scaleEnum?: DictValueEnumObj;
|
||||||
|
companyNatureEnum?: DictValueEnumObj;
|
||||||
industryEnum?: DictValueEnumObj;
|
industryEnum?: DictValueEnumObj;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const contacts = record?.companyContactList?.filter(
|
const contacts = record?.companyContactList?.filter(
|
||||||
(c) => c.contactPerson || c.contactPersonPhone || c.position,
|
(c) => c.contactPerson || c.contactPersonPhone,
|
||||||
);
|
);
|
||||||
|
|
||||||
const statusTag =
|
const statusTag =
|
||||||
@@ -254,6 +278,9 @@ export const CompanyDetailView = ({
|
|||||||
{record?.scale != null && record.scale !== '' && (
|
{record?.scale != null && record.scale !== '' && (
|
||||||
<DictTag enums={scaleEnum} value={record.scale} />
|
<DictTag enums={scaleEnum} value={record.scale} />
|
||||||
)}
|
)}
|
||||||
|
{record?.companyNature != null && record.companyNature !== '' && (
|
||||||
|
<DictTag enums={companyNatureEnum} value={record.companyNature} />
|
||||||
|
)}
|
||||||
{statusTag}
|
{statusTag}
|
||||||
{record?.industry != null && record.industry !== '' ? (
|
{record?.industry != null && record.industry !== '' ? (
|
||||||
<DictTag enums={industryEnum} value={record.industry} />
|
<DictTag enums={industryEnum} value={record.industry} />
|
||||||
@@ -272,9 +299,25 @@ export const CompanyDetailView = ({
|
|||||||
<Descriptions.Item label="公司位置" span={2}>
|
<Descriptions.Item label="公司位置" span={2}>
|
||||||
{renderDetailText(record?.location as string)}
|
{renderDetailText(record?.location as string)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="企业性质">
|
||||||
|
{record?.companyNature ? (
|
||||||
|
<DictTag enums={companyNatureEnum} value={record.companyNature} />
|
||||||
|
) : (
|
||||||
|
renderDetailText()
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="信用代码" span={2}>
|
<Descriptions.Item label="信用代码" span={2}>
|
||||||
{renderDetailText(record?.code)}
|
{renderDetailText(record?.code)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="注册地址">
|
||||||
|
{renderDetailText(record?.registeredAddress)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="法人姓名">
|
||||||
|
{renderDetailText(record?.legalPerson)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="法人联系方式">
|
||||||
|
{renderDetailText(record?.legalPhone)}
|
||||||
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -307,12 +350,6 @@ export const CompanyDetailView = ({
|
|||||||
<PhoneOutlined />
|
<PhoneOutlined />
|
||||||
<span>{renderDetailText(contact.contactPersonPhone)}</span>
|
<span>{renderDetailText(contact.contactPersonPhone)}</span>
|
||||||
</div>
|
</div>
|
||||||
{contact.position?.trim() ? (
|
|
||||||
<div className="company-detail__contact-row">
|
|
||||||
<IdcardOutlined />
|
|
||||||
<span>{contact.position}</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ function ManagementList() {
|
|||||||
const [detailLoading, setDetailLoading] = useState<boolean>(false);
|
const [detailLoading, setDetailLoading] = useState<boolean>(false);
|
||||||
const [detailData, setDetailData] = useState<API.CompanyList.Company | undefined>();
|
const [detailData, setDetailData] = useState<API.CompanyList.Company | undefined>();
|
||||||
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
||||||
|
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||||||
const [industryEnum, setIndustryEnum] = useState<DictValueEnumObj>({});
|
const [industryEnum, setIndustryEnum] = useState<DictValueEnumObj>({});
|
||||||
const [approvalVisible, setApprovalVisible] = useState<boolean>(false);
|
const [approvalVisible, setApprovalVisible] = useState<boolean>(false);
|
||||||
const [approvalStatus, setApprovalStatus] = useState<1 | 2>(1);
|
const [approvalStatus, setApprovalStatus] = useState<1 | 2>(1);
|
||||||
@@ -110,6 +111,9 @@ function ManagementList() {
|
|||||||
getDictValueEnum('scale', true, true).then((data) => {
|
getDictValueEnum('scale', true, true).then((data) => {
|
||||||
setScaleEnum(data);
|
setScaleEnum(data);
|
||||||
});
|
});
|
||||||
|
getDictValueEnum('company_nature', false, true).then((data) => {
|
||||||
|
setCompanyNatureEnum(data);
|
||||||
|
});
|
||||||
getCmsIndustryTreeList().then((res) => {
|
getCmsIndustryTreeList().then((res) => {
|
||||||
if (res?.data) {
|
if (res?.data) {
|
||||||
setIndustryEnum(flattenIndustryTree(res.data));
|
setIndustryEnum(flattenIndustryTree(res.data));
|
||||||
@@ -158,6 +162,14 @@ function ManagementList() {
|
|||||||
return <DictTag enums={scaleEnum} value={record.scale} />;
|
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: '公司位置',
|
title: '公司位置',
|
||||||
dataIndex: 'location',
|
dataIndex: 'location',
|
||||||
@@ -350,9 +362,15 @@ function ManagementList() {
|
|||||||
onSubmit={async (values) => {
|
onSubmit={async (values) => {
|
||||||
let resData;
|
let resData;
|
||||||
if (values.companyId) {
|
if (values.companyId) {
|
||||||
resData = await putCmsCompanyList(values);
|
resData = await putCmsCompanyList({
|
||||||
|
...values,
|
||||||
|
companyNature: values.companyNature || '',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
resData = await addCmsCompanyList(values);
|
resData = await addCmsCompanyList({
|
||||||
|
...values,
|
||||||
|
companyNature: values.companyNature || '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (resData.code === 200) {
|
if (resData.code === 200) {
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
@@ -367,6 +385,7 @@ function ManagementList() {
|
|||||||
}}
|
}}
|
||||||
values={currentRow}
|
values={currentRow}
|
||||||
scaleEnum={scaleEnum}
|
scaleEnum={scaleEnum}
|
||||||
|
companyNatureEnum={companyNatureEnum}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CompanyDetailView
|
<CompanyDetailView
|
||||||
@@ -378,6 +397,7 @@ function ManagementList() {
|
|||||||
}}
|
}}
|
||||||
record={detailData}
|
record={detailData}
|
||||||
scaleEnum={scaleEnum}
|
scaleEnum={scaleEnum}
|
||||||
|
companyNatureEnum={companyNatureEnum}
|
||||||
industryEnum={industryEnum}
|
industryEnum={industryEnum}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,34 @@
|
|||||||
border: 1px solid @jp-primary-border;
|
border: 1px solid @jp-primary-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-detail-tags-card {
|
||||||
|
.job-detail-tag-groups {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 128px minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-label {
|
||||||
|
padding-top: 2px;
|
||||||
|
color: @jp-text-secondary;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tag {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.company-info-card {
|
.company-info-card {
|
||||||
.company-overview {
|
.company-overview {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -163,5 +191,10 @@
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.job-detail-page .job-detail-container {
|
.job-detail-page .job-detail-container {
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
|
|
||||||
|
.job-detail-tags-card .job-detail-tag-group {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import JobComplaintModal from '@/components/JobComplaintModal';
|
|||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||||
import type { DictValueEnumObj } from '@/components/DictTag';
|
import type { DictValueEnumObj } from '@/components/DictTag';
|
||||||
|
import { parseCommaSeparatedTags } from '@/utils/jobDetailTags';
|
||||||
|
|
||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
@@ -87,9 +88,13 @@ const transformJobData = (jobData: any) => {
|
|||||||
? formatSalary(jobData.minSalary, jobData.maxSalary)
|
? formatSalary(jobData.minSalary, jobData.maxSalary)
|
||||||
: (jobData.salary || '面议'),
|
: (jobData.salary || '面议'),
|
||||||
location: jobData.jobLocation || jobData.location,
|
location: jobData.jobLocation || jobData.location,
|
||||||
|
companyNature: jobData.companyNature || jobData.company?.companyNature || '',
|
||||||
experience: jobData.experience ? experienceMap[jobData.experience] || jobData.experience : '不限',
|
experience: jobData.experience ? experienceMap[jobData.experience] || jobData.experience : '不限',
|
||||||
education: jobData.education ? educationMap[jobData.education] || jobData.education : '不限',
|
education: jobData.education ? educationMap[jobData.education] || jobData.education : '不限',
|
||||||
tags: jobData.tags || ['五险一金', '带薪年假', '年终奖'],
|
tags: Array.isArray(jobData.tags) ? jobData.tags : [],
|
||||||
|
salaryComposition: parseCommaSeparatedTags(jobData.salaryComposition),
|
||||||
|
welfareBenefits: parseCommaSeparatedTags(jobData.welfareBenefits),
|
||||||
|
workSchedule: parseCommaSeparatedTags(jobData.workSchedule),
|
||||||
publishTime: jobData.publishTime || new Date().toISOString().split('T')[0],
|
publishTime: jobData.publishTime || new Date().toISOString().split('T')[0],
|
||||||
description: jobData.description || `
|
description: jobData.description || `
|
||||||
<h3>职位职责:</h3>
|
<h3>职位职责:</h3>
|
||||||
@@ -137,7 +142,11 @@ const mockJobDetail = {
|
|||||||
location: '青岛·李沧区',
|
location: '青岛·李沧区',
|
||||||
experience: '3-5年',
|
experience: '3-5年',
|
||||||
education: '本科',
|
education: '本科',
|
||||||
tags: ['五险一金', '带薪年假', '年终奖', '定期体检'],
|
companyNature: '',
|
||||||
|
tags: [],
|
||||||
|
salaryComposition: [] as string[],
|
||||||
|
welfareBenefits: [] as string[],
|
||||||
|
workSchedule: [] as string[],
|
||||||
publishTime: '2024-03-15',
|
publishTime: '2024-03-15',
|
||||||
description: `
|
description: `
|
||||||
<h3>职位职责:</h3>
|
<h3>职位职责:</h3>
|
||||||
@@ -193,15 +202,27 @@ const JobDetailPage: React.FC = () => {
|
|||||||
{ item: '工作地', score: 0 }
|
{ item: '工作地', score: 0 }
|
||||||
]);
|
]);
|
||||||
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
||||||
|
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||||||
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
||||||
const [complaintVisible, setComplaintVisible] = useState(false);
|
const [complaintVisible, setComplaintVisible] = useState(false);
|
||||||
|
|
||||||
|
const detailTagGroups = useMemo(
|
||||||
|
() => [
|
||||||
|
{ label: '薪资范围与构成', values: jobDetail.salaryComposition },
|
||||||
|
{ label: '福利待遇', values: jobDetail.welfareBenefits },
|
||||||
|
{ label: '工作时间安排', values: jobDetail.workSchedule },
|
||||||
|
].filter((group) => group.values.length > 0),
|
||||||
|
[jobDetail.salaryComposition, jobDetail.welfareBenefits, jobDetail.workSchedule],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getDictValueEnum('scale', true, true),
|
getDictValueEnum('scale', true, true),
|
||||||
|
getDictValueEnum('company_nature', false, true),
|
||||||
getCmsIndustryTreeList(),
|
getCmsIndustryTreeList(),
|
||||||
]).then(([scaleData, industryRes]) => {
|
]).then(([scaleData, companyNatureData, industryRes]) => {
|
||||||
setScaleEnum(scaleData);
|
setScaleEnum(scaleData);
|
||||||
|
setCompanyNatureEnum(companyNatureData);
|
||||||
if (industryRes?.code === 200 && industryRes?.data) {
|
if (industryRes?.code === 200 && industryRes?.data) {
|
||||||
setIndustryTree(industryRes.data);
|
setIndustryTree(industryRes.data);
|
||||||
}
|
}
|
||||||
@@ -243,6 +264,11 @@ const JobDetailPage: React.FC = () => {
|
|||||||
setJobDetail(transformedJobData);
|
setJobDetail(transformedJobData);
|
||||||
// 根据 isCollection 字段设置收藏状态
|
// 根据 isCollection 字段设置收藏状态
|
||||||
setIsFavorited(passedJobData?.isCollection !== null && passedJobData?.isCollection !== 0 && passedJobData?.isCollection !== undefined);
|
setIsFavorited(passedJobData?.isCollection !== null && passedJobData?.isCollection !== 0 && passedJobData?.isCollection !== undefined);
|
||||||
|
// 列表卡片不携带详情标签;进入详情页后始终请求详情接口补齐完整岗位数据。
|
||||||
|
const passedJobId = passedJobData.jobId || passedJobData.id || id;
|
||||||
|
if (passedJobId) {
|
||||||
|
fetchJobDetailFromApi(String(passedJobId));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 直接刷新页面时,从 URL 参数或 route param 获取 jobId,调 API 获取数据
|
// 直接刷新页面时,从 URL 参数或 route param 获取 jobId,调 API 获取数据
|
||||||
const jobIdFromUrl = searchParams.get('jobId');
|
const jobIdFromUrl = searchParams.get('jobId');
|
||||||
@@ -507,6 +533,11 @@ const JobDetailPage: React.FC = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
{jobDetail.companyNature && (
|
||||||
|
<Tag color="blue">
|
||||||
|
{getDictLabel(companyNatureEnum, jobDetail.companyNature)}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
{jobDetail.tags.map((tag, index) => (
|
{jobDetail.tags.map((tag, index) => (
|
||||||
<Tag key={index} color="blue">{tag}</Tag>
|
<Tag key={index} color="blue">{tag}</Tag>
|
||||||
))}
|
))}
|
||||||
@@ -549,6 +580,23 @@ const JobDetailPage: React.FC = () => {
|
|||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
{/* 左侧内容区 */}
|
{/* 左侧内容区 */}
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
|
{detailTagGroups.length > 0 && (
|
||||||
|
<Card title="岗位待遇与工作安排" className="info-card job-detail-tags-card">
|
||||||
|
<div className="job-detail-tag-groups">
|
||||||
|
{detailTagGroups.map((group) => (
|
||||||
|
<div className="job-detail-tag-group" key={group.label}>
|
||||||
|
<div className="job-detail-tag-label">{group.label}</div>
|
||||||
|
<Space wrap size={[8, 8]}>
|
||||||
|
{group.values.map((value) => (
|
||||||
|
<Tag color="blue" key={`${group.label}-${value}`}>{value}</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 职位描述 */}
|
{/* 职位描述 */}
|
||||||
<Card title="职位描述" className="info-card">
|
<Card title="职位描述" className="info-card">
|
||||||
<div
|
<div
|
||||||
@@ -697,4 +745,3 @@ const JobDetailPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default JobDetailPage;
|
export default JobDetailPage;
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,43 @@
|
|||||||
margin: 20px 0 24px;
|
margin: 20px 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-section {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: @jp-text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-groups {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 128px minmax(0, 1fr);
|
||||||
|
align-items: start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-detail-tag-label {
|
||||||
|
color: @jp-text-secondary;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tag {
|
||||||
|
.jp-job-tag();
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 10px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.job-description {
|
.job-description {
|
||||||
.section-title {
|
.section-title {
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
|
|||||||
@@ -28,10 +28,18 @@ import {
|
|||||||
import { history, useLocation } from '@umijs/max';
|
import { history, useLocation } from '@umijs/max';
|
||||||
import { getJobList, getJobRecommend } from '@/services/common/jobTitle';
|
import { getJobList, getJobRecommend } from '@/services/common/jobTitle';
|
||||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||||
import { favoriteJob, unfavoriteJob, applyJob, browseJob, blockCompany } from '@/services/jobportal/user';
|
import {
|
||||||
|
favoriteJob,
|
||||||
|
unfavoriteJob,
|
||||||
|
applyJob,
|
||||||
|
browseJob,
|
||||||
|
blockCompany,
|
||||||
|
getJobDetail,
|
||||||
|
} from '@/services/jobportal/user';
|
||||||
import { ensureJobPortalLogin, requireJobPortalUserId } from '@/utils/jobPortalAuth';
|
import { ensureJobPortalLogin, requireJobPortalUserId } from '@/utils/jobPortalAuth';
|
||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import { getDictLabel, findTreeLabelById, resolveIndustryLabel } from '@/utils/jobPortalDict';
|
import { getDictLabel, findTreeLabelById, resolveIndustryLabel } from '@/utils/jobPortalDict';
|
||||||
|
import { parseCommaSeparatedTags } from '@/utils/jobDetailTags';
|
||||||
import JobComplaintModal from '@/components/JobComplaintModal';
|
import JobComplaintModal from '@/components/JobComplaintModal';
|
||||||
import { getJobTitleTreeSelect } from '@/services/common/jobTitle';
|
import { getJobTitleTreeSelect } from '@/services/common/jobTitle';
|
||||||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||||
@@ -203,6 +211,7 @@ const JobListPage: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [jobCategory, setJobCategory] = useState<string>('');
|
const [jobCategory, setJobCategory] = useState<string>('');
|
||||||
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
||||||
|
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||||||
const [jobTitleTree, setJobTitleTree] = useState<any[]>([]);
|
const [jobTitleTree, setJobTitleTree] = useState<any[]>([]);
|
||||||
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
||||||
const [searchValue, setSearchValue] = useState<string>(''); // 搜索框的值
|
const [searchValue, setSearchValue] = useState<string>(''); // 搜索框的值
|
||||||
@@ -217,6 +226,30 @@ const JobListPage: React.FC = () => {
|
|||||||
{ item: '工作地', score: 0 }
|
{ item: '工作地', score: 0 }
|
||||||
]);
|
]);
|
||||||
const [complaintVisible, setComplaintVisible] = useState(false);
|
const [complaintVisible, setComplaintVisible] = useState(false);
|
||||||
|
const selectedJobId = selectedJob?.jobId || selectedJob?.id;
|
||||||
|
|
||||||
|
const selectedJobDetailTagGroups = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: '薪资范围与构成',
|
||||||
|
values: parseCommaSeparatedTags(selectedJob?.salaryComposition),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '福利待遇',
|
||||||
|
values: parseCommaSeparatedTags(selectedJob?.welfareBenefits),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '工作时间安排',
|
||||||
|
values: parseCommaSeparatedTags(selectedJob?.workSchedule),
|
||||||
|
},
|
||||||
|
].filter((group) => group.values.length > 0),
|
||||||
|
[
|
||||||
|
selectedJob?.salaryComposition,
|
||||||
|
selectedJob?.welfareBenefits,
|
||||||
|
selectedJob?.workSchedule,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// 格式化薪资显示
|
// 格式化薪资显示
|
||||||
const formatSalary = (minSalary: number, maxSalary: number) => {
|
const formatSalary = (minSalary: number, maxSalary: number) => {
|
||||||
if (minSalary && maxSalary) {
|
if (minSalary && maxSalary) {
|
||||||
@@ -232,10 +265,12 @@ const JobListPage: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getDictValueEnum('scale', true, true),
|
getDictValueEnum('scale', true, true),
|
||||||
|
getDictValueEnum('company_nature', false, true),
|
||||||
getCmsIndustryTreeList(),
|
getCmsIndustryTreeList(),
|
||||||
getJobTitleTreeSelect(),
|
getJobTitleTreeSelect(),
|
||||||
]).then(([scaleData, industryRes, jobTitleRes]) => {
|
]).then(([scaleData, companyNatureData, industryRes, jobTitleRes]) => {
|
||||||
setScaleEnum(scaleData);
|
setScaleEnum(scaleData);
|
||||||
|
setCompanyNatureEnum(companyNatureData);
|
||||||
if (industryRes?.code === 200 && industryRes?.data) {
|
if (industryRes?.code === 200 && industryRes?.data) {
|
||||||
setIndustryTree(industryRes.data);
|
setIndustryTree(industryRes.data);
|
||||||
}
|
}
|
||||||
@@ -302,6 +337,32 @@ const JobListPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [jobList, selectedJob]);
|
}, [jobList, selectedJob]);
|
||||||
|
|
||||||
|
// 列表数据不包含岗位待遇字段,选中岗位后补取完整详情。
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedJobId) return;
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
const requestedJobId = selectedJobId;
|
||||||
|
|
||||||
|
getJobDetail(requestedJobId)
|
||||||
|
.then((response) => {
|
||||||
|
if (cancelled || response?.code !== 200 || !response?.data) return;
|
||||||
|
|
||||||
|
setSelectedJob((current: any) => {
|
||||||
|
const currentJobId = current?.jobId || current?.id;
|
||||||
|
if (String(currentJobId) !== String(requestedJobId)) return current;
|
||||||
|
return { ...current, ...response.data };
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 详情请求失败时保留列表已有信息,不影响岗位浏览。
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [selectedJobId]);
|
||||||
|
|
||||||
// 当 selectedJob 变化时,更新收藏状态
|
// 当 selectedJob 变化时,更新收藏状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedJob) {
|
if (selectedJob) {
|
||||||
@@ -313,31 +374,31 @@ const JobListPage: React.FC = () => {
|
|||||||
|
|
||||||
// 当 selectedJob 变化时,获取竞争力分析数据(仅登录)
|
// 当 selectedJob 变化时,获取竞争力分析数据(仅登录)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedJob) return;
|
if (!selectedJobId) return;
|
||||||
if (!isJobPortalLoggedIn()) {
|
if (!isJobPortalLoggedIn()) {
|
||||||
setOverallScore(0);
|
setOverallScore(0);
|
||||||
setRadarData(getEmptyCompetitivenessRadar());
|
setRadarData(getEmptyCompetitivenessRadar());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const jobIdNum = Number(selectedJob?.jobId || selectedJob?.id);
|
const jobIdNum = Number(selectedJobId);
|
||||||
if (!Number.isNaN(jobIdNum) && jobIdNum) {
|
if (!Number.isNaN(jobIdNum) && jobIdNum) {
|
||||||
fetchCompetitiveness(jobIdNum);
|
fetchCompetitiveness(jobIdNum);
|
||||||
}
|
}
|
||||||
}, [selectedJob]);
|
}, [selectedJobId]);
|
||||||
|
|
||||||
// 当 selectedJob 变化时,上报浏览记录(仅登录)
|
// 当 selectedJob 变化时,上报浏览记录(仅登录)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedJob) return;
|
if (!selectedJobId) return;
|
||||||
if (!isJobPortalLoggedIn()) {
|
if (!isJobPortalLoggedIn()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const jobIdNum = Number(selectedJob?.jobId || selectedJob?.id);
|
const jobIdNum = Number(selectedJobId);
|
||||||
if (!Number.isNaN(jobIdNum) && jobIdNum > 0) {
|
if (!Number.isNaN(jobIdNum) && jobIdNum > 0) {
|
||||||
browseJob({ jobId: jobIdNum }).catch(() => {
|
browseJob({ jobId: jobIdNum }).catch(() => {
|
||||||
// 浏览上报失败不阻塞页面
|
// 浏览上报失败不阻塞页面
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [selectedJob]);
|
}, [selectedJobId]);
|
||||||
|
|
||||||
const fetchCompetitiveness = async (jobIdNum: number) => {
|
const fetchCompetitiveness = async (jobIdNum: number) => {
|
||||||
if (!isJobPortalLoggedIn()) {
|
if (!isJobPortalLoggedIn()) {
|
||||||
@@ -781,6 +842,11 @@ const JobListPage: React.FC = () => {
|
|||||||
{job.jobCategory && (
|
{job.jobCategory && (
|
||||||
<Tag className="job-tag">{resolveJobCategoryLabel(job)}</Tag>
|
<Tag className="job-tag">{resolveJobCategoryLabel(job)}</Tag>
|
||||||
)}
|
)}
|
||||||
|
{(job.companyNature || job.company?.companyNature) && (
|
||||||
|
<Tag className="job-tag">
|
||||||
|
{getDictLabel(companyNatureEnum, job.companyNature || job.company?.companyNature)}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
{/* {job.dataSource && <Tag className="job-tag">{job.dataSource}</Tag>} */}
|
{/* {job.dataSource && <Tag className="job-tag">{job.dataSource}</Tag>} */}
|
||||||
{job.vacancies && <Tag className="job-tag">招聘{job.vacancies}人</Tag>}
|
{job.vacancies && <Tag className="job-tag">招聘{job.vacancies}人</Tag>}
|
||||||
{/* {job.industry && <Tag className="job-tag">{job.industry}</Tag>} */}
|
{/* {job.industry && <Tag className="job-tag">{job.industry}</Tag>} */}
|
||||||
@@ -847,6 +913,14 @@ const JobListPage: React.FC = () => {
|
|||||||
{selectedJob?.jobCategory && (
|
{selectedJob?.jobCategory && (
|
||||||
<Tag className="meta-tag">{resolveJobCategoryLabel(selectedJob)}</Tag>
|
<Tag className="meta-tag">{resolveJobCategoryLabel(selectedJob)}</Tag>
|
||||||
)}
|
)}
|
||||||
|
{(selectedJob?.companyNature || selectedJob?.company?.companyNature) && (
|
||||||
|
<Tag className="meta-tag">
|
||||||
|
{getDictLabel(
|
||||||
|
companyNatureEnum,
|
||||||
|
selectedJob.companyNature || selectedJob.company?.companyNature,
|
||||||
|
)}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
{selectedJob?.vacancies && (
|
{selectedJob?.vacancies && (
|
||||||
<Tag className="meta-tag">招聘{selectedJob.vacancies}人</Tag>
|
<Tag className="meta-tag">招聘{selectedJob.vacancies}人</Tag>
|
||||||
)}
|
)}
|
||||||
@@ -891,6 +965,30 @@ const JobListPage: React.FC = () => {
|
|||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
{/* 左侧内容区 */}
|
{/* 左侧内容区 */}
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
|
{selectedJobDetailTagGroups.length > 0 && (
|
||||||
|
<div className="job-detail-tag-section">
|
||||||
|
<Title level={4} className="section-title">
|
||||||
|
岗位待遇与工作安排
|
||||||
|
</Title>
|
||||||
|
<div className="job-detail-tag-groups">
|
||||||
|
{selectedJobDetailTagGroups.map((group) => (
|
||||||
|
<div className="job-detail-tag-group" key={group.label}>
|
||||||
|
<div className="job-detail-tag-label">{group.label}</div>
|
||||||
|
<Space wrap size={[8, 8]}>
|
||||||
|
{group.values.map((value) => (
|
||||||
|
<Tag
|
||||||
|
className="detail-tag"
|
||||||
|
key={`${group.label}-${value}`}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="job-description">
|
<div className="job-description">
|
||||||
<Title level={4} className="section-title">职位描述</Title>
|
<Title level={4} className="section-title">职位描述</Title>
|
||||||
<Paragraph className="description-text">
|
<Paragraph className="description-text">
|
||||||
@@ -1049,4 +1147,3 @@ const JobListPage: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default JobListPage;
|
export default JobListPage;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import { getGetInfoCache, saveGetInfoCache } from '@/utils/jobPortalAuth';
|
|||||||
import { getAccessToken } from '@/access';
|
import { getAccessToken } from '@/access';
|
||||||
import { getUserInfo } from '@/services/session';
|
import { getUserInfo } from '@/services/session';
|
||||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||||
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
|
import { getDictLabel } from '@/utils/jobPortalDict';
|
||||||
|
import type { DictValueEnumObj } from '@/components/DictTag';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
@@ -128,6 +131,7 @@ const JobPortalPage: React.FC = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
const [jobTitleData, setJobTitleData] = useState<any>(null);
|
const [jobTitleData, setJobTitleData] = useState<any>(null);
|
||||||
const [jobRecommendData, setJobRecommendData] = useState<any>(null);
|
const [jobRecommendData, setJobRecommendData] = useState<any>(null);
|
||||||
|
const [companyNatureEnum, setCompanyNatureEnum] = useState<DictValueEnumObj>({});
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [itemsPerPage] = useState<number>(6);
|
const [itemsPerPage] = useState<number>(6);
|
||||||
const [totalPages, setTotalPages] = useState<number>(1);
|
const [totalPages, setTotalPages] = useState<number>(1);
|
||||||
@@ -189,6 +193,9 @@ const JobPortalPage: React.FC = () => {
|
|||||||
|
|
||||||
fetchJobTitleData();
|
fetchJobTitleData();
|
||||||
fetchJobRecommendData();
|
fetchJobRecommendData();
|
||||||
|
getDictValueEnum('company_nature', false, true)
|
||||||
|
.then(setCompanyNatureEnum)
|
||||||
|
.catch(() => setCompanyNatureEnum({}));
|
||||||
}, [itemsPerPage]);
|
}, [itemsPerPage]);
|
||||||
|
|
||||||
const handleIndustryHover = (industryId: string) => {
|
const handleIndustryHover = (industryId: string) => {
|
||||||
@@ -290,6 +297,11 @@ const JobPortalPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="job-tags">
|
<div className="job-tags">
|
||||||
{job.jobCategory && <Tag className="job-tag">{job.jobCategory}</Tag>}
|
{job.jobCategory && <Tag className="job-tag">{job.jobCategory}</Tag>}
|
||||||
|
{(job.companyNature || job.company?.companyNature) && (
|
||||||
|
<Tag className="job-tag">
|
||||||
|
{getDictLabel(companyNatureEnum, job.companyNature || job.company?.companyNature)}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
{job.vacancies && <Tag className="job-tag">招聘{job.vacancies}人</Tag>}
|
{job.vacancies && <Tag className="job-tag">招聘{job.vacancies}人</Tag>}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
ProFormTextArea,
|
ProFormTextArea,
|
||||||
ProDescriptions,
|
ProDescriptions,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Form, Button, Input, TreeSelect, Switch } from 'antd';
|
import { Form, Button, Input, Tag, TreeSelect, Switch } from 'antd';
|
||||||
import { PlusOutlined, MinusCircleOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
import { PlusOutlined, MinusCircleOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { DictValueEnumObj } from '@/components/DictTag';
|
import { DictValueEnumObj } from '@/components/DictTag';
|
||||||
@@ -16,6 +16,37 @@ import { getCmsCompanyList } from '@/services/company/list';
|
|||||||
import { getCmsJobDetail } from '@/services/Management/list';
|
import { getCmsJobDetail } from '@/services/Management/list';
|
||||||
import { getJobTitleTreeSelect } from '@/services/common/jobTitle';
|
import { getJobTitleTreeSelect } from '@/services/common/jobTitle';
|
||||||
import ProFromMap from '@/components/ProFromMap';
|
import ProFromMap from '@/components/ProFromMap';
|
||||||
|
import {
|
||||||
|
parseCommaSeparatedTags,
|
||||||
|
serializeCommaSeparatedTags,
|
||||||
|
} from '@/utils/jobDetailTags';
|
||||||
|
|
||||||
|
type ManageFormValues = Omit<
|
||||||
|
API.ManagementList.Manage,
|
||||||
|
'salaryComposition' | 'welfareBenefits' | 'workSchedule'
|
||||||
|
> & {
|
||||||
|
salaryComposition?: string[];
|
||||||
|
welfareBenefits?: string[];
|
||||||
|
workSchedule?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const toManageFormValues = (
|
||||||
|
values: Partial<API.ManagementList.Manage>,
|
||||||
|
): Partial<ManageFormValues> => ({
|
||||||
|
...values,
|
||||||
|
jobLocationAreaCode: String(values.jobLocationAreaCode || ''),
|
||||||
|
salaryComposition: parseCommaSeparatedTags(values.salaryComposition),
|
||||||
|
welfareBenefits: parseCommaSeparatedTags(values.welfareBenefits),
|
||||||
|
workSchedule: parseCommaSeparatedTags(values.workSchedule),
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderDetailTags = (value: unknown) => {
|
||||||
|
const tags = parseCommaSeparatedTags(value);
|
||||||
|
if (tags.length === 0) {
|
||||||
|
return <span style={{ color: '#999' }}>暂无</span>;
|
||||||
|
}
|
||||||
|
return tags.map((tag) => <Tag key={tag}>{tag}</Tag>);
|
||||||
|
};
|
||||||
|
|
||||||
export type ListFormProps = {
|
export type ListFormProps = {
|
||||||
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
||||||
@@ -26,6 +57,9 @@ export type ListFormProps = {
|
|||||||
experienceEnum: DictValueEnumObj;
|
experienceEnum: DictValueEnumObj;
|
||||||
areaEnum: DictValueEnumObj;
|
areaEnum: DictValueEnumObj;
|
||||||
jobTypeEnum: DictValueEnumObj;
|
jobTypeEnum: DictValueEnumObj;
|
||||||
|
salaryCompositionEnum: DictValueEnumObj;
|
||||||
|
welfareBenefitsEnum: DictValueEnumObj;
|
||||||
|
workScheduleEnum: DictValueEnumObj;
|
||||||
mode?: 'view' | 'edit' | 'create';
|
mode?: 'view' | 'edit' | 'create';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,23 +72,28 @@ const waitTime = (time: number = 100) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
const listEdit: React.FC<ListFormProps> = (props) => {
|
||||||
const [form] = Form.useForm<API.ManagementList.Manage>();
|
const [form] = Form.useForm<ManageFormValues>();
|
||||||
const companyNameMap = useRef<Record<number, string>>({});
|
const companyNameMap = useRef<Record<number, string>>({});
|
||||||
const [jobDetail, setJobDetail] = useState<API.ManagementList.Manage | null>(null);
|
const [jobDetail, setJobDetail] = useState<API.ManagementList.Manage | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [jobCategoryTreeData, setJobCategoryTreeData] = useState<any[]>([]);
|
const [jobCategoryTreeData, setJobCategoryTreeData] = useState<any[]>([]);
|
||||||
const [mapOpen, setMapOpen] = useState<boolean>(false);
|
const [mapOpen, setMapOpen] = useState<boolean>(false);
|
||||||
const [mapViewInfo, setMapViewInfo] = useState<any>({});
|
const [mapViewInfo, setMapViewInfo] = useState<any>({});
|
||||||
const { educationEnum, experienceEnum, areaEnum, jobTypeEnum } = props;
|
const {
|
||||||
|
educationEnum,
|
||||||
|
experienceEnum,
|
||||||
|
areaEnum,
|
||||||
|
jobTypeEnum,
|
||||||
|
salaryCompositionEnum,
|
||||||
|
welfareBenefitsEnum,
|
||||||
|
workScheduleEnum,
|
||||||
|
} = props;
|
||||||
const { mode = props.values ? 'edit' : 'create' } = props;
|
const { mode = props.values ? 'edit' : 'create' } = props;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.open){
|
if(props.open){
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
if (props.values) {
|
if (props.values) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue(toManageFormValues(props.values));
|
||||||
...props.values,
|
|
||||||
jobLocationAreaCode: String(props.values.jobLocationAreaCode || ''),
|
|
||||||
});
|
|
||||||
// 初始化地图位置信息(编辑时回显)
|
// 初始化地图位置信息(编辑时回显)
|
||||||
if (props.values.latitude || props.values.longitude) {
|
if (props.values.latitude || props.values.longitude) {
|
||||||
setMapViewInfo({
|
setMapViewInfo({
|
||||||
@@ -92,10 +131,7 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
setJobDetail(response.data);
|
setJobDetail(response.data);
|
||||||
// 如果是编辑模式,将获取到的详情数据设置到表单中
|
// 如果是编辑模式,将获取到的详情数据设置到表单中
|
||||||
if (mode === 'edit') {
|
if (mode === 'edit') {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue(toManageFormValues(response.data));
|
||||||
...response.data,
|
|
||||||
jobLocationAreaCode: String(response.data.jobLocationAreaCode || ''),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -139,6 +175,9 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
: companyNameMap.current[values.companyId] ?? '',
|
: companyNameMap.current[values.companyId] ?? '',
|
||||||
minSalary: values.minSalary,
|
minSalary: values.minSalary,
|
||||||
maxSalary: values.maxSalary,
|
maxSalary: values.maxSalary,
|
||||||
|
salaryComposition: serializeCommaSeparatedTags(values.salaryComposition),
|
||||||
|
welfareBenefits: serializeCommaSeparatedTags(values.welfareBenefits),
|
||||||
|
workSchedule: serializeCommaSeparatedTags(values.workSchedule),
|
||||||
education: values.education,
|
education: values.education,
|
||||||
experience: values.experience,
|
experience: values.experience,
|
||||||
jobLocationAreaCode: values.jobLocationAreaCode,
|
jobLocationAreaCode: values.jobLocationAreaCode,
|
||||||
@@ -189,6 +228,24 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<ProDescriptions.Item dataIndex="minSalary" label="最低薪资(元/月)" />
|
<ProDescriptions.Item dataIndex="minSalary" label="最低薪资(元/月)" />
|
||||||
<ProDescriptions.Item dataIndex="maxSalary" label="最高薪资(元/月)" />
|
<ProDescriptions.Item dataIndex="maxSalary" label="最高薪资(元/月)" />
|
||||||
|
<ProDescriptions.Item
|
||||||
|
dataIndex="salaryComposition"
|
||||||
|
label="薪资范围与构成"
|
||||||
|
span={2}
|
||||||
|
render={(value) => renderDetailTags(value)}
|
||||||
|
/>
|
||||||
|
<ProDescriptions.Item
|
||||||
|
dataIndex="welfareBenefits"
|
||||||
|
label="福利待遇"
|
||||||
|
span={2}
|
||||||
|
render={(value) => renderDetailTags(value)}
|
||||||
|
/>
|
||||||
|
<ProDescriptions.Item
|
||||||
|
dataIndex="workSchedule"
|
||||||
|
label="工作时间安排"
|
||||||
|
span={2}
|
||||||
|
render={(value) => renderDetailTags(value)}
|
||||||
|
/>
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item
|
||||||
dataIndex="education"
|
dataIndex="education"
|
||||||
label="学历要求"
|
label="学历要求"
|
||||||
@@ -341,7 +398,7 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ModalForm<API.ManagementList.Manage>
|
<ModalForm<ManageFormValues>
|
||||||
title={mode === 'edit' ? '编辑岗位' : '新建岗位'}
|
title={mode === 'edit' ? '编辑岗位' : '新建岗位'}
|
||||||
form={form}
|
form={form}
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
@@ -411,6 +468,33 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
label="最大薪资(元/月)"
|
label="最大薪资(元/月)"
|
||||||
placeholder="请输入最大薪资(元)"
|
placeholder="请输入最大薪资(元)"
|
||||||
/>
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
mode="tags"
|
||||||
|
width="md"
|
||||||
|
name="salaryComposition"
|
||||||
|
label="薪资范围与构成"
|
||||||
|
valueEnum={salaryCompositionEnum}
|
||||||
|
placeholder="请选择或手动输入薪资构成"
|
||||||
|
fieldProps={{ tokenSeparators: [',', ','], maxTagCount: 'responsive' }}
|
||||||
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
mode="tags"
|
||||||
|
width="md"
|
||||||
|
name="welfareBenefits"
|
||||||
|
label="福利待遇"
|
||||||
|
valueEnum={welfareBenefitsEnum}
|
||||||
|
placeholder="请选择或手动输入福利待遇"
|
||||||
|
fieldProps={{ tokenSeparators: [',', ','], maxTagCount: 'responsive' }}
|
||||||
|
/>
|
||||||
|
<ProFormSelect
|
||||||
|
mode="tags"
|
||||||
|
width="md"
|
||||||
|
name="workSchedule"
|
||||||
|
label="工作时间安排"
|
||||||
|
valueEnum={workScheduleEnum}
|
||||||
|
placeholder="请选择或手动输入工作时间安排"
|
||||||
|
fieldProps={{ tokenSeparators: [',', ','], maxTagCount: 'responsive' }}
|
||||||
|
/>
|
||||||
<ProFormSelect
|
<ProFormSelect
|
||||||
width="md"
|
width="md"
|
||||||
name="education"
|
name="education"
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ function ManagementList() {
|
|||||||
const [isPublishEnum, setIsPublishEnum] = useState<any>([]);
|
const [isPublishEnum, setIsPublishEnum] = useState<any>([]);
|
||||||
const [jobTypeEnum, setJobTypeEnum] = useState<any>([]);
|
const [jobTypeEnum, setJobTypeEnum] = useState<any>([]);
|
||||||
const [compensationEnum, setCompensationEnum] = useState<any>([]);
|
const [compensationEnum, setCompensationEnum] = useState<any>([]);
|
||||||
|
const [salaryCompositionEnum, setSalaryCompositionEnum] = useState<any>([]);
|
||||||
|
const [welfareBenefitsEnum, setWelfareBenefitsEnum] = useState<any>([]);
|
||||||
|
const [workScheduleEnum, setWorkScheduleEnum] = useState<any>([]);
|
||||||
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||||
@@ -107,6 +110,15 @@ function ManagementList() {
|
|||||||
getDictValueEnum('job_type',true,true).then((data) => {
|
getDictValueEnum('job_type',true,true).then((data) => {
|
||||||
setJobTypeEnum(data)
|
setJobTypeEnum(data)
|
||||||
})
|
})
|
||||||
|
getDictValueEnum('salary_composition', false, true).then((data) => {
|
||||||
|
setSalaryCompositionEnum(data);
|
||||||
|
});
|
||||||
|
getDictValueEnum('welfare_benefits', false, true).then((data) => {
|
||||||
|
setWelfareBenefitsEnum(data);
|
||||||
|
});
|
||||||
|
getDictValueEnum('work_schedule', false, true).then((data) => {
|
||||||
|
setWorkScheduleEnum(data);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function changeRelease(record: API.ManagementList.Manage) {
|
async function changeRelease(record: API.ManagementList.Manage) {
|
||||||
@@ -492,6 +504,9 @@ function ManagementList() {
|
|||||||
experienceEnum={experienceEnum}
|
experienceEnum={experienceEnum}
|
||||||
areaEnum={areaEnum}
|
areaEnum={areaEnum}
|
||||||
jobTypeEnum={jobTypeEnum}
|
jobTypeEnum={jobTypeEnum}
|
||||||
|
salaryCompositionEnum={salaryCompositionEnum}
|
||||||
|
welfareBenefitsEnum={welfareBenefitsEnum}
|
||||||
|
workScheduleEnum={workScheduleEnum}
|
||||||
></EditManageRow>
|
></EditManageRow>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,3 +58,22 @@ export async function getCmsCompanyDetail(companyId: number | string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取当前企业用户关联的企业信息 */
|
||||||
|
export async function getCmsCurrentCompany() {
|
||||||
|
return request<{
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
data?: API.CompanyList.Company;
|
||||||
|
}>('/api/cms/company/current', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改当前企业用户关联的企业信息 */
|
||||||
|
export async function putCmsCurrentCompany(params: API.CompanyList.Params) {
|
||||||
|
return request<API.CompanyList.CompanyListResult>('/api/cms/company/current', {
|
||||||
|
method: 'PUT',
|
||||||
|
data: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
8
src/types/Management/list.d.ts
vendored
8
src/types/Management/list.d.ts
vendored
@@ -23,6 +23,9 @@ declare namespace API.ManagementList {
|
|||||||
longitude?: number;
|
longitude?: number;
|
||||||
maxSalary?: number;
|
maxSalary?: number;
|
||||||
minSalary?: number;
|
minSalary?: number;
|
||||||
|
salaryComposition?: string;
|
||||||
|
welfareBenefits?: string;
|
||||||
|
workSchedule?: string;
|
||||||
postingDate?: string;
|
postingDate?: string;
|
||||||
vacancies?: number;
|
vacancies?: number;
|
||||||
view?: number;
|
view?: number;
|
||||||
@@ -32,6 +35,7 @@ declare namespace API.ManagementList {
|
|||||||
jobContactList?: ContactPerson[];
|
jobContactList?: ContactPerson[];
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
jobCategory?: string;
|
jobCategory?: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddParams {
|
export interface AddParams {
|
||||||
@@ -52,6 +56,9 @@ declare namespace API.ManagementList {
|
|||||||
longitude?: number;
|
longitude?: number;
|
||||||
maxSalary?: number;
|
maxSalary?: number;
|
||||||
minSalary?: number;
|
minSalary?: number;
|
||||||
|
salaryComposition?: string;
|
||||||
|
welfareBenefits?: string;
|
||||||
|
workSchedule?: string;
|
||||||
postingDate?: string;
|
postingDate?: string;
|
||||||
vacancies?: number;
|
vacancies?: number;
|
||||||
view?: number;
|
view?: number;
|
||||||
@@ -60,6 +67,7 @@ declare namespace API.ManagementList {
|
|||||||
jobType?: string;
|
jobType?: string;
|
||||||
jobContactList?: ContactPerson[];
|
jobContactList?: ContactPerson[];
|
||||||
jobCategory?: string;
|
jobCategory?: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListParams {
|
export interface ListParams {
|
||||||
|
|||||||
11
src/types/company/list.d.ts
vendored
11
src/types/company/list.d.ts
vendored
@@ -9,7 +9,6 @@ declare namespace API.CompanyList {
|
|||||||
export interface CompanyContact {
|
export interface CompanyContact {
|
||||||
contactPerson: string;
|
contactPerson: string;
|
||||||
contactPersonPhone: string;
|
contactPersonPhone: string;
|
||||||
position?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Company {
|
export interface Company {
|
||||||
@@ -19,8 +18,12 @@ declare namespace API.CompanyList {
|
|||||||
location?: any;
|
location?: any;
|
||||||
industry: string;
|
industry: string;
|
||||||
scale: string;
|
scale: string;
|
||||||
|
companyNature?: string;
|
||||||
code: string;
|
code: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
registeredAddress?: string;
|
||||||
|
legalPerson?: string;
|
||||||
|
legalPhone?: string;
|
||||||
companyContactList?: CompanyContact[];
|
companyContactList?: CompanyContact[];
|
||||||
contactPerson?: string;
|
contactPerson?: string;
|
||||||
contactPersonPhone?: string;
|
contactPersonPhone?: string;
|
||||||
@@ -36,7 +39,13 @@ declare namespace API.CompanyList {
|
|||||||
location?: any;
|
location?: any;
|
||||||
industry?: string;
|
industry?: string;
|
||||||
scale?: string;
|
scale?: string;
|
||||||
|
companyNature?: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
|
description?: string;
|
||||||
|
registeredAddress?: string;
|
||||||
|
legalPerson?: string;
|
||||||
|
legalPhone?: string;
|
||||||
|
companyContactList?: CompanyContact[];
|
||||||
status?: 0 | 1 | 2;
|
status?: 0 | 1 | 2;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
|||||||
16
src/utils/jobDetailTags.ts
Normal file
16
src/utils/jobDetailTags.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const parseCommaSeparatedTags = (value: unknown): string[] => {
|
||||||
|
const rawValues = Array.isArray(value)
|
||||||
|
? value
|
||||||
|
: typeof value === 'string'
|
||||||
|
? value.split(/[,,]/)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return Array.from(new Set(rawValues.map((item) => String(item ?? '').trim()).filter(Boolean)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serializeCommaSeparatedTags = (value: unknown): string | undefined => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return parseCommaSeparatedTags(value).join(',');
|
||||||
|
};
|
||||||
21
tests/utils/jobDetailTags.test.ts
Normal file
21
tests/utils/jobDetailTags.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { parseCommaSeparatedTags, serializeCommaSeparatedTags } from '@/utils/jobDetailTags';
|
||||||
|
|
||||||
|
describe('job detail tag helpers', () => {
|
||||||
|
it('parses English and Chinese commas, trims values, and removes duplicates', () => {
|
||||||
|
expect(parseCommaSeparatedTags('基本工资, 绩效工资,奖金,基本工资')).toEqual([
|
||||||
|
'基本工资',
|
||||||
|
'绩效工资',
|
||||||
|
'奖金',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps dictionary selections and manual entries in one comma-separated value', () => {
|
||||||
|
expect(serializeCommaSeparatedTags(['五险一金', '带薪年假', '生日福利'])).toBe(
|
||||||
|
'五险一金,带薪年假,生日福利',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serializes a cleared selector as an empty string so an existing value can be removed', () => {
|
||||||
|
expect(serializeCommaSeparatedTags([])).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user