diff --git a/config/proxy.ts b/config/proxy.ts index bc9e1ad..6d6cc1e 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -15,8 +15,8 @@ export default { // localhost:8000/api/** -> https://preview.pro.ant.design/api/** '/api/': { // 要代理的地址 - target: 'http://39.98.44.136:6024/api/shihezi/', - // target: 'http://localhost:9091', // 本地代理 + target: 'http://localhost:9091', // 本地开发环境 + // target: 'http://39.98.44.136:6024/api/shihezi/', // 线上环境 // target: 'http://wykj.cdwsx.com/api',// 后端 // target: 'http://ks.zhaopinzao8dian.com/api/ks', // 配置了这个可以从 http 代理到 https @@ -26,20 +26,21 @@ export default { // logLevel: 'debug', }, '/profile/avatar/': { - target: 'http://36.105.163.21:30081/api/ks', // 发版 + target: 'http://localhost:9091', // 本地开发环境 + // target: 'http://36.105.163.21:30081/api/ks', // 发版 // target: 'http://wykj.cdwsx.com/api', // target: 'http://ks.zhaopinzao8dian.com/api/ks', changeOrigin: true, }, '/app/': { // 要代理的地址 - target: 'http://36.105.163.21:30081/api/ks', // 发版 + target: 'http://localhost:9091', // 本地开发环境 + // target: 'http://36.105.163.21:30081/api/ks', // 发版 // target: 'http://wykj.cdwsx.com/api', // target: 'http://ks.zhaopinzao8dian.com/api/ks', // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, - pathRewrite: { '^/api': '' }, }, }, diff --git a/src/pages/JobPortal/Detail/index.tsx b/src/pages/JobPortal/Detail/index.tsx index dd268a6..1fbb121 100644 --- a/src/pages/JobPortal/Detail/index.tsx +++ b/src/pages/JobPortal/Detail/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { useParams, history, useLocation } from '@umijs/max'; +import { useParams, history, useLocation, useSearchParams } from '@umijs/max'; import { Card, Typography, @@ -32,7 +32,7 @@ import { getEmptyCompetitivenessRadar, } from '@/services/jobportal/competitiveness'; import { isJobPortalLoggedIn } from '@/utils/jobPortalAuth'; -import { favoriteJob, unfavoriteJob, applyJob, browseJob } from '@/services/jobportal/user'; +import { favoriteJob, unfavoriteJob, applyJob, browseJob, getJobDetail } from '@/services/jobportal/user'; import { ensureJobPortalLogin, requireJobPortalUserId } from '@/utils/jobPortalAuth'; import { getDictLabel, resolveIndustryLabel } from '@/utils/jobPortalDict'; import { getDictValueEnum } from '@/services/system/dict'; @@ -174,6 +174,7 @@ const mockJobDetail = { const JobDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const location = useLocation(); + const [searchParams] = useSearchParams(); const [jobDetail, setJobDetail] = useState(mockJobDetail); const [loading, setLoading] = useState(false); const [isFavorited, setIsFavorited] = useState(false); @@ -236,18 +237,44 @@ const JobDetailPage: React.FC = () => { const transformedJobData = transformJobData(passedJobData); setJobDetail(transformedJobData); // 根据 isCollection 字段设置收藏状态 - // isCollection 为 null 或 0 表示未收藏,其他值表示已收藏 setIsFavorited(passedJobData?.isCollection !== null && passedJobData?.isCollection !== 0 && passedJobData?.isCollection !== undefined); } else { - // 如果没有传递数据,则根据id从API获取数据 - // fetchJobDetail(id); - // 默认未收藏 - setOriginalJobData(null); - setIsFavorited(false); + // 直接刷新页面时,从 URL 参数或 route param 获取 jobId,调 API 获取数据 + const jobIdFromUrl = searchParams.get('jobId'); + if (jobIdFromUrl) { + fetchJobDetailFromApi(jobIdFromUrl); + } else { + setOriginalJobData(null); + setIsFavorited(false); + } } }, [id, location.state]); + // 页面直接刷新时调接口获取岗位详情 + const fetchJobDetailFromApi = async (jobId: string) => { + try { + setLoading(true); + const res = await getJobDetail(jobId); + if (res?.code === 200 && res?.data) { + const jobData = res.data; + setOriginalJobData(jobData); + const transformed = transformJobData(jobData); + setJobDetail(transformed); + setIsFavorited(jobData?.isCollection !== null && jobData?.isCollection !== 0 && jobData?.isCollection !== undefined); + } else { + setOriginalJobData(null); + setIsFavorited(false); + } + } catch (error) { + console.error('获取岗位详情失败:', error); + setOriginalJobData(null); + setIsFavorited(false); + } finally { + setLoading(false); + } + }; + // 仅登录且有真实岗位 id 时请求竞争力 useEffect(() => { if (!isJobPortalLoggedIn()) { diff --git a/src/pages/JobPortal/Message/index.tsx b/src/pages/JobPortal/Message/index.tsx index 0c59960..0c0323a 100644 --- a/src/pages/JobPortal/Message/index.tsx +++ b/src/pages/JobPortal/Message/index.tsx @@ -117,7 +117,7 @@ const MessagePage: React.FC = () => { // 如果选择了 appointment 分类,需要前端过滤(因为接口参数中没有对应的 noticeType) if (category === 'appointment') { messageList = messageList.filter((msg: API.Message) => { - return msg.noticeType === 'appointment' || msg.noticeType === 'interview'; + return msg.noticeType === 'appointment' || msg.noticeType === 'interview' || msg.noticeType === '4'; }); } @@ -141,7 +141,7 @@ const MessagePage: React.FC = () => { const allMessages = response.rows || []; setCategoryCounts({ job: allMessages.filter((m: API.Message) => m.noticeType === '2').length, - appointment: allMessages.filter((m: API.Message) => m.noticeType === 'appointment' || m.noticeType === 'interview').length, + appointment: allMessages.filter((m: API.Message) => m.noticeType === 'appointment' || m.noticeType === 'interview' || m.noticeType === '4').length, system: allMessages.filter((m: API.Message) => m.noticeType === '1').length }); }); diff --git a/src/pages/JobPortal/PersonalCenter/Applications/index.tsx b/src/pages/JobPortal/PersonalCenter/Applications/index.tsx index 2436641..29316dd 100644 --- a/src/pages/JobPortal/PersonalCenter/Applications/index.tsx +++ b/src/pages/JobPortal/PersonalCenter/Applications/index.tsx @@ -140,7 +140,7 @@ const ApplicationsPage: React.FC = () => { const handleJobClick = (job: API.ApplicationJob) => { // 跳转到岗位详情页,通过 state 传递完整的岗位数据 - history.push('/job-portal/detail', { jobData: job }); + history.push(`/job-portal/detail?jobId=${job.jobId || job.id}`, { jobData: job }); }; return ( diff --git a/src/pages/JobPortal/PersonalCenter/Favorites/index.tsx b/src/pages/JobPortal/PersonalCenter/Favorites/index.tsx index c431aae..6f90253 100644 --- a/src/pages/JobPortal/PersonalCenter/Favorites/index.tsx +++ b/src/pages/JobPortal/PersonalCenter/Favorites/index.tsx @@ -130,7 +130,7 @@ const FavoritesPage: React.FC = () => { const handleJobClick = (job: API.ApplicationJob) => { // 跳转到岗位详情页,通过 state 传递完整的岗位数据 - history.push('/job-portal/detail', { jobData: job }); + history.push(`/job-portal/detail?jobId=${job.jobId || job.id}`, { jobData: job }); }; const handleRemoveFavorite = async (job: API.ApplicationJob, e?: React.MouseEvent) => { diff --git a/src/pages/JobPortal/PersonalCenter/Footprints/index.tsx b/src/pages/JobPortal/PersonalCenter/Footprints/index.tsx index 98c3d6e..bd2d179 100644 --- a/src/pages/JobPortal/PersonalCenter/Footprints/index.tsx +++ b/src/pages/JobPortal/PersonalCenter/Footprints/index.tsx @@ -128,7 +128,7 @@ const FootprintsPage: React.FC = () => { const handleJobClick = (job: API.ApplicationJob) => { // 跳转到岗位详情页,通过 state 传递完整的岗位数据 - history.push('/job-portal/detail', { jobData: job }); + history.push(`/job-portal/detail?jobId=${job.jobId || job.id}`, { jobData: job }); }; return ( diff --git a/src/pages/JobPortal/PersonalCenter/Interviews/index.less b/src/pages/JobPortal/PersonalCenter/Interviews/index.less new file mode 100644 index 0000000..384665b --- /dev/null +++ b/src/pages/JobPortal/PersonalCenter/Interviews/index.less @@ -0,0 +1,43 @@ +@import '../../theme.less'; + +.interviews-page { + min-height: 100vh; + background-color: @jp-bg-page; + + .page-content { + .jp-page-container(); + padding: 24px 20px 40px; + + .back-button .ant-btn { + color: @jp-primary; + padding: 0; + + &:hover { + color: @jp-primary-dark; + } + } + + .page-header { + margin-bottom: 24px; + + .ant-typography { + color: @jp-text-primary; + } + } + + .job-list .job-card { + .jp-card-base(); + margin-bottom: 16px; + transition: all 0.2s; + + &:hover { + border-color: @jp-primary-border; + box-shadow: @jp-shadow-hover; + } + + .job-info .ant-typography { + color: @jp-text-primary; + } + } + } +} diff --git a/src/pages/JobPortal/PersonalCenter/Interviews/index.tsx b/src/pages/JobPortal/PersonalCenter/Interviews/index.tsx index 500c3a1..9e80321 100644 --- a/src/pages/JobPortal/PersonalCenter/Interviews/index.tsx +++ b/src/pages/JobPortal/PersonalCenter/Interviews/index.tsx @@ -19,7 +19,8 @@ import { } from '@ant-design/icons'; import { history } from '@umijs/max'; import JobPortalHeader from '@/components/JobPortalHeader'; -import { getCmsInterviewList } from '@/services/Management/list'; +import { getAppInterviewList } from '@/services/Management/list'; +import './index.less'; const { Title, Text } = Typography; @@ -63,7 +64,7 @@ const InterviewsPage: React.FC = () => { try { setLoading(true); - const response = await getCmsInterviewList({ userId }); + const response = await getAppInterviewList({ userId }); if (response.code === 200) { setInterviews(response.rows || []); } else { @@ -120,24 +121,27 @@ const InterviewsPage: React.FC = () => {
- + <Title level={4} style={{ marginBottom: 4 }}> + {item.companyName || '未知公司'} - {item.jobTitle || item.jobName || '未知岗位'} + + <ClockCircleOutlined /> 面试时间:{item.interviewTime || '未指定'} {item.interviewMethod === 'online' ? '线上' : '线下'} - {st.text} + {/* {st.text} */}
{item.interviewMethod === 'online' ? ( - 腾讯会议 + 在线会议 {item.meetingLink && ( - 链接:{item.meetingLink} + 链接:{item.meetingLink} )} {item.meetingPassword && ( diff --git a/src/pages/JobPortal/index.tsx b/src/pages/JobPortal/index.tsx index b664e98..b182457 100644 --- a/src/pages/JobPortal/index.tsx +++ b/src/pages/JobPortal/index.tsx @@ -241,7 +241,7 @@ const JobPortalPage: React.FC = () => { }; const handleJobClick = (job: any) => { - history.push(`/job-portal/detail`, { jobData: job }); + history.push(`/job-portal/detail?jobId=${job.jobId || job.id}`, { jobData: job }); }; const handleProfessionTagClick = (name: string) => { diff --git a/src/pages/Management/List/SeeMatching/index.tsx b/src/pages/Management/List/SeeMatching/index.tsx index 0a4f70b..d085a97 100644 --- a/src/pages/Management/List/SeeMatching/index.tsx +++ b/src/pages/Management/List/SeeMatching/index.tsx @@ -298,7 +298,7 @@ function ManagementList() { { setResumeVisible(false); setCurrentRow(undefined); diff --git a/src/pages/Management/List/SeeMatching/resumeView.tsx b/src/pages/Management/List/SeeMatching/resumeView.tsx index e4293ae..12e8b12 100644 --- a/src/pages/Management/List/SeeMatching/resumeView.tsx +++ b/src/pages/Management/List/SeeMatching/resumeView.tsx @@ -4,6 +4,7 @@ import { ProDescriptions} from '@ant-design/pro-components'; import { addCmsUserWorkExperiencesList, addCmsInterview, + getCmsInterviewList, } from '@/services/Management/list'; import dayjs from 'dayjs'; @@ -27,17 +28,43 @@ const listEdit: React.FC = (props) => { const [interviewForm] = Form.useForm(); const [interviewMethod, setInterviewMethod] = useState('online'); const [submitting, setSubmitting] = useState(false); + const [alreadyInvited, setAlreadyInvited] = useState(false); + const [checkingInvitation, setCheckingInvitation] = useState(false); useEffect(() => { if (props.open && props.values) { console.log(props.values); fetchResumeDetail(); + checkExistingInvitation(); // 重置面试邀约表单 interviewForm.resetFields(); setInterviewMethod('online'); } }, [props.values,props.open]); + // 检查是否已存在有效邀约 + const checkExistingInvitation = async () => { + if (!props.matching?.jobId || !props.values?.userId) return; + setCheckingInvitation(true); + try { + const res = await getCmsInterviewList({ + jobId: Number(props.matching.jobId), + userId: props.values.userId, + }); + if (res?.code === 200 && res?.rows?.length > 0) { + // 存在非 rejected 的邀约即为已邀约 + const hasActive = res.rows.some( + (item: API.Management.InterviewInvitation) => item.status !== 'rejected' + ); + setAlreadyInvited(hasActive); + } + } catch (error) { + console.error('检查邀约状态失败:', error); + } finally { + setCheckingInvitation(false); + } + }; + const fetchResumeDetail = async () => { if (!props.values?.userId) return; @@ -66,6 +93,7 @@ const listEdit: React.FC = (props) => { jobId: Number(props.matching?.jobId), userId: props.values?.userId, applyId: props.values?.applyId ? Number(props.values.applyId) : undefined, + jobName: props.matching?.jobName, interviewTime: values.interviewTime ? dayjs(values.interviewTime).format('YYYY-MM-DD HH:mm:ss') : undefined, @@ -81,12 +109,15 @@ const listEdit: React.FC = (props) => { const response = await addCmsInterview(payload); if (response.code === 200) { message.success('面试邀约已发送'); + setAlreadyInvited(true); interviewForm.resetFields(); setInterviewMethod('online'); } else { message.error(response.msg || '发送失败'); } - } catch (error) { + } catch (error: any) { + const errMsg = error?.response?.data?.msg || error?.message || '发送面试邀约失败'; + message.error(errMsg); console.error('发送面试邀约失败:', error); } finally { setSubmitting(false); @@ -156,7 +187,7 @@ const listEdit: React.FC = (props) => {
= (props) => { <> - + - - + */} )} @@ -229,8 +259,13 @@ const listEdit: React.FC = (props) => { - diff --git a/src/services/Management/list.ts b/src/services/Management/list.ts index 4f405e2..308ded4 100644 --- a/src/services/Management/list.ts +++ b/src/services/Management/list.ts @@ -90,6 +90,7 @@ export async function addCmsUserWorkExperiencesList(params?: API.Management.Para // ========== 面试邀约 APIs ========== +/** 后台管理端:面试邀约列表 */ export async function getCmsInterviewList(params?: API.Management.InterviewInvitation) { return request(`/api/cms/interview/list`, { method: 'GET', @@ -97,6 +98,14 @@ export async function getCmsInterviewList(params?: API.Management.InterviewInvit }); } +/** 求职者端:我的面试邀约列表(无需后台权限) */ +export async function getAppInterviewList(params?: API.Management.InterviewInvitation) { + return request(`/api/app/interview/list`, { + method: 'GET', + params: params, + }); +} + export async function addCmsInterview(params?: API.Management.InterviewInvitation) { return request(`/api/cms/interview`, { method: 'POST', diff --git a/src/services/jobportal/user.ts b/src/services/jobportal/user.ts index 49812b3..cde0da0 100644 --- a/src/services/jobportal/user.ts +++ b/src/services/jobportal/user.ts @@ -70,6 +70,13 @@ export async function applyJob(params: { jobId: string; userId: string }, option }); } +/** 获取岗位详情 GET /app/job/{jobId}(含 isApply / isCollection) */ +export async function getJobDetail(jobId: string | number) { + return request(`/app/job/${jobId}`, { + method: 'GET', + }); +} + /** 浏览岗位 POST /api/cms/job/browse */ export async function browseJob(params: { jobId: number }, options?: Record) { return request('/api/cms/job/browse', { diff --git a/src/types/Management/interview.d.ts b/src/types/Management/interview.d.ts index 4a66ece..a474e5e 100644 --- a/src/types/Management/interview.d.ts +++ b/src/types/Management/interview.d.ts @@ -13,6 +13,9 @@ declare namespace API.Management { meetingPassword?: string; interviewLocation?: string; contactPhone?: string; + companyName?: string; + jobName?: string; + jobTitle?: string; status?: string; remark?: string; createTime?: string;