feat: add job suggestion feature with autocomplete in JobPortalHeader
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled

This commit is contained in:
2026-07-16 10:13:01 +08:00
parent 219a945668
commit 4d48d33476
3 changed files with 99 additions and 13 deletions

View File

@@ -245,6 +245,22 @@
margin: 0 4px;
}
.search-input-autocomplete {
flex: 1;
min-width: 0;
.ant-select-selector {
border: none !important;
box-shadow: none !important;
background: transparent !important;
padding: 0 !important;
}
.search-input {
width: 100%;
}
}
.search-input {
flex: 1;
min-width: 0;
@@ -341,6 +357,14 @@
}
}
.job-suggestion-option {
padding: 4px 2px;
color: @jp-text-primary;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 768px) {
.job-portal-header {
.top-utility-bar .utility-container {

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
AutoComplete,
Input,
Select,
Button,
@@ -26,7 +27,7 @@ import {
PORTAL_LOGIN_URL,
prefetchJobPortalUserInfo,
} from '@/utils/jobPortalAuth';
import { getJobRecommend } from '@/services/common/jobTitle';
import { getJobRecommend, getJobSuggestions } 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';
@@ -53,6 +54,9 @@ const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
const [jobRecommendData, setJobRecommendData] = useState<any>(null);
const [currentBgIndex, setCurrentBgIndex] = useState<number>(0);
const [unreadCount, setUnreadCount] = useState<number>(0);
const [suggestions, setSuggestions] = useState<string[]>([]);
const [suggestionsLoading, setSuggestionsLoading] = useState<boolean>(false);
const suggestionRequestId = useRef(0);
const location = useLocation();
@@ -195,10 +199,12 @@ const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
}, [showHotJobs]);
// 处理搜索功能
const handleSearch = () => {
if (searchValue.trim()) {
const handleSearch = (value?: string) => {
const keyword = (value ?? searchValue).trim();
if (keyword) {
setSuggestions([]);
// 跳转到职位列表页面,传递搜索参数
history.push(`/job-portal/list`, { queryParams: { name: searchValue.trim() } });
history.push(`/job-portal/list`, { queryParams: { name: keyword } });
} else {
message.warning('请输入搜索关键词');
}
@@ -209,6 +215,40 @@ const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
setSearchValue(e.target.value);
};
// 输入停止后请求岗位联想,避免每个字符都立即请求后端。
useEffect(() => {
const keyword = searchValue.trim();
const requestId = ++suggestionRequestId.current;
if (!keyword) {
setSuggestions([]);
setSuggestionsLoading(false);
return;
}
const timer = window.setTimeout(async () => {
setSuggestionsLoading(true);
try {
const response = await getJobSuggestions(keyword, 10);
if (requestId !== suggestionRequestId.current) {
return;
}
const data = Array.isArray(response?.data) ? response.data : [];
setSuggestions(data.filter((item): item is string => typeof item === 'string' && item.trim().length > 0));
} catch (error) {
if (requestId === suggestionRequestId.current) {
setSuggestions([]);
}
} finally {
if (requestId === suggestionRequestId.current) {
setSuggestionsLoading(false);
}
}
}, 250);
return () => window.clearTimeout(timer);
}, [searchValue]);
// 处理回车搜索
const handleSearchKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
@@ -216,6 +256,11 @@ const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
}
};
const suggestionOptions = suggestions.map((suggestion) => ({
value: suggestion,
label: <div className="job-suggestion-option">{suggestion}</div>,
}));
// 处理热门职位点击
const handleHotJobClick = (job: string) => {
history.push(`/job-portal/list`, { queryParams: { name: job } });
@@ -355,19 +400,28 @@ const JobPortalHeader: React.FC<JobPortalHeaderProps> = ({
<Option value="job-type"></Option>
</Select>
<span className="search-divider" />
<Input
className="search-input"
placeholder="搜索职位、公司"
bordered={false}
<AutoComplete
className="search-input-autocomplete"
value={searchValue}
onChange={handleSearchInputChange}
onKeyPress={handleSearchKeyPress}
/>
options={suggestionOptions}
onChange={(value) => setSearchValue(value)}
onSelect={(value) => handleSearch(value)}
popupMatchSelectWidth={false}
notFoundContent={suggestionsLoading ? '正在搜索...' : null}
>
<Input
className="search-input"
placeholder="搜索职位、公司"
bordered={false}
onChange={handleSearchInputChange}
onKeyDown={handleSearchKeyPress}
/>
</AutoComplete>
<Button
type="primary"
className="search-submit"
icon={<SearchOutlined />}
onClick={handleSearch}
onClick={() => handleSearch()}
>
</Button>

View File

@@ -18,6 +18,14 @@ export async function getJobRecommend(params?: { jobTitle?: string }) {
});
}
/** 获取岗位名称联想 GET /api/app/job/suggest */
export async function getJobSuggestions(keyword: string, limit = 10) {
return request<{ code: number; msg?: string; data?: string[] }>('/api/app/job/suggest', {
method: 'GET',
params: { keyword, limit },
});
}
// 获取职位列表数据
export async function getJobList(jobCategory: string) {
return request('/api/cms/job/recommend', {