192 lines
8.1 KiB
TypeScript
192 lines
8.1 KiB
TypeScript
|
|
import React, { useEffect, useState } from 'react';
|
|||
|
|
import { Modal, Input, Row, Col, Spin, message } from 'antd';
|
|||
|
|
import type { ModalProps } from 'antd';
|
|||
|
|
import { request } from '@umijs/max';
|
|||
|
|
|
|||
|
|
interface JobTitle {
|
|||
|
|
id: number;
|
|||
|
|
label: string;
|
|||
|
|
children?: JobTitle[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface JobTitleSelectorProps extends Omit<ModalProps, 'onOk'|'onCancel'> {
|
|||
|
|
open: boolean;
|
|||
|
|
value?: {id:number, label:string};
|
|||
|
|
onOk: (job: {id:number, label:string})=>void;
|
|||
|
|
onCancel: ()=>void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const JobTitleSelector: React.FC<JobTitleSelectorProps> = ({ open, onOk, onCancel, value }) => {
|
|||
|
|
const [loading, setLoading] = useState(false);
|
|||
|
|
const [data, setData] = useState<JobTitle[]>([]);
|
|||
|
|
const [search, setSearch] = useState('');
|
|||
|
|
const [filtered, setFiltered] = useState<JobTitle[]>([]); // 筛选后数据
|
|||
|
|
const [selectedCat, setSelectedCat] = useState<JobTitle|null>(null); // 左列选中一级
|
|||
|
|
const [showSubId, setShowSubId] = useState<number|null>(null); // 右列当前展开二级ID
|
|||
|
|
const [selected, setSelected] = useState<{id:number, label:string}|null>(value||null);
|
|||
|
|
|
|||
|
|
useEffect(()=>{
|
|||
|
|
if(open){
|
|||
|
|
setLoading(true);
|
|||
|
|
request('/api/app/common/jobTitle/treeselect').then(res=>{
|
|||
|
|
setData(res.data||[]);
|
|||
|
|
setFiltered(res.data||[]);
|
|||
|
|
setSelectedCat(null);
|
|||
|
|
setShowSubId(null);
|
|||
|
|
setSelected(value||null);
|
|||
|
|
setSearch('');
|
|||
|
|
}).catch(()=>message.error('加载岗位数据失败')).finally(()=>setLoading(false));
|
|||
|
|
}
|
|||
|
|
},[open]);
|
|||
|
|
|
|||
|
|
// 搜索时过滤全部三级,依然保持原树结构
|
|||
|
|
useEffect(()=>{
|
|||
|
|
if(search&&search.length>0){
|
|||
|
|
// 返回新的树,只在三级岗位匹配关键词的二/一级保留
|
|||
|
|
function filterTree(nodes:JobTitle[]):JobTitle[]{
|
|||
|
|
return nodes.map(cat=>{
|
|||
|
|
// cat: 一级
|
|||
|
|
if(!cat.children)return null;
|
|||
|
|
const subFiltered=cat.children.map(sub=>{
|
|||
|
|
if(!sub.children)return null;
|
|||
|
|
const jobs=sub.children.filter(j=>j.label.includes(search));
|
|||
|
|
if(jobs.length>0){ return {...sub,children:jobs}; }
|
|||
|
|
return null;
|
|||
|
|
}).filter(Boolean) as JobTitle[];
|
|||
|
|
if(subFiltered.length>0)return {...cat,children:subFiltered};
|
|||
|
|
return null;
|
|||
|
|
}).filter(Boolean) as JobTitle[];
|
|||
|
|
}
|
|||
|
|
setFiltered(filterTree(data));
|
|||
|
|
setSelectedCat(null);
|
|||
|
|
setShowSubId(null);
|
|||
|
|
}else{
|
|||
|
|
setFiltered(data);
|
|||
|
|
}
|
|||
|
|
},[search,data]);
|
|||
|
|
|
|||
|
|
// 左列,一级点击切换
|
|||
|
|
const handleCat = (cat:JobTitle) => {
|
|||
|
|
setSelectedCat(cat); setShowSubId(null);
|
|||
|
|
};
|
|||
|
|
// 右列,点二级分类
|
|||
|
|
const handleSub = (sub:JobTitle) => {
|
|||
|
|
setShowSubId(sub.id===showSubId?null:sub.id);
|
|||
|
|
};
|
|||
|
|
// 右列,选三级岗位
|
|||
|
|
const handleJob = (job:JobTitle) => {
|
|||
|
|
setSelected({id:job.id,label:job.label});
|
|||
|
|
};
|
|||
|
|
const handleOk = () => {
|
|||
|
|
if(selected){ onOk(selected); } else { message.warning('请选择岗位'); }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取当前分类下所有二级
|
|||
|
|
const curSubs = selectedCat?.children || [];
|
|||
|
|
|
|||
|
|
// 搜索时过滤所有二级岗位的分组,全局(不再依赖selectedCat)
|
|||
|
|
const getGlobalSearchGroups = ()=>{
|
|||
|
|
if (!search) return [];
|
|||
|
|
const groups: {cat:JobTitle, sub: JobTitle, jobs: JobTitle[]}[] = [];
|
|||
|
|
data.forEach(cat=>{
|
|||
|
|
cat.children?.forEach(sub=>{
|
|||
|
|
if(sub.children){
|
|||
|
|
const jobs=sub.children.filter(j=>j.label.includes(search));
|
|||
|
|
if(jobs.length>0)groups.push({cat,sub,jobs});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
return groups;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Modal open={open} width={800} onCancel={onCancel} onOk={handleOk} title="请选择岗位名称" okText="确定" cancelText="取消">
|
|||
|
|
<Spin spinning={loading}>
|
|||
|
|
<Input
|
|||
|
|
placeholder="请输入职位关键词"
|
|||
|
|
allowClear
|
|||
|
|
value={search}
|
|||
|
|
onChange={e=>setSearch(e.target.value)}
|
|||
|
|
style={{marginBottom:10,width:280}}
|
|||
|
|
/>
|
|||
|
|
<div style={{display:'flex',minHeight:410, maxHeight:520, overflow:'hidden'}}>
|
|||
|
|
{/* 左侧一级分类 */}
|
|||
|
|
<div style={{width:180,borderRight:'1px solid #f0f0f0',overflowY:'auto',paddingTop:2,maxHeight:500}}>
|
|||
|
|
{filtered.map(cat=>(
|
|||
|
|
<div
|
|||
|
|
key={cat.id}
|
|||
|
|
style={{padding:'10px 16px', cursor:'pointer', background:selectedCat?.id===cat.id?'#e6f7ff':'', fontWeight:selectedCat?.id===cat.id?'bold':undefined}}
|
|||
|
|
onClick={()=>handleCat(cat)}
|
|||
|
|
>{cat.label}</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
{/* 右侧内容区优化 */}
|
|||
|
|
<div style={{flex:1, padding:'16px 20px 10px 20px', minHeight:380, maxHeight:500,overflowY:'auto'}}>
|
|||
|
|
{(search) ? (
|
|||
|
|
(()=>{
|
|||
|
|
const groups = getGlobalSearchGroups();
|
|||
|
|
return groups.length===0?
|
|||
|
|
<div style={{color:'#bbb',textAlign:'center'}}>无相关岗位</div>:
|
|||
|
|
groups.map(({cat,sub,jobs})=>(
|
|||
|
|
<div key={cat.id+'-'+sub.id} style={{marginBottom:16}}>
|
|||
|
|
<div style={{fontWeight:600,margin:'4px 0'}}>
|
|||
|
|
{cat.label} / {sub.label}
|
|||
|
|
</div>
|
|||
|
|
<div style={{display:'flex',flexWrap:'wrap',flexDirection:'row',gap:'8px 6px',marginTop: 8}}>
|
|||
|
|
{jobs.map(job=>(
|
|||
|
|
<div key={job.id}
|
|||
|
|
style={{padding:'6px 18px',borderRadius:16,background:selected?.id===job.id?'#1890ff':'#f6f6f6',color:selected?.id===job.id?'#fff':'#222',cursor:'pointer',whiteSpace:'nowrap',fontSize:14}}
|
|||
|
|
onClick={()=>handleJob(job)}
|
|||
|
|
>{job.label}</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
));
|
|||
|
|
})()
|
|||
|
|
) : (
|
|||
|
|
!selectedCat?
|
|||
|
|
<div style={{color:'#bbb',padding:'100px 0',textAlign:'center'}}>请先选择左侧所属行业类别</div>:
|
|||
|
|
<div>
|
|||
|
|
{curSubs.length===0?
|
|||
|
|
<div style={{color:'#bbb',textAlign:'center'}}>暂无子分类</div>:
|
|||
|
|
<div style={{overflow:'visible',maxWidth:'100%'}}>
|
|||
|
|
<div style={{display:'flex',flexWrap:'wrap',paddingBottom:12,gap:'0 36px'}}>
|
|||
|
|
{curSubs.map(sub=>(
|
|||
|
|
<div key={sub.id} style={{marginBottom:16, minWidth:150}}>
|
|||
|
|
<div
|
|||
|
|
style={{display:'flex',alignItems:'center', cursor:sub.children?'pointer':'default',fontWeight:600,padding:'4px 0',color:showSubId===sub.id?'#1890ff':'#333'}}
|
|||
|
|
onClick={()=>sub.children&&handleSub(sub)}
|
|||
|
|
>
|
|||
|
|
<span>{sub.label}</span>
|
|||
|
|
{sub.children && (
|
|||
|
|
<span style={{fontSize:20, color:'#999', marginLeft:4, fontWeight:'bold'}}>
|
|||
|
|
{showSubId===sub.id?'-':'+'}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
{/* 只在当前展开的二级下 横向显示三级 */}
|
|||
|
|
{showSubId===sub.id&&sub.children&&(
|
|||
|
|
<div style={{display:'flex',flexWrap:'wrap',flexDirection:'row',gap:'8px 6px',marginTop: 8}}>
|
|||
|
|
{sub.children.map(job=>(
|
|||
|
|
<div key={job.id}
|
|||
|
|
style={{padding:'6px 18px',borderRadius:16,background:selected?.id===job.id?'#1890ff':'#f6f6f6',color:selected?.id===job.id?'#fff':'#222',cursor:'pointer',whiteSpace:'nowrap',fontSize:14}}
|
|||
|
|
onClick={()=>handleJob(job)}
|
|||
|
|
>{job.label}</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</Spin>
|
|||
|
|
</Modal>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
export default JobTitleSelector;
|