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
CodeQL / Analyze (javascript) (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
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;
|