Some checks are pending
Node CI / build (14.x, macOS-latest) (push) Waiting to run
Node CI / build (14.x, ubuntu-latest) (push) Waiting to run
Node CI / build (14.x, windows-latest) (push) Waiting to run
Node CI / build (16.x, macOS-latest) (push) Waiting to run
Node CI / build (16.x, ubuntu-latest) (push) Waiting to run
Node CI / build (16.x, windows-latest) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
coverage CI / build (push) Waiting to run
Node pnpm CI / build (16.x, macOS-latest) (push) Waiting to run
Node pnpm CI / build (16.x, ubuntu-latest) (push) Waiting to run
Node pnpm CI / build (16.x, windows-latest) (push) Waiting to run
393 lines
12 KiB
TypeScript
393 lines
12 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import {
|
||
Input,
|
||
Select,
|
||
Button,
|
||
Tag,
|
||
message,
|
||
Badge
|
||
} from 'antd';
|
||
import {
|
||
SearchOutlined,
|
||
UserOutlined,
|
||
FileTextOutlined,
|
||
HomeOutlined,
|
||
BellOutlined,
|
||
ReadOutlined,
|
||
LoginOutlined,
|
||
FundProjectionScreenOutlined,
|
||
} from '@ant-design/icons';
|
||
import { history, useLocation, useModel } from '@umijs/max';
|
||
import {
|
||
ensureJobPortalLogin,
|
||
getGetInfoCache,
|
||
isJobPortalLoggedIn,
|
||
PORTAL_LOGIN_URL,
|
||
prefetchJobPortalUserInfo,
|
||
} from '@/utils/jobPortalAuth';
|
||
import { getJobRecommend } from '@/services/common/jobTitle';
|
||
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 portalLogo from '@/assets/logo.png';
|
||
import './index.less';
|
||
|
||
const { Option } = Select;
|
||
|
||
/** 人社门户首页(顶部 Logo 点击跳转) */
|
||
const PORTAL_HOME_URL = 'http://218.31.252.15:9081/hrss-web-vue/home';
|
||
|
||
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,其次 getInfo 缓存,最后 appUser 缓存
|
||
const getUserName = (): string => {
|
||
if (currentUser?.nickName) {
|
||
return currentUser.nickName;
|
||
}
|
||
const getInfoUser = getGetInfoCache()?.user;
|
||
if (getInfoUser?.nickName) {
|
||
return String(getInfoUser.nickName);
|
||
}
|
||
if (getInfoUser?.userName) {
|
||
return String(getInfoUser.userName);
|
||
}
|
||
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();
|
||
const loggedIn = isJobPortalLoggedIn();
|
||
|
||
// SSO 仅有 token、本地尚无 userInfo 时预拉求职者资料
|
||
useEffect(() => {
|
||
if (loggedIn) {
|
||
prefetchJobPortalUserInfo();
|
||
}
|
||
}, [loggedIn]);
|
||
|
||
// 判断激活导航(简化为 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');
|
||
const isPolicy = location.pathname.startsWith('/job-portal/policy');
|
||
const isCareerRecommend = location.pathname.startsWith('/job-portal/career-recommendation');
|
||
|
||
// 获取未读消息数量(仅登录用户)
|
||
useEffect(() => {
|
||
if (!loggedIn) {
|
||
setUnreadCount(0);
|
||
return;
|
||
}
|
||
|
||
const fetchUnreadCount = async () => {
|
||
if (!isJobPortalLoggedIn()) {
|
||
setUnreadCount(0);
|
||
return;
|
||
}
|
||
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);
|
||
|
||
fetchUnreadCount();
|
||
const interval = setInterval(fetchUnreadCount, 30000);
|
||
|
||
return () => {
|
||
clearInterval(interval);
|
||
window.removeEventListener('messageCountUpdated', handleMessageCountUpdate);
|
||
};
|
||
}, [loggedIn]);
|
||
|
||
// 背景图片配置
|
||
const backgroundImages = [topBg1, topBg2, topBg3, topBg4];
|
||
|
||
// 热门职位(兜底)
|
||
const hotJobs = ['Java', '产品经理', '前端开发', '测试工程师', '运维工程师', '数据分析'];
|
||
|
||
const HOT_JOB_DISPLAY_MAX = 12;
|
||
const HOT_JOB_SHOW_COUNT = 6;
|
||
|
||
const formatHotJobLabel = (title: string) => {
|
||
const text = (title || '').trim();
|
||
if (text.length <= HOT_JOB_DISPLAY_MAX) return text;
|
||
return `${text.slice(0, HOT_JOB_DISPLAY_MAX)}…`;
|
||
};
|
||
|
||
const getHotJobItems = (): { key: string | number; title: string }[] => {
|
||
if (jobRecommendData?.data?.length) {
|
||
return jobRecommendData.data.slice(0, HOT_JOB_SHOW_COUNT).map((job: any) => ({
|
||
key: job.jobId ?? job.jobTitle,
|
||
title: job.jobTitle || '',
|
||
}));
|
||
}
|
||
return hotJobs.slice(0, HOT_JOB_SHOW_COUNT).map((title) => ({
|
||
key: title,
|
||
title,
|
||
}));
|
||
};
|
||
|
||
// 背景图轮播效果
|
||
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 } });
|
||
};
|
||
|
||
// 处理导航点击
|
||
const handleNavClick = (path: string, wl: boolean = false) => {
|
||
if (wl) {
|
||
window.open(path, '_blank');
|
||
return;
|
||
}
|
||
history.push(path);
|
||
};
|
||
|
||
const handleGoToLogin = () => {
|
||
window.location.href = PORTAL_LOGIN_URL;
|
||
};
|
||
|
||
/** 需登录的导航:未登录弹窗提示 */
|
||
const handleAuthNavClick = (path: string, actionHint: string) => {
|
||
if (!ensureJobPortalLogin(actionHint)) {
|
||
return;
|
||
}
|
||
handleNavClick(path);
|
||
};
|
||
|
||
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="top-utility-bar">
|
||
<div className="utility-container">
|
||
<div className="header-brand" onClick={() => { window.location.href = PORTAL_HOME_URL; }}>
|
||
<img className="header-logo" src={portalLogo} alt="石城智慧就业" />
|
||
<span className="header-title">石城智慧就业</span>
|
||
</div>
|
||
<nav className="top-nav">
|
||
<Button
|
||
type="text"
|
||
icon={<HomeOutlined />}
|
||
onClick={() => handleNavClick('/job-portal')}
|
||
className={`nav-btn${isHome ? ' active' : ''}`}
|
||
>
|
||
找工作
|
||
</Button>
|
||
<Button
|
||
type="text"
|
||
icon={<FileTextOutlined />}
|
||
onClick={() => handleAuthNavClick('/job-portal/resume', '查看简历')}
|
||
className={`nav-btn${isResume ? ' active' : ''}`}
|
||
>
|
||
简历
|
||
</Button>
|
||
{/* <Button
|
||
type="text"
|
||
icon={<FundProjectionScreenOutlined />}
|
||
onClick={() => handleNavClick('/job-portal/career-recommendation')}
|
||
className={`nav-btn${isCareerRecommend ? ' active' : ''}`}
|
||
>
|
||
职业推荐
|
||
</Button> */}
|
||
<Button
|
||
type="text"
|
||
icon={<ReadOutlined />}
|
||
onClick={() => handleNavClick('/job-portal/policy')}
|
||
className={`nav-btn${isPolicy ? ' active' : ''}`}
|
||
>
|
||
政策
|
||
</Button>
|
||
<Badge count={unreadCount > 0 ? unreadCount : 0} offset={[8, 0]} size="small">
|
||
<Button
|
||
type="text"
|
||
icon={<BellOutlined />}
|
||
onClick={() => handleAuthNavClick('/job-portal/message', '查看消息')}
|
||
className={`nav-btn${isMessage ? ' active' : ''}`}
|
||
>
|
||
消息
|
||
</Button>
|
||
</Badge>
|
||
</nav>
|
||
<div className="utility-actions">
|
||
{loggedIn ? (
|
||
<Button
|
||
type="text"
|
||
icon={<UserOutlined />}
|
||
onClick={() => handleNavClick('/job-portal/personal-center')}
|
||
className={`nav-btn user-btn${isMine ? ' active' : ''}`}
|
||
>
|
||
{userName}
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
type="text"
|
||
icon={<LoginOutlined />}
|
||
onClick={handleGoToLogin}
|
||
className="nav-btn login-btn"
|
||
>
|
||
登录
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{showSearch && (
|
||
<div className="banner-section">
|
||
<div className="banner-inner">
|
||
<div className="search-section">
|
||
<div className="search-panel">
|
||
<div className="search-bar">
|
||
<Select
|
||
defaultValue="job-type"
|
||
className="search-type-select"
|
||
popupMatchSelectWidth={false}
|
||
bordered={false}
|
||
>
|
||
<Option value="job-type">找工作</Option>
|
||
</Select>
|
||
<span className="search-divider" />
|
||
<Input
|
||
className="search-input"
|
||
placeholder="搜索职位、公司"
|
||
bordered={false}
|
||
value={searchValue}
|
||
onChange={handleSearchInputChange}
|
||
onKeyPress={handleSearchKeyPress}
|
||
/>
|
||
<Button
|
||
type="primary"
|
||
className="search-submit"
|
||
icon={<SearchOutlined />}
|
||
onClick={handleSearch}
|
||
>
|
||
搜索
|
||
</Button>
|
||
</div>
|
||
|
||
{showHotJobs && (
|
||
<div className="hot-jobs">
|
||
<span className="hot-jobs-label">热门职位</span>
|
||
<div className="hot-jobs-container">
|
||
{getHotJobItems().map((item) => (
|
||
<Tag
|
||
key={item.key}
|
||
className="hot-job-tag"
|
||
title={item.title}
|
||
onClick={() => handleHotJobClick(item.title)}
|
||
>
|
||
{formatHotJobLabel(item.title)}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default JobPortalHeader;
|