11
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
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:
@@ -1,222 +1,74 @@
|
||||
@import '../theme.less';
|
||||
|
||||
.job-detail-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
// padding: 20px;
|
||||
.job-detail-container {
|
||||
margin-top: 20px!important;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: @jp-bg-page;
|
||||
|
||||
.back-button {
|
||||
.job-detail-container {
|
||||
.jp-page-container();
|
||||
margin-top: 20px !important;
|
||||
padding-bottom: 40px;
|
||||
|
||||
.back-button .ant-btn {
|
||||
color: @jp-primary;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
color: @jp-primary-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.job-header-card,
|
||||
.info-card,
|
||||
.competitiveness-card,
|
||||
.skills-card {
|
||||
.jp-card-base();
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ant-btn {
|
||||
color: #1890ff;
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
.ant-card-head-title {
|
||||
.jp-section-title();
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-header-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.job-title-section {
|
||||
.job-title {
|
||||
color: #262626;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.job-salary-display {
|
||||
color: #ff4d4f;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
.job-salary-display {
|
||||
.jp-salary-text();
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.job-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
.action-buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.ant-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.ant-card-head-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
.job-meta {
|
||||
color: @jp-text-muted;
|
||||
}
|
||||
|
||||
.company-intro {
|
||||
.company-meta {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
margin-top: 16px;
|
||||
line-height: 1.8;
|
||||
color: #595959;
|
||||
}
|
||||
.job-actions .ant-btn-primary {
|
||||
background: @jp-primary;
|
||||
border-color: @jp-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.competitiveness-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.ant-card-head-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
.score-value {
|
||||
color: @jp-primary;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.radar-chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.radar-summary {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
|
||||
.summary-score {
|
||||
.score-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.score-label {
|
||||
color: @jp-text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.skills-card {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.ant-card-head-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.skill-item {
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
|
||||
.skill-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.anticon {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
.salary-indicator {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 992px) {
|
||||
.job-header-card {
|
||||
.job-title-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.job-actions {
|
||||
width: 100%;
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 10px;
|
||||
|
||||
.job-header-card {
|
||||
.job-title-section {
|
||||
.job-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.job-salary-display {
|
||||
font-size: 18px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.competitiveness-card {
|
||||
.radar-chart-container {
|
||||
.radar-summary {
|
||||
.summary-score {
|
||||
.score-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.skills-card .skill-item {
|
||||
background: @jp-primary-light;
|
||||
border-radius: @jp-radius;
|
||||
border: 1px solid @jp-primary-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.job-detail-page .job-detail-container {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useParams, history, useLocation } from '@umijs/max';
|
||||
import {
|
||||
Card,
|
||||
@@ -27,10 +27,17 @@ import {
|
||||
import { Radar } from '@ant-design/charts';
|
||||
import JobPortalHeader from '@/components/JobPortalHeader';
|
||||
import './index.less';
|
||||
import { getJobCompetitiveness } from '@/services/jobportal/competitiveness';
|
||||
import {
|
||||
getJobCompetitiveness,
|
||||
getEmptyCompetitivenessRadar,
|
||||
} from '@/services/jobportal/competitiveness';
|
||||
import { isJobPortalLoggedIn } from '@/utils/jobPortalAuth';
|
||||
import { favoriteJob, unfavoriteJob, applyJob } from '@/services/jobportal/user';
|
||||
import { getAccessToken } from '@/access';
|
||||
import { PageEnum } from '@/enums/pagesEnums';
|
||||
import { ensureJobPortalLogin, PORTAL_LOGIN_URL } from '@/utils/jobPortalAuth';
|
||||
import { getDictLabel, findTreeLabelById } from '@/utils/jobPortalDict';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||
import type { DictValueEnumObj } from '@/components/DictTag';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
|
||||
@@ -106,11 +113,13 @@ const transformJobData = (jobData: any) => {
|
||||
<p>3. 具备良好的团队协作能力;</p>
|
||||
`,
|
||||
companyInfo: {
|
||||
name: jobData.companyName || jobData.company,
|
||||
type: jobData.industry || '互联网/电子商务',
|
||||
scale: jobData.scale || '100-499人',
|
||||
industry: jobData.industry || '计算机/互联网',
|
||||
description: jobData.companyVo?.companyDescription || jobData.companyDescription || '暂无公司描述信息'
|
||||
name: jobData.companyName || jobData.company || jobData.companyVo?.name,
|
||||
scale: jobData.companyVo?.scale ?? jobData.scale ?? '',
|
||||
industry: jobData.companyVo?.industry ?? jobData.industry ?? '',
|
||||
description:
|
||||
jobData.companyVo?.companyDescription
|
||||
|| jobData.companyDescription
|
||||
|| '暂无公司描述信息',
|
||||
},
|
||||
competitiveness: {
|
||||
overall: 75,
|
||||
@@ -157,7 +166,6 @@ const mockJobDetail = {
|
||||
`,
|
||||
companyInfo: {
|
||||
name: '青岛科技发展有限公司',
|
||||
type: '互联网/电子商务',
|
||||
scale: '500-999人',
|
||||
industry: '计算机/互联网',
|
||||
description: '青岛科技发展有限公司是一家专注于软件开发、技术服务的科技企业。公司致力于为客户提供优质的IT解决方案,业务涵盖企业信息化、移动应用开发等领域。我们拥有一支年轻、专业的团队,注重技术创新和人才培养,为员工提供良好的发展平台。'
|
||||
@@ -193,6 +201,36 @@ const JobDetailPage: React.FC = () => {
|
||||
{ item: '年龄', score: 0 },
|
||||
{ item: '工作地', score: 0 }
|
||||
]);
|
||||
const [scaleEnum, setScaleEnum] = useState<DictValueEnumObj>({});
|
||||
const [industryTree, setIndustryTree] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
getDictValueEnum('scale', true, true),
|
||||
getCmsIndustryTreeList(),
|
||||
]).then(([scaleData, industryRes]) => {
|
||||
setScaleEnum(scaleData);
|
||||
if (industryRes?.code === 200 && industryRes?.data) {
|
||||
setIndustryTree(industryRes.data);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const companyMetaItems = useMemo(() => {
|
||||
const company = originalJobData?.companyVo || {};
|
||||
const rawScale = company.scale ?? originalJobData?.scale ?? jobDetail.companyInfo?.scale;
|
||||
const rawIndustry =
|
||||
company.industry ?? originalJobData?.industry ?? jobDetail.companyInfo?.industry;
|
||||
|
||||
const items: string[] = [];
|
||||
const scaleLabel = getDictLabel(scaleEnum, rawScale) || rawScale;
|
||||
const industryLabel = findTreeLabelById(industryTree, rawIndustry) || rawIndustry;
|
||||
|
||||
if (scaleLabel) items.push(scaleLabel);
|
||||
if (industryLabel) items.push(industryLabel);
|
||||
|
||||
return items;
|
||||
}, [originalJobData, jobDetail.companyInfo, scaleEnum, industryTree]);
|
||||
|
||||
useEffect(() => {
|
||||
// 首先尝试从传递的数据中获取职位信息
|
||||
@@ -226,10 +264,19 @@ const JobDetailPage: React.FC = () => {
|
||||
}, [jobDetail?.id]);
|
||||
|
||||
const fetchCompetitiveness = async (jobIdNum: number) => {
|
||||
if (!isJobPortalLoggedIn()) {
|
||||
setOverallScore(0);
|
||||
setRadarData(getEmptyCompetitivenessRadar());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getJobCompetitiveness(jobIdNum);
|
||||
const data = res as any;
|
||||
if (!data) return;
|
||||
if (!data) {
|
||||
setOverallScore(0);
|
||||
setRadarData(getEmptyCompetitivenessRadar());
|
||||
return;
|
||||
}
|
||||
if (typeof data.matchScore === 'number') {
|
||||
const clamped = Math.max(0, Math.min(100, data.matchScore));
|
||||
setOverallScore(clamped);
|
||||
@@ -248,36 +295,25 @@ const JobDetailPage: React.FC = () => {
|
||||
].map(d => ({ ...d, score: Math.max(0, Math.min(100, d.score)) }));
|
||||
setRadarData(mapped);
|
||||
} else {
|
||||
setRadarData([
|
||||
{ item: '技能', score: 0 },
|
||||
{ item: '工作经验', score: 0 },
|
||||
{ item: '学历', score: 0 },
|
||||
{ item: '薪资', score: 0 },
|
||||
{ item: '年龄', score: 0 },
|
||||
{ item: '工作地', score: 0 }
|
||||
]);
|
||||
setRadarData(getEmptyCompetitivenessRadar());
|
||||
}
|
||||
} catch (e) {
|
||||
// 保持默认mock数据
|
||||
setOverallScore(0);
|
||||
setRadarData(getEmptyCompetitivenessRadar());
|
||||
}
|
||||
};
|
||||
|
||||
const handleCollect = async () => {
|
||||
// 检查用户是否登录
|
||||
const token = getAccessToken();
|
||||
if (!token) {
|
||||
message.warning('请先登录');
|
||||
history.push(PageEnum.LOGIN);
|
||||
if (!ensureJobPortalLogin('收藏职位')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是取消收藏,调用取消收藏接口
|
||||
if (isFavorited) {
|
||||
// 获取用户ID
|
||||
const userId = getUserIdFromCache();
|
||||
if (!userId) {
|
||||
message.warning('无法获取用户信息,请重新登录');
|
||||
history.push(PageEnum.LOGIN);
|
||||
window.location.href = PORTAL_LOGIN_URL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -307,12 +343,10 @@ const JobDetailPage: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是收藏,调用收藏接口
|
||||
// 获取用户ID
|
||||
const userId = getUserIdFromCache();
|
||||
if (!userId) {
|
||||
message.warning('无法获取用户信息,请重新登录');
|
||||
history.push(PageEnum.LOGIN);
|
||||
window.location.href = PORTAL_LOGIN_URL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -342,19 +376,14 @@ const JobDetailPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleApply = async () => {
|
||||
// 检查用户是否登录
|
||||
const token = getAccessToken();
|
||||
if (!token) {
|
||||
message.warning('请先登录');
|
||||
history.push(PageEnum.LOGIN);
|
||||
if (!ensureJobPortalLogin('申请职位')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户ID
|
||||
const userId = getUserIdFromCache();
|
||||
if (!userId) {
|
||||
message.warning('无法获取用户信息,请重新登录');
|
||||
history.push(PageEnum.LOGIN);
|
||||
window.location.href = PORTAL_LOGIN_URL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -471,12 +500,10 @@ const JobDetailPage: React.FC = () => {
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{jobDetail.companyInfo.name}
|
||||
</Title>
|
||||
<Space className="company-meta">
|
||||
<Text>{jobDetail.companyInfo.type}</Text>
|
||||
<Divider type="vertical" />
|
||||
<Text>{jobDetail.companyInfo.scale}</Text>
|
||||
<Divider type="vertical" />
|
||||
<Text>{jobDetail.companyInfo.industry}</Text>
|
||||
<Space className="company-meta" split={<Divider type="vertical" />}>
|
||||
{companyMetaItems.map((label) => (
|
||||
<Text key={label}>{label}</Text>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user