Files
shz-admin/src/components/JobTitleSelector.tsx

192 lines
8.1 KiB
TypeScript
Raw Normal View History

2025-11-10 16:28:01 +08:00
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;