feat: 更新企业内推岗位功能

- 删除岗位陪跑流程图标和弹窗组件
- 导入企业内推岗位库.json新数据源(39个岗位)
- 实现岗位按截止时间降序排序
- 添加过期岗位判断逻辑,过期岗位显示已过期且无法投递
- 更新岗位卡片显示截止时间格式(YYYY-MM-DD)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-08 04:51:28 +08:00
parent 44a378b4ec
commit b424a1fd50
29 changed files with 1410 additions and 346 deletions

1124
src/data/companyJobsNew.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,135 +1,65 @@
// 导入JSON数据
import companyJobsData from './companyJobs.json';
import companyJobsNewData from './companyJobsNew.json';
import calendarCoursesData from './calendarCourses.json';
import aiCoursesData from '../../网页未导入数据/文旅产业/ai课程表.json';
import marketingCoursesData from './marketingCourses.json';
// 转换函数将JSON数据转换为页面所需格式
const transformCompanyJobs = (jobsData) => {
return jobsData.map((job, index) => ({
id: index + 1,
company: job["公司介绍"] ? job["公司介绍"].split('。')[0].substring(0, 20) + '...' : `公司${index + 1}`,
position: job["内推岗位名称"],
salary: job["薪资"],
status: "available",
jobType: job["岗位标签"] === "实习" ? "internship" : "fulltime",
remainingPositions: parseInt(job["招聘人数"]) || 1,
applicationStatus: ["not_applied", "applied", "interview_success", "interview_failed"][Math.floor(Math.random() * 4)],
tags: job["职位标签"] || [],
deadline: job["截止时间"],
type: job["岗位相关标签"] || "专业相关岗位",
jobCategory: job["岗位相关标签"] || "专业相关岗位", // 岗位相关标签
location: job["工作地点"], // 工作地点
education: job["学历要求"], // 学历要求
benefits: job["福利标签"] || [], // 福利标签
isRecommended: Math.random() > 0.7,
details: {
location: job["工作地点"],
experience: "1-3年",
education: job["学历要求"],
positions: job["招聘人数"],
description: job["职位描述"],
requirements: job["任职要求"] ? job["任职要求"].split(/[;。\n]/).filter(r => r.trim()) : [],
benefits: job["福利标签"] || [],
companyInfo: job["公司介绍"]
}
}));
};
// 转换函数将日历课程JSON数据转换为日历事件格式
const transformCalendarCourses = (coursesData) => {
return coursesData
.filter(course => course["❌课程状态"] !== "休息" && course["日期"])
.map((course, index) => {
// 解析日期
const dateStr = course["日期"];
const [year, month, day] = dateStr.split('/').map(Number);
const courseDate = new Date(year, month - 1, day);
// 获取今天的日期(只保留日期部分)
const today = new Date();
today.setHours(0, 0, 0, 0);
return jobsData
.map((job, index) => {
// 处理截止时间格式2025/8/28 -> Date对象
const deadlineParts = job["截止时间"].split('/');
const deadlineDate = new Date(
parseInt(deadlineParts[0]),
parseInt(deadlineParts[1]) - 1,
parseInt(deadlineParts[2])
);
deadlineDate.setHours(0, 0, 0, 0);
// 获取课程名称(优先级从高到低)
const courseName = course["复合技能阶段"] ||
course["公开课"] ||
course["垂直方向阶段(方向二:商业活动策划)"] ||
course["1V1 规划阶段"] ||
course["模拟面试实战练习阶段"] ||
"未命名课程";
// 解析上课时间
const timeStr = course["上课时间"] || "20:00-21:00";
let startTime = "20:00";
let endTime = "21:00";
// 特殊处理1V1规划课程的时间
if (course["1V1 规划阶段"]) {
startTime = "14:00";
endTime = "16:00";
} else {
// 处理时间格式,确保有效
if (timeStr && timeStr.includes('-')) {
const timeParts = timeStr.split('-').map(t => t ? t.trim() : '');
if (timeParts[0]) startTime = timeParts[0];
if (timeParts[1]) endTime = timeParts[1];
} else if (timeStr) {
// 如果没有'-',假设是开始时间
startTime = timeStr.trim();
// 自动计算结束时间加1小时
const [hour, minute] = startTime.split(':');
const endHour = parseInt(hour) + 1;
endTime = `${endHour.toString().padStart(2, '0')}:${minute || '00'}`;
}
}
// 构建完整的时间戳
const startDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${startTime}`;
const endDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${endTime}`;
// 确定课程类型和颜色
let type = 'class';
let color = '#3b82f6'; // 默认蓝色
if (course["公开课"]) {
type = 'public-course';
color = '#f59e0b'; // 橙色
} else if (course["1V1 规划阶段"]) {
type = 'one-on-one';
color = '#ec4899'; // 粉色
} else if (course["模拟面试实战练习阶段"]) {
type = 'interview';
color = '#3b82f6'; // 蓝色
} else if (course["复合技能阶段"]) {
type = 'compound-skill';
color = '#667eea'; // 紫色
} else if (course["垂直方向阶段(方向二:商业活动策划)"]) {
type = 'vertical-skill';
color = '#22c55e'; // 绿色
}
// 确定课程状态
let status = 'upcoming';
const now = new Date();
if (courseDate < now) {
status = 'completed';
} else if (courseDate.toDateString() === now.toDateString()) {
status = 'ongoing';
}
// 判断岗位是否已过期
const isExpired = deadlineDate < today;
return {
id: index + 1,
title: courseName,
fullTitle: courseName,
teacher: course["❌导师姓名查询"] || "待定",
unit: course["❌查询单元名称"] || "",
startTime: startDateTime,
endTime: endDateTime,
type: type,
color: color,
textColor: '#1d2129',
description: `${courseName} - ${course["❌导师姓名查询"] || "待定"}老师`,
status: course["❌课程状态"] || status,
weekday: course["星期"],
location: course["上课地点"] || "线上",
originalDate: course["日期"]
company: job["公司介绍"] ? job["公司介绍"].split('。')[0].substring(0, 20) + '...' : `公司${index + 1}`,
position: job["内推岗位名称"],
salary: job["薪资"],
status: isExpired ? "expired" : "available",
jobType: job["岗位标签"] === "实习" ? "internship" : "fulltime",
remainingPositions: parseInt(job["招聘人数"]) || 1,
applicationStatus: isExpired ? "expired" : "not_applied",
tags: job["职位标签"] || [],
deadline: `${deadlineParts[0]}-${deadlineParts[1].padStart(2, '0')}-${deadlineParts[2].padStart(2, '0')}`, // 格式化为 YYYY-MM-DD
deadlineDate: deadlineDate, // 用于排序
isExpired: isExpired, // 标记是否过期
type: job["岗位相关标签"] || "专业相关岗位",
jobCategory: job["岗位相关标签"] || "专业相关岗位",
location: job["工作地点"],
education: job["学历要求"],
benefits: job["福利标签"] || [],
isRecommended: Math.random() > 0.7,
details: {
location: job["工作地点"],
experience: "1-3年",
education: job["学历要求"],
positions: job["招聘人数"],
description: job["职位描述"],
requirements: job["任职要求"] ? job["任职要求"].split(/[;
]/).filter(r => r.trim()) : [],
benefits: job["福利标签"] || [],
companyInfo: job["公司介绍"]
}
};
})
.sort((a, b) => {
// 按截止时间降序排序(最晚的在前)
return b.deadlineDate - a.deadlineDate;
});
};
@@ -2185,7 +2115,7 @@ export const mockData = {
companyJobs: {
title: "企业内推岗位",
subtitle: "基于AI智能匹配的企业内推岗位推荐系统",
companyPositions: transformCompanyJobs(companyJobsData),
companyPositions: transformCompanyJobs(companyJobsNewData),
},
// 用户简历数据

View File

@@ -169,6 +169,23 @@
background-size: 100% 100%;
margin-right: 5px;
}
&.disabled {
background-color: #f2f3f5;
color: #c9cdd4;
cursor: not-allowed;
pointer-events: none;
&:hover {
background-color: #f2f3f5;
box-shadow: none;
transform: none;
}
> i {
opacity: 0.5;
}
}
}
.company-jobs-info-deadline {

View File

@@ -32,6 +32,12 @@ export default ({ className = "", data = [], backgroundColor }) => {
const handleDeliverClick = async (e, item) => {
e.stopPropagation(); // 阻止冒泡到li的点击事件
// 检查岗位是否已过期
if (item.isExpired || item.status === 'expired') {
toast.error('该岗位已过期,无法投递');
return;
}
// 获取岗位详情然后打开投递弹窗
const res = await getJobsDetail(item.id);
if (res.success) {
@@ -147,11 +153,12 @@ export default ({ className = "", data = [], backgroundColor }) => {
{item?.salary}
</p>
<button
className="company-jobs-btn"
className={`company-jobs-btn ${(item.isExpired || item.status === 'expired') ? 'disabled' : ''}`}
onClick={(e) => handleDeliverClick(e, item)}
disabled={item.isExpired || item.status === 'expired'}
>
<i />
<span>投递</span>
<span>{(item.isExpired || item.status === 'expired') ? '已过期' : '投递'}</span>
</button>
{item?.deadline && (
<p className="company-jobs-info-deadline">

View File

@@ -17,7 +17,6 @@ const PAGE_SIZE = 10;
const CompanyJobsPage = () => {
const studentInfo = useSelector((state) => state.student.studentInfo);
const [isExpand, setIsExpand] = useState(false); // 是否展开
const [jobs, setJobs] = useState([]);
const [jobsListPage, setJobsListPage] = useState(1);
const [jobsListHasMore, setJobsListHasMore] = useState(true);
@@ -272,37 +271,6 @@ const CompanyJobsPage = () => {
</InfiniteScroll>
</div>
</div>
<div
onClick={() => setIsExpand(!isExpand)}
className={
isExpand
? "company-jobs-page-process-wrapper-expand"
: "company-jobs-page-process-wrapper-close"
}
>
<div className="company-jobs-page-process-wrapper-title">
岗位陪跑流程
</div>
<div className="company-jobs-page-process-content">
<div className="company-jobs-page-process-item-icon icon1">
<p>内推岗位简历投递</p>
</div>
<div className="company-jobs-page-process-item-round-dot icon2" />
<div className="company-jobs-page-process-item-icon icon3">
<p>岗位简历接收</p>
</div>
<div className="company-jobs-page-process-item-icon icon4">
<p>面试时间地点确定</p>
</div>
<div className="company-jobs-page-process-item-icon icon5">
<p>参与企业面试</p>
</div>
<div className="company-jobs-page-process-item-round-dot icon6" />
<div className="company-jobs-page-process-item-icon icon7">
<p>企业offer发送</p>
</div>
</div>
</div>
</>
)}
</div>