2025-11-10 16:28:01 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
Input,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Badge
|
|
|
|
|
|
} from 'antd';
|
|
|
|
|
|
import {
|
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
|
UserOutlined,
|
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
|
HomeOutlined,
|
2026-06-04 16:25:38 +08:00
|
|
|
|
BellOutlined,
|
|
|
|
|
|
ReadOutlined,
|
|
|
|
|
|
LoginOutlined,
|
2026-06-05 16:27:19 +08:00
|
|
|
|
FundProjectionScreenOutlined,
|
2025-11-10 16:28:01 +08:00
|
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
|
import { history, useLocation, useModel } from '@umijs/max';
|
2026-06-04 16:25:38 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ensureJobPortalLogin,
|
2026-06-04 18:47:59 +08:00
|
|
|
|
getGetInfoCache,
|
2026-06-04 16:25:38 +08:00
|
|
|
|
isJobPortalLoggedIn,
|
|
|
|
|
|
PORTAL_LOGIN_URL,
|
2026-06-04 18:47:59 +08:00
|
|
|
|
prefetchJobPortalUserInfo,
|
2026-06-04 16:25:38 +08:00
|
|
|
|
} from '@/utils/jobPortalAuth';
|
2025-11-10 16:28:01 +08:00
|
|
|
|
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';
|
2026-06-04 16:25:38 +08:00
|
|
|
|
import portalLogo from '@/assets/logo.png';
|
2025-11-10 16:28:01 +08:00
|
|
|
|
import './index.less';
|
|
|
|
|
|
|
|
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
/** 人社门户首页(顶部 Logo 点击跳转) */
|
|
|
|
|
|
const PORTAL_HOME_URL = 'http://218.31.252.15:9081/hrss-web-vue/home';
|
|
|
|
|
|
|
2025-11-10 16:28:01 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-06-04 18:47:59 +08:00
|
|
|
|
// 获取用户名,优先 initialState,其次 getInfo 缓存,最后 appUser 缓存
|
2025-11-10 16:28:01 +08:00
|
|
|
|
const getUserName = (): string => {
|
|
|
|
|
|
if (currentUser?.nickName) {
|
|
|
|
|
|
return currentUser.nickName;
|
|
|
|
|
|
}
|
2026-06-04 18:47:59 +08:00
|
|
|
|
const getInfoUser = getGetInfoCache()?.user;
|
|
|
|
|
|
if (getInfoUser?.nickName) {
|
|
|
|
|
|
return String(getInfoUser.nickName);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (getInfoUser?.userName) {
|
|
|
|
|
|
return String(getInfoUser.userName);
|
|
|
|
|
|
}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
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();
|
2026-06-04 16:25:38 +08:00
|
|
|
|
const loggedIn = isJobPortalLoggedIn();
|
2025-11-10 16:28:01 +08:00
|
|
|
|
|
2026-06-04 18:47:59 +08:00
|
|
|
|
// SSO 仅有 token、本地尚无 userInfo 时预拉求职者资料
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (loggedIn) {
|
|
|
|
|
|
prefetchJobPortalUserInfo();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [loggedIn]);
|
|
|
|
|
|
|
2025-11-10 16:28:01 +08:00
|
|
|
|
// 判断激活导航(简化为 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');
|
2026-06-04 16:25:38 +08:00
|
|
|
|
const isPolicy = location.pathname.startsWith('/job-portal/policy');
|
2026-06-05 16:27:19 +08:00
|
|
|
|
const isCareerRecommend = location.pathname.startsWith('/job-portal/career-recommendation');
|
2025-11-10 16:28:01 +08:00
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
// 获取未读消息数量(仅登录用户)
|
2025-11-10 16:28:01 +08:00
|
|
|
|
useEffect(() => {
|
2026-06-04 16:25:38 +08:00
|
|
|
|
if (!loggedIn) {
|
|
|
|
|
|
setUnreadCount(0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-10 16:28:01 +08:00
|
|
|
|
const fetchUnreadCount = async () => {
|
2026-06-04 16:25:38 +08:00
|
|
|
|
if (!isJobPortalLoggedIn()) {
|
|
|
|
|
|
setUnreadCount(0);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
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);
|
|
|
|
|
|
};
|
2026-06-04 16:25:38 +08:00
|
|
|
|
}, [loggedIn]);
|
2025-11-10 16:28:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 背景图片配置
|
|
|
|
|
|
const backgroundImages = [topBg1, topBg2, topBg3, topBg4];
|
|
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
// 热门职位(兜底)
|
|
|
|
|
|
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,
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
2025-11-10 16:28:01 +08:00
|
|
|
|
|
|
|
|
|
|
// 背景图轮播效果
|
|
|
|
|
|
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);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
const handleGoToLogin = () => {
|
|
|
|
|
|
window.location.href = PORTAL_LOGIN_URL;
|
2025-11-10 16:28:01 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
/** 需登录的导航:未登录弹窗提示 */
|
|
|
|
|
|
const handleAuthNavClick = (path: string, actionHint: string) => {
|
|
|
|
|
|
if (!ensureJobPortalLogin(actionHint)) {
|
|
|
|
|
|
return;
|
2025-11-10 16:28:01 +08:00
|
|
|
|
}
|
2026-06-04 16:25:38 +08:00
|
|
|
|
handleNavClick(path);
|
2025-11-10 16:28:01 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
|
2026-06-04 16:25:38 +08:00
|
|
|
|
{/* 顶部栏:问候 + 导航 + 快捷操作 */}
|
|
|
|
|
|
<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>
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</div>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
<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>
|
2026-06-09 16:08:39 +08:00
|
|
|
|
{/* <Button
|
2026-06-05 16:27:19 +08:00
|
|
|
|
type="text"
|
|
|
|
|
|
icon={<FundProjectionScreenOutlined />}
|
|
|
|
|
|
onClick={() => handleNavClick('/job-portal/career-recommendation')}
|
|
|
|
|
|
className={`nav-btn${isCareerRecommend ? ' active' : ''}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
职业推荐
|
2026-06-09 16:08:39 +08:00
|
|
|
|
</Button> */}
|
2026-06-04 16:25:38 +08:00
|
|
|
|
<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">
|
2025-11-10 16:28:01 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="text"
|
2026-06-04 16:25:38 +08:00
|
|
|
|
icon={<BellOutlined />}
|
|
|
|
|
|
onClick={() => handleAuthNavClick('/job-portal/message', '查看消息')}
|
|
|
|
|
|
className={`nav-btn${isMessage ? ' active' : ''}`}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
消息
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</Button>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
</Badge>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
<div className="utility-actions">
|
|
|
|
|
|
{loggedIn ? (
|
2025-11-10 16:28:01 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="text"
|
2026-06-04 16:25:38 +08:00
|
|
|
|
icon={<UserOutlined />}
|
|
|
|
|
|
onClick={() => handleNavClick('/job-portal/personal-center')}
|
|
|
|
|
|
className={`nav-btn user-btn${isMine ? ' active' : ''}`}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
{userName}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</Button>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
) : (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
icon={<LoginOutlined />}
|
|
|
|
|
|
onClick={handleGoToLogin}
|
|
|
|
|
|
className="nav-btn login-btn"
|
2025-11-10 16:28:01 +08:00
|
|
|
|
>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
登录
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{showSearch && (
|
2026-06-04 16:25:38 +08:00
|
|
|
|
<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>
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</div>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
|
|
|
|
|
|
{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>
|
|
|
|
|
|
)}
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</div>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
</div>
|
2025-11-10 16:28:01 +08:00
|
|
|
|
</div>
|
2026-06-04 16:25:38 +08:00
|
|
|
|
</div>
|
2025-11-10 16:28:01 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default JobPortalHeader;
|