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;
|