Files
shz-admin/src/components/JobPortalHeader/index.tsx
francis-fh f2da3d6929
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
二次调研功能开发
2026-06-09 16:08:39 +08:00

393 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;