diff --git a/config/routes.ts b/config/routes.ts index ad6c04b..f377afc 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -219,6 +219,17 @@ export default [ }, ], }, + { + name: 'talentcollect', + path: '/talentcollect', + routes: [ + { + name: '人才库', + path: '/talentcollect/list', + component: './Talentcollect/List', + }, + ], + }, { name: 'company', path: '/company', diff --git a/src/pages/Mobileusers/List/CollectTalent.tsx b/src/pages/Mobileusers/List/CollectTalent.tsx new file mode 100644 index 0000000..8f02c51 --- /dev/null +++ b/src/pages/Mobileusers/List/CollectTalent.tsx @@ -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 = (props) => { + const { open, userId } = props; + + const [collectForm] = Form.useForm(); + const [submitting, setSubmitting] = useState(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 ( + props.onClose()} + title="收藏求职者" + width={500} + footer={[ + , + , + ]} + destroyOnClose + > +
+ + + + + + + +
+
+ ); +}; + +export default CollectTalent; diff --git a/src/pages/Mobileusers/List/index.tsx b/src/pages/Mobileusers/List/index.tsx index 32fab8b..d0691a7 100644 --- a/src/pages/Mobileusers/List/index.tsx +++ b/src/pages/Mobileusers/List/index.tsx @@ -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([]); const [currentRow, setCurrentRow] = useState(); const [modalVisible, setModalVisible] = useState(false); + const [inviteVisible, setInviteVisible] = useState(false); + const [collectVisible, setCollectVisible] = useState(false); + const [collectMap, setCollectMap] = useState>(new Map()); const [isPublishEnum, setIsPublishEnum] = useState([]); 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(); + 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() { ), }, + { + title: '操作', + hideInSearch: true, + align: 'center', + dataIndex: 'jobId', + width: 200, + render: (_, record) => { + const isCollected = collectMap.has(record.userId); + return [ + , + isCollected ? ( + + ) : ( + + ), + ]; + }, + }, ]; return ( @@ -235,6 +329,33 @@ function ManagementList() { , ]} /> + { + setInviteVisible(false); + setCurrentRow(undefined); + if (needRefresh) { + actionRef.current?.reload(); + } + }} + /> + { + setCollectVisible(false); + setCurrentRow(undefined); + if (needRefresh) { + fetchCollectList(); + } + }} + /> ); diff --git a/src/pages/Talentcollect/List/index.tsx b/src/pages/Talentcollect/List/index.tsx new file mode 100644 index 0000000..9bcc735 --- /dev/null +++ b/src/pages/Talentcollect/List/index.tsx @@ -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(); + const actionRef = useRef(); + + const [educationEnum, setEducationEnum] = useState({}); + const [areaEnum, setAreaEnum] = useState({}); + const [sexEnum, setSexEnum] = useState({}); + + 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 = { + '1': { text: '高意向', color: 'red' }, + '2': { text: '中等意向', color: 'blue' }, + '3': { text: '低意向', color: 'default' }, + }; + + const sourceTypeMap: Record = { + '1': '简历搜索', + '2': '投递记录', + '3': '邀约面试', + '4': '手动录入', + }; + + const columns: ProColumns[] = [ + { + 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 {info.text}; + }, + }, + { + 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.trim()} + + ))} + + ); + }, + }, + { + 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 ; + }, + }, + { + title: '区域', + dataIndex: 'area', + valueType: 'select', + align: 'center', + valueEnum: areaEnum, + render: (_, record) => { + return ; + }, + }, + { + title: '性别', + dataIndex: 'sex', + valueType: 'select', + align: 'center', + valueEnum: sexEnum, + render: (_, record) => { + return ; + }, + }, + { + title: '收藏时间', + dataIndex: 'createTime', + valueType: 'dateTime', + align: 'center', + hideInSearch: true, + }, + { + title: '操作', + hideInSearch: true, + align: 'center', + dataIndex: 'userId', + width: 120, + render: (_, record) => [ + , + ], + }, + ]; + + return ( + +
+ + search={{ labelWidth: 'auto' }} + actionRef={actionRef} + formRef={formTableRef} + rowKey="id" + key="talentCollect" + columns={columns} + request={(params) => { + const { current, pageSize, ...searchParams } = params; + const filteredParams: Record = {}; + 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; + }); + }} + /> +
+
+ ); +} + +export default TalentCollectList; diff --git a/src/services/mobileusers/list.ts b/src/services/mobileusers/list.ts index 97f2383..80137b8 100644 --- a/src/services/mobileusers/list.ts +++ b/src/services/mobileusers/list.ts @@ -28,3 +28,33 @@ export async function applyAgree(params: { id: string; hire: number }) { data: params, }); } + +// ========== 企业人才收藏 APIs ========== + +/** 获取当前企业人才收藏列表 */ +export async function getHrTalentCollectList(params?: Record) { + return request('/api/cms/hrTalentCollect/list', { + method: 'GET', + params: params, + }); +} + +/** 收藏求职者(加入人才库) */ +export async function addHrTalentCollect(data: { + userId: string; + intentionLevel: string; + collectTags?: string; + sourceType: string; +}) { + return request('/api/cms/hrTalentCollect', { + method: 'POST', + data: data, + }); +} + +/** 取消收藏(移出人才库,路径参数为userId) */ +export async function delHrTalentCollect(userId: string) { + return request(`/api/cms/hrTalentCollect/${userId}`, { + method: 'DELETE', + }); +} diff --git a/src/types/mobileusers/list.d.ts b/src/types/mobileusers/list.d.ts index 58078ad..cb44d13 100644 --- a/src/types/mobileusers/list.d.ts +++ b/src/types/mobileusers/list.d.ts @@ -9,6 +9,7 @@ declare namespace API.MobileUser { export interface ListRow { applyId?: string; jobId?: string; + companyId?: number; createTime?: string; userId: number; name: string; diff --git a/src/types/talentcollect/list.d.ts b/src/types/talentcollect/list.d.ts new file mode 100644 index 0000000..c753299 --- /dev/null +++ b/src/types/talentcollect/list.d.ts @@ -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; + } +}