Files
shz-admin/src/components/JobPortalHeader/index.tsx

348 lines
10 KiB
TypeScript
Raw Normal View History

2025-11-10 16:28:01 +08:00
import React, { useState, useEffect } from 'react';
import {
Input,
Select,
Button,
Typography,
Tag,
Avatar,
Space,
message,
Dropdown,
MenuProps,
Badge
} from 'antd';
import {
SearchOutlined,
EnvironmentOutlined,
UserOutlined,
FileTextOutlined,
HomeOutlined,
LogoutOutlined,
BellOutlined
} from '@ant-design/icons';
import { history, useLocation, useModel } from '@umijs/max';
import { getJobRecommend } from '@/services/common/jobTitle';
import { logout } from '@/services/system/auth';
import { clearSessionToken } from '@/access';
import { setRemoteMenu } from '@/services/session';
import { getMessageTotal } from '@/services/jobportal/user';
import topBg1 from '@/assets/images/top-bg1.png';
import topBg2 from '@/assets/images/top-bg2.png';
import topBg3 from '@/assets/images/top-bg3.png';
import topBg4 from '@/assets/images/top-bg4.png';
import './index.less';
const { Text } = Typography;
const { Option } = Select;
interface JobPortalHeaderProps {
showSearch?: boolean; // 是否显示搜索区域
showHotJobs?: boolean; // 是否显示热门职位
}
const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
showSearch = true,
showHotJobs = true
}) => {
const [searchValue, setSearchValue] = useState<string>('');
const [jobRecommendData, setJobRecommendData] = useState<any>(null);
const [currentBgIndex, setCurrentBgIndex] = useState<number>(0);
const [unreadCount, setUnreadCount] = useState<number>(0);
const location = useLocation();
// 获取当前登录用户信息
const { initialState } = useModel('@@initialState');
const currentUser = initialState?.currentUser;
// 获取用户名,优先使用 initialState其次使用 localStorage 中的 userInfo
const getUserName = (): string => {
if (currentUser?.nickName) {
return currentUser.nickName;
}
// 尝试从 localStorage 获取用户信息
try {
const cachedUserInfo = localStorage.getItem('userInfo');
if (cachedUserInfo) {
const userInfo = JSON.parse(cachedUserInfo);
if (userInfo?.name) {
return userInfo.name;
}
}
} catch (error) {
console.error('读取用户信息失败:', error);
}
return '我的';
};
const userName = getUserName();
// 判断激活导航(简化为 startsWith 匹配)
const isHome = /^\/job-portal(\/(list|detail))?$/.test(location.pathname);
const isResume = location.pathname.startsWith('/job-portal/resume');
const isMine = location.pathname.startsWith('/job-portal/personal-center') || location.pathname.startsWith('/job-portal/profile');
const isMessage = location.pathname.startsWith('/job-portal/message');
// 获取未读消息数量
useEffect(() => {
const fetchUnreadCount = async () => {
try {
const response = await getMessageTotal();
if (response?.code === 200 && response?.data) {
setUnreadCount(response.data.wdxx || 0);
}
} catch (error) {
// 静默处理错误,不影响用户体验
console.error('获取未读消息数量失败:', error);
}
};
// 监听消息数量更新事件
const handleMessageCountUpdate = () => {
fetchUnreadCount();
};
window.addEventListener('messageCountUpdated', handleMessageCountUpdate);
// 每30秒刷新一次未读消息数量
fetchUnreadCount();
const interval = setInterval(fetchUnreadCount, 30000);
return () => {
clearInterval(interval);
window.removeEventListener('messageCountUpdated', handleMessageCountUpdate);
};
}, []);
// 背景图片配置
const backgroundImages = [topBg1, topBg2, topBg3, topBg4];
// 热门职位
const hotJobs = ['Java', '产品经理', '前端开发工程师', '测试工程师', '运维工程师', '数据分析师', '平面设计'];
// 背景图轮播效果
useEffect(() => {
const interval = setInterval(() => {
setCurrentBgIndex((prevIndex) => (prevIndex + 1) % backgroundImages.length);
}, 2000); // 每2秒切换一次
return () => clearInterval(interval);
}, [backgroundImages.length]);
// 获取推荐职位数据
React.useEffect(() => {
const fetchJobRecommendData = async () => {
try {
const response = await getJobRecommend();
setJobRecommendData(response);
} catch (error) {
// 静默处理错误,不影响用户体验
}
};
if (showHotJobs) {
fetchJobRecommendData();
}
}, [showHotJobs]);
// 处理搜索功能
const handleSearch = () => {
if (searchValue.trim()) {
// 跳转到职位列表页面,传递搜索参数
history.push(`/job-portal/list`, { queryParams: { name: searchValue.trim() } });
} else {
message.warning('请输入搜索关键词');
}
};
// 处理搜索框输入
const handleSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
};
// 处理回车搜索
const handleSearchKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSearch();
}
};
// 处理热门职位点击
const handleHotJobClick = (job: string) => {
history.push(`/job-portal/list`, { queryParams: { name: job } });
};
// 处理导航点击
2026-05-29 15:23:27 +08:00
const handleNavClick = (path: string, wl: boolean = false) => {
if (wl) {
window.open(path, '_blank');
return;
}
2025-11-10 16:28:01 +08:00
history.push(path);
};
// 退出登录
const handleLogout = async () => {
try {
await logout();
clearSessionToken();
setRemoteMenu(null);
message.success('退出登录成功');
2026-05-29 15:23:27 +08:00
// history.push('/user/login');
window.location.href = 'http://218.31.252.15:9081/hrss-web-vue/home';
2025-11-10 16:28:01 +08:00
} catch (error) {
message.error('退出登录失败');
}
};
// 下拉菜单点击处理
const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
if (key === 'logout') {
handleLogout();
} else if (key === 'personal-center') {
handleNavClick('/job-portal/personal-center');
}
};
// 下拉菜单项
const menuItems: MenuProps['items'] = [
{
key: 'personal-center',
icon: <UserOutlined />,
label: '个人中心',
},
{
type: 'divider',
},
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
danger: true,
},
];
return (
<div className="job-portal-header">
{/* 背景图片轮播 */}
<div className="background-carousel">
{backgroundImages.map((image, index) => (
<div
key={index}
className={`background-slide ${index === currentBgIndex ? 'active' : ''}`}
style={{
backgroundImage: `url(${image})`
}}
/>
))}
</div>
{/* 顶部导航栏 */}
<div className="header-nav">
<div className="nav-container">
<div className="nav-left">
2026-05-29 15:23:27 +08:00
{/* <div className="logo" onClick={() => handleNavClick('/job-portal')}> */}
<div className="logo" onClick={() => handleNavClick('http://218.31.252.15:9081/hrss-web-vue/home', true)}>
<span className="logo-text"></span>
2025-11-10 16:28:01 +08:00
</div>
</div>
<div className="nav-right">
<Space size="large">
<Button
type="text"
icon={<HomeOutlined />}
onClick={() => handleNavClick('/job-portal')}
className={`nav-btn${isHome?' active':''}`}
>
</Button>
<Button
type="text"
icon={<FileTextOutlined />}
onClick={() => handleNavClick('/job-portal/resume')}
className={`nav-btn${isResume?' active':''}`}
>
</Button>
<Badge count={unreadCount > 0 ? unreadCount : 0} offset={[10, 0]} size="small">
<Button
type="text"
icon={<BellOutlined />}
onClick={() => handleNavClick('/job-portal/message')}
className={`nav-btn${isMessage?' active':''}`}
>
</Button>
</Badge>
<Dropdown
menu={{
items: menuItems,
onClick: handleMenuClick,
}}
placement="bottomRight"
>
<Button
type="text"
icon={<UserOutlined />}
className={`nav-btn user-btn${isMine?' active':''}`}
>
{userName}
</Button>
</Dropdown>
</Space>
</div>
</div>
</div>
{/* 搜索区域 */}
{showSearch && (
<div className="search-section">
<div className="search-container">
<div className="search-bar">
<Select
defaultValue="职位类型"
style={{ width: 120 }}
bordered={false}
>
<Option value="job-type"></Option>
</Select>
<Input
placeholder="搜索职位、公司"
style={{ flex: 1, border: 'none' }}
bordered={false}
value={searchValue}
onChange={handleSearchInputChange}
onKeyPress={handleSearchKeyPress}
/>
<Button type="text" icon={<EnvironmentOutlined />} />
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
</Button>
</div>
{/* 热门职位 */}
{showHotJobs && (
<div className="hot-jobs">
<Text strong>:</Text>
<div className="hot-jobs-container">
{(jobRecommendData?.data?.slice(0, 8) || hotJobs.slice(0, 7)).map((job: any, index: number) => (
<Tag
key={job.jobId || job}
className="hot-job-tag"
onClick={() => handleHotJobClick(job.jobTitle || job)}
>
{job.jobTitle || job}
</Tag>
))}
</div>
</div>
)}
</div>
</div>
)}
</div>
);
};
export default JobPortalHeader;