求职者管理增加收藏功能、面试邀请、人才库功能开发
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
This commit is contained in:
102
src/pages/Mobileusers/List/CollectTalent.tsx
Normal file
102
src/pages/Mobileusers/List/CollectTalent.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Modal, Form, Select, Input, Button, message } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { addHrTalentCollect } from '@/services/mobileusers/list';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export type CollectTalentProps = {
|
||||
onClose: (needRefresh?: boolean) => void;
|
||||
open: boolean;
|
||||
userId?: number;
|
||||
};
|
||||
|
||||
const CollectTalent: React.FC<CollectTalentProps> = (props) => {
|
||||
const { open, userId } = props;
|
||||
|
||||
const [collectForm] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
collectForm.resetFields();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleCollectSubmit = async () => {
|
||||
try {
|
||||
const values = await collectForm.validateFields();
|
||||
setSubmitting(true);
|
||||
|
||||
const response = await addHrTalentCollect({
|
||||
userId: String(userId),
|
||||
intentionLevel: values.intentionLevel,
|
||||
collectTags: values.collectTags || undefined,
|
||||
sourceType: '2', // 投递记录
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
message.success('收藏成功');
|
||||
collectForm.resetFields();
|
||||
props.onClose(true);
|
||||
} else {
|
||||
message.error(response.msg || '收藏失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = error?.response?.data?.msg || error?.message || '收藏失败';
|
||||
message.error(errMsg);
|
||||
console.error('收藏失败:', error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onCancel={() => props.onClose()}
|
||||
title="收藏求职者"
|
||||
width={500}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => props.onClose()}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
loading={submitting}
|
||||
onClick={handleCollectSubmit}
|
||||
>
|
||||
确认收藏
|
||||
</Button>,
|
||||
]}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form
|
||||
form={collectForm}
|
||||
layout="vertical"
|
||||
initialValues={{ intentionLevel: '2' }}
|
||||
>
|
||||
<Form.Item
|
||||
name="intentionLevel"
|
||||
label="意向等级"
|
||||
rules={[{ required: true, message: '请选择意向等级' }]}
|
||||
>
|
||||
<Select placeholder="请选择意向等级">
|
||||
<Option value="1">高意向</Option>
|
||||
<Option value="2">中等意向</Option>
|
||||
<Option value="3">低意向</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="collectTags"
|
||||
label="人才标签"
|
||||
>
|
||||
<Input placeholder="请输入人才标签,如:备用人才、核心人才等" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectTalent;
|
||||
@@ -2,10 +2,12 @@ import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { FormattedMessage, useAccess } from '@umijs/max';
|
||||
import {Button, FormInstance, message, Switch} from 'antd';
|
||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { PlusOutlined, SendOutlined, StarOutlined, StarFilled } from '@ant-design/icons';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import DictTag from '@/components/DictTag';
|
||||
import { applyAgree, exportCmsAppUserExport, getCmsAppUserList } from '@/services/mobileusers/list';
|
||||
import { applyAgree, delHrTalentCollect, exportCmsAppUserExport, getCmsAppUserList, getHrTalentCollectList } from '@/services/mobileusers/list';
|
||||
import InterviewInvite from '@/pages/Management/List/ResumeRecommend/InterviewInvite';
|
||||
import CollectTalent from './CollectTalent';
|
||||
|
||||
const handleExport = async (values: API.MobileUser.ListParams) => {
|
||||
const hide = message.loading('正在导出');
|
||||
@@ -35,6 +37,9 @@ function ManagementList() {
|
||||
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
||||
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [inviteVisible, setInviteVisible] = useState<boolean>(false);
|
||||
const [collectVisible, setCollectVisible] = useState<boolean>(false);
|
||||
const [collectMap, setCollectMap] = useState<Map<number, { id: number; intentionLevel?: string }>>(new Map());
|
||||
const [isPublishEnum, setIsPublishEnum] = useState<any>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -59,8 +64,46 @@ function ManagementList() {
|
||||
// getDictValueEnum('job_hot',true).then((data) => {
|
||||
// setHotEnum(data)
|
||||
// })
|
||||
fetchCollectList();
|
||||
}, []);
|
||||
|
||||
// 获取当前企业的收藏列表,构建 userId -> collectInfo 映射
|
||||
const fetchCollectList = async () => {
|
||||
try {
|
||||
const res: any = await getHrTalentCollectList({ pageSize: 10000 });
|
||||
if (res?.code === 200 && res?.rows?.length > 0) {
|
||||
const map = new Map<number, { id: number; intentionLevel?: string }>();
|
||||
res.rows.forEach((item: any) => {
|
||||
if (item.userId) {
|
||||
map.set(item.userId, { id: item.id, intentionLevel: item.intentionLevel });
|
||||
}
|
||||
});
|
||||
setCollectMap(map);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取收藏列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消收藏(接口路径参数为userId)
|
||||
const handleCancelCollect = async (record: API.MobileUser.ListRow) => {
|
||||
try {
|
||||
const res = await delHrTalentCollect(String(record.userId));
|
||||
if (res.code === 200) {
|
||||
message.success('已取消收藏');
|
||||
setCollectMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.delete(record.userId);
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
message.error(res.msg || '取消收藏失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.msg || error?.message || '取消收藏失败');
|
||||
}
|
||||
};
|
||||
|
||||
async function changeHire(record: API.MobileUser.ListRow) {
|
||||
const resData = await applyAgree({
|
||||
id: String(record.applyId),
|
||||
@@ -180,6 +223,57 @@ function ManagementList() {
|
||||
<Switch checked={record.hire === '0'} onChange={changeHire.bind(null, record)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
dataIndex: 'jobId',
|
||||
width: 200,
|
||||
render: (_, record) => {
|
||||
const isCollected = collectMap.has(record.userId);
|
||||
return [
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="invite-interview"
|
||||
icon={<SendOutlined />}
|
||||
hidden={!access.hasPerms('cms:interview:add')}
|
||||
onClick={() => {
|
||||
setCurrentRow(record);
|
||||
setInviteVisible(true);
|
||||
}}
|
||||
>
|
||||
邀请面试
|
||||
</Button>,
|
||||
isCollected ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="cancel-collect"
|
||||
icon={<StarFilled style={{ color: '#faad14' }} />}
|
||||
hidden={!access.hasPerms('cms:hrTalentCollect:remove')}
|
||||
onClick={() => handleCancelCollect(record)}
|
||||
>
|
||||
取消收藏
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="collect"
|
||||
icon={<StarOutlined />}
|
||||
hidden={!access.hasPerms('cms:hrTalentCollect:add')}
|
||||
onClick={() => {
|
||||
setCurrentRow(record);
|
||||
setCollectVisible(true);
|
||||
}}
|
||||
>
|
||||
收藏
|
||||
</Button>
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -235,6 +329,33 @@ function ManagementList() {
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
<InterviewInvite
|
||||
open={inviteVisible}
|
||||
userId={currentRow?.userId}
|
||||
userName={currentRow?.name}
|
||||
jobId={currentRow?.jobId}
|
||||
companyId={currentRow?.companyId}
|
||||
jobName={currentRow?.jobName}
|
||||
applyId={currentRow?.applyId ? Number(currentRow.applyId) : undefined}
|
||||
onClose={(needRefresh) => {
|
||||
setInviteVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
if (needRefresh) {
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CollectTalent
|
||||
open={collectVisible}
|
||||
userId={currentRow?.userId}
|
||||
onClose={(needRefresh) => {
|
||||
setCollectVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
if (needRefresh) {
|
||||
fetchCollectList();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
212
src/pages/Talentcollect/List/index.tsx
Normal file
212
src/pages/Talentcollect/List/index.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||
import { useAccess } from '@umijs/max';
|
||||
import { Button, FormInstance, message, Tag } from 'antd';
|
||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { StarFilled } from '@ant-design/icons';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import DictTag from '@/components/DictTag';
|
||||
import { delHrTalentCollect, getHrTalentCollectList } from '@/services/mobileusers/list';
|
||||
|
||||
function TalentCollectList() {
|
||||
const access = useAccess();
|
||||
|
||||
const formTableRef = useRef<FormInstance>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
const [educationEnum, setEducationEnum] = useState<any>({});
|
||||
const [areaEnum, setAreaEnum] = useState<any>({});
|
||||
const [sexEnum, setSexEnum] = useState<any>({});
|
||||
|
||||
useEffect(() => {
|
||||
getDictValueEnum('education', true, true).then((data) => setEducationEnum(data));
|
||||
getDictValueEnum('area', true, true).then((data) => setAreaEnum(data));
|
||||
getDictValueEnum('sys_user_sex', false).then((data) => setSexEnum(data));
|
||||
}, []);
|
||||
|
||||
// 取消收藏
|
||||
const handleCancelCollect = async (record: API.TalentCollect.ListRow) => {
|
||||
if (!record.userId) return;
|
||||
try {
|
||||
const res = await delHrTalentCollect(String(record.userId));
|
||||
if (res.code === 200) {
|
||||
message.success('已取消收藏');
|
||||
actionRef.current?.reload();
|
||||
} else {
|
||||
message.error(res.msg || '取消收藏失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error?.response?.data?.msg || error?.message || '取消收藏失败');
|
||||
}
|
||||
};
|
||||
|
||||
const intentionLevelMap: Record<string, { text: string; color: string }> = {
|
||||
'1': { text: '高意向', color: 'red' },
|
||||
'2': { text: '中等意向', color: 'blue' },
|
||||
'3': { text: '低意向', color: 'default' },
|
||||
};
|
||||
|
||||
const sourceTypeMap: Record<string, string> = {
|
||||
'1': '简历搜索',
|
||||
'2': '投递记录',
|
||||
'3': '邀约面试',
|
||||
'4': '手动录入',
|
||||
};
|
||||
|
||||
const columns: ProColumns<API.TalentCollect.ListRow>[] = [
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'name',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '联系方式',
|
||||
dataIndex: 'phone',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '意向等级',
|
||||
dataIndex: 'intentionLevel',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
valueEnum: {
|
||||
'1': '高意向',
|
||||
'2': '中等意向',
|
||||
'3': '低意向',
|
||||
},
|
||||
render: (_, record) => {
|
||||
const info = intentionLevelMap[record.intentionLevel || ''];
|
||||
if (!info) return '-';
|
||||
return <Tag color={info.color}>{info.text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '人才标签',
|
||||
dataIndex: 'collectTags',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
if (!record.collectTags) return '-';
|
||||
const tags = record.collectTags.split(',').filter(Boolean);
|
||||
return (
|
||||
<>
|
||||
{tags.map((tag) => (
|
||||
<Tag key={tag} color="blue">
|
||||
{tag.trim()}
|
||||
</Tag>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '收藏来源',
|
||||
dataIndex: 'sourceType',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
valueEnum: {
|
||||
'1': '简历搜索',
|
||||
'2': '投递记录',
|
||||
'3': '邀约面试',
|
||||
'4': '手动录入',
|
||||
},
|
||||
render: (_, record) => sourceTypeMap[record.sourceType || ''] || '-',
|
||||
},
|
||||
{
|
||||
title: '学历',
|
||||
dataIndex: 'education',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
valueEnum: educationEnum,
|
||||
render: (_, record) => {
|
||||
return <DictTag enums={educationEnum} value={record.education} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '区域',
|
||||
dataIndex: 'area',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
valueEnum: areaEnum,
|
||||
render: (_, record) => {
|
||||
return <DictTag enums={areaEnum} value={record.area} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex',
|
||||
valueType: 'select',
|
||||
align: 'center',
|
||||
valueEnum: sexEnum,
|
||||
render: (_, record) => {
|
||||
return <DictTag enums={sexEnum} value={record.sex} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '收藏时间',
|
||||
dataIndex: 'createTime',
|
||||
valueType: 'dateTime',
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
dataIndex: 'userId',
|
||||
width: 120,
|
||||
render: (_, record) => [
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="cancel-collect"
|
||||
icon={<StarFilled style={{ color: '#faad14' }} />}
|
||||
hidden={!access.hasPerms('cms:hrTalentCollect:remove')}
|
||||
onClick={() => handleCancelCollect(record)}
|
||||
>
|
||||
取消收藏
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{ width: '100%', float: 'right' }}>
|
||||
<ProTable<API.TalentCollect.ListRow>
|
||||
search={{ labelWidth: 'auto' }}
|
||||
actionRef={actionRef}
|
||||
formRef={formTableRef}
|
||||
rowKey="id"
|
||||
key="talentCollect"
|
||||
columns={columns}
|
||||
request={(params) => {
|
||||
const { current, pageSize, ...searchParams } = params;
|
||||
const filteredParams: Record<string, any> = {};
|
||||
Object.keys(searchParams).forEach((key) => {
|
||||
const val = (searchParams as any)[key];
|
||||
if (val !== undefined && val !== null && val !== '') {
|
||||
filteredParams[key] = val;
|
||||
}
|
||||
});
|
||||
return getHrTalentCollectList({
|
||||
pageNum: current,
|
||||
pageSize,
|
||||
...filteredParams,
|
||||
}).then((res: any) => {
|
||||
const result = {
|
||||
data: res.rows,
|
||||
total: res.total,
|
||||
success: true,
|
||||
};
|
||||
return result;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default TalentCollectList;
|
||||
@@ -28,3 +28,33 @@ export async function applyAgree(params: { id: string; hire: number }) {
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 企业人才收藏 APIs ==========
|
||||
|
||||
/** 获取当前企业人才收藏列表 */
|
||||
export async function getHrTalentCollectList(params?: Record<string, any>) {
|
||||
return request<API.Result>('/api/cms/hrTalentCollect/list', {
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 收藏求职者(加入人才库) */
|
||||
export async function addHrTalentCollect(data: {
|
||||
userId: string;
|
||||
intentionLevel: string;
|
||||
collectTags?: string;
|
||||
sourceType: string;
|
||||
}) {
|
||||
return request<API.Result>('/api/cms/hrTalentCollect', {
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/** 取消收藏(移出人才库,路径参数为userId) */
|
||||
export async function delHrTalentCollect(userId: string) {
|
||||
return request<API.Result>(`/api/cms/hrTalentCollect/${userId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
1
src/types/mobileusers/list.d.ts
vendored
1
src/types/mobileusers/list.d.ts
vendored
@@ -9,6 +9,7 @@ declare namespace API.MobileUser {
|
||||
export interface ListRow {
|
||||
applyId?: string;
|
||||
jobId?: string;
|
||||
companyId?: number;
|
||||
createTime?: string;
|
||||
userId: number;
|
||||
name: string;
|
||||
|
||||
48
src/types/talentcollect/list.d.ts
vendored
Normal file
48
src/types/talentcollect/list.d.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
declare namespace API.TalentCollect {
|
||||
export interface ListResult {
|
||||
total: number;
|
||||
rows: ListRow[];
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface ListRow {
|
||||
id?: number;
|
||||
companyId?: number;
|
||||
userId?: number;
|
||||
/** 意向等级 1=高意向,2=中等意向,3=低意向 */
|
||||
intentionLevel?: string;
|
||||
/** 意向岗位ID */
|
||||
intendJobIds?: string;
|
||||
/** 人才标签 */
|
||||
collectTags?: string;
|
||||
/** 收藏来源 1=简历搜索,2=投递记录,3=邀约面试,4=手动录入 */
|
||||
sourceType?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
createTime?: string;
|
||||
// 用户信息(来自关联查询)
|
||||
name?: string;
|
||||
age?: string;
|
||||
sex?: string;
|
||||
birthDate?: string;
|
||||
education?: string;
|
||||
politicalAffiliation?: string;
|
||||
phone?: string;
|
||||
avatar?: string;
|
||||
salaryMin?: string;
|
||||
salaryMax?: string;
|
||||
area?: string;
|
||||
idCard?: string;
|
||||
workExperience?: string;
|
||||
}
|
||||
|
||||
export interface ListParams {
|
||||
intentionLevel?: string;
|
||||
sourceType?: string;
|
||||
collectTags?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
education?: string;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user