完整的教务系统前端项目 - 包含所有修复和9月份数据
This commit is contained in:
233
src/pages/CompanyJobsPage/components/JobInfoModal/index.css
Normal file
233
src/pages/CompanyJobsPage/components/JobInfoModal/index.css
Normal file
@@ -0,0 +1,233 @@
|
||||
.job-info-modal-content {
|
||||
max-height: 720px;
|
||||
width: 844px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: #f2f3f5;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
|
||||
.job-info-modal-search {
|
||||
width: 319px;
|
||||
height: 36px;
|
||||
border: 1px solid #2c7aff;
|
||||
|
||||
span {
|
||||
background-color: #fff;
|
||||
}
|
||||
input {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.empty-data-wrapper {
|
||||
width: 100%;
|
||||
min-height: 555px;
|
||||
display: flex;
|
||||
}
|
||||
.job-info-modal-user-resumes-list {
|
||||
width: 100%;
|
||||
min-height: 555px;
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr); /* 每行两列 */
|
||||
gap: 20px; /* 网格间距 */
|
||||
justify-items: start; /* 项目左对齐 */
|
||||
overflow-y: auto;
|
||||
|
||||
.list-item {
|
||||
width: 390px;
|
||||
height: 100px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.list-item-info {
|
||||
height: 68px;
|
||||
width: 300px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.file-icon {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
width: 220px;
|
||||
height: 68px;
|
||||
> p {
|
||||
text-align: left;
|
||||
}
|
||||
.file-info-targetPosition {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
color: #09090b;
|
||||
line-height: 28px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-info-skills {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
height: 21px;
|
||||
color: #788089;
|
||||
line-height: 21px;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
overflow: hidden; /* 超出隐藏 */
|
||||
white-space: nowrap; /* 禁止换行 */
|
||||
text-overflow: ellipsis; /* 文本溢出显示省略号 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-btn {
|
||||
width: 64px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #2c7aff;
|
||||
color: #2c7aff;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-info-modal-content-position-info {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
|
||||
.job-info-modal-content-position-info-position {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
color: #1d2129;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.job-info-modal-content-position-info-num {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: #ff7d00;
|
||||
}
|
||||
|
||||
.job-info-modal-content-position-info-salary {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #ff7d00;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.job-info-modal-info-tags {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
|
||||
.job-info-modal-info-tag {
|
||||
background-color: #e5e6eb;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
padding: 1px 8px;
|
||||
color: #1d2129;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-info-modal-content-position-info-description,
|
||||
.job-info-modal-content-position-info-requirements,
|
||||
.job-info-modal-content-position-info-companyInfo {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
margin: 10px 0;
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.description-title,
|
||||
.requirements-title,
|
||||
.companyInfo-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
color: #000;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.description-content,
|
||||
.companyInfo-content {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
}
|
||||
.requirements-content {
|
||||
width: 100%;
|
||||
.requirements-item {
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-info-modal-btn {
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
color: #fff;
|
||||
background-color: #2c7aff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
> i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 5px;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/btn_icon_2.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
288
src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx
Normal file
288
src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx
Normal file
@@ -0,0 +1,288 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Input } from "@arco-design/web-react";
|
||||
import Modal from "@/components/Modal";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import FILEICON from "@/assets/images/CompanyJobsPage/file_icon.png";
|
||||
import ResumeInfoModal from "../ResumeInfoModal";
|
||||
import { getResumesList, submitResume, getPageData } from "@/services";
|
||||
import "./index.css";
|
||||
|
||||
const InputSearch = Input.Search;
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export default ({ visible, onClose, data, directToResume = false }) => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
const [resumeModalShow, setResumeModalShow] = useState(directToResume);
|
||||
const [resumeInfoModalShow, setResumeInfoModalShow] = useState(false);
|
||||
const [resumeInfoData, setResumeInfoData] = useState(null);
|
||||
const [resumeList, setResumeList] = useState([]); // 简历列表
|
||||
const [listPage, setListPage] = useState(1);
|
||||
const [listHasMore, setListHasMore] = useState(true);
|
||||
|
||||
// 处理directToResume参数变化
|
||||
useEffect(() => {
|
||||
if (visible && directToResume) {
|
||||
setResumeModalShow(true);
|
||||
} else if (visible && !directToResume) {
|
||||
setResumeModalShow(false);
|
||||
}
|
||||
}, [visible, directToResume]);
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setResumeModalShow(false);
|
||||
setResumeList([]); // 清空简历列表
|
||||
setListPage(1); // 重置分页
|
||||
setListHasMore(true); // 重置加载更多状态
|
||||
onClose();
|
||||
};
|
||||
|
||||
const queryResumeList = useCallback(async () => {
|
||||
const res = await getResumesList({
|
||||
page: listPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id
|
||||
});
|
||||
if (res.success) {
|
||||
setResumeList((prevList) => {
|
||||
const newList = [...prevList, ...res.data];
|
||||
if (res.total === newList?.length) {
|
||||
setListHasMore(false);
|
||||
} else {
|
||||
setListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
}, [listPage, studentInfo?.id]);
|
||||
|
||||
// 点击立即投递
|
||||
const handleClickDeliverBtn = (e) => {
|
||||
e.stopPropagation();
|
||||
setResumeModalShow(true);
|
||||
};
|
||||
|
||||
const onSearch = (value) => {
|
||||
// todo
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
// 选择简历投递
|
||||
const userResumesClick = async (item) => {
|
||||
try {
|
||||
// 调用投递服务
|
||||
const result = await submitResume({
|
||||
resumeId: item.id,
|
||||
jobId: data?.id,
|
||||
studentId: studentInfo?.id,
|
||||
resumeTitle: item.title,
|
||||
jobPosition: data?.position,
|
||||
company: data?.company
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// 投递成功,显示成功提示
|
||||
toast.success(`简历"${item.title}"投递成功!`);
|
||||
|
||||
// 关闭模态框
|
||||
handleCloseModal();
|
||||
|
||||
// 输出投递成功信息
|
||||
console.log('投递成功', {
|
||||
applicationId: result.data.applicationId,
|
||||
resumeId: item.id,
|
||||
jobId: data?.id,
|
||||
resumeTitle: item.title,
|
||||
jobPosition: data?.position,
|
||||
submittedAt: result.data.submittedAt
|
||||
});
|
||||
} else {
|
||||
toast.error(result.message || '投递失败,请重试');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
toast.error('投递失败,请重试');
|
||||
console.error('投递失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 点击简历详情
|
||||
const userResumesBtnClick = async (e, item) => {
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
// 获取岗位与面试题页面的数据
|
||||
const pageDataResponse = await getPageData();
|
||||
|
||||
if (pageDataResponse.success) {
|
||||
const pageData = pageDataResponse.data;
|
||||
|
||||
// 直接使用简历列表中的模板数据
|
||||
const selectedTemplate = item.template;
|
||||
|
||||
// 找到对应的行业信息
|
||||
const matchedIndustry = pageData.industries?.find(industry =>
|
||||
industry.name === item.industry
|
||||
);
|
||||
|
||||
// 传递数据给 ResumeInfoModal
|
||||
const resumeData = {
|
||||
selectedTemplate,
|
||||
studentResume: pageData.myResume,
|
||||
industry: matchedIndustry,
|
||||
jobPosition: item.position
|
||||
};
|
||||
|
||||
console.log('加载简历数据:', {
|
||||
resumeTitle: item.title,
|
||||
position: item.position,
|
||||
industry: item.industry
|
||||
});
|
||||
|
||||
setResumeInfoData(resumeData);
|
||||
setResumeInfoModalShow(true);
|
||||
} else {
|
||||
toast.error('加载简历数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取简历数据失败:', error);
|
||||
toast.error('加载简历数据失败');
|
||||
}
|
||||
};;;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal visible={visible} onClose={handleCloseModal}>
|
||||
<div className="job-info-modal-content">
|
||||
{resumeModalShow ? (
|
||||
<>
|
||||
<InputSearch
|
||||
className="job-info-modal-search"
|
||||
onSearch={onSearch}
|
||||
searchButton
|
||||
placeholder="搜索简历"
|
||||
/>
|
||||
{
|
||||
<InfiniteScroll
|
||||
loadMore={queryResumeList}
|
||||
hasMore={listHasMore}
|
||||
empty={resumeList.length === 0}
|
||||
className={`${
|
||||
resumeList.length
|
||||
? "job-info-modal-user-resumes-list"
|
||||
: "empty-data-wrapper"
|
||||
}`}
|
||||
>
|
||||
{resumeList.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className="list-item"
|
||||
onClick={() => userResumesClick(item)}
|
||||
>
|
||||
<div className="list-item-info">
|
||||
<img src={FILEICON} className="file-icon" />
|
||||
<div className="file-info">
|
||||
<p className="file-info-targetPosition">
|
||||
{item.title}
|
||||
</p>
|
||||
{item?.skills?.length > 0 && (
|
||||
<p className="file-info-skills">
|
||||
{item?.skills?.join("/")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="info-btn"
|
||||
onClick={(e) => userResumesBtnClick(e, item)}
|
||||
>
|
||||
简历详情
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="job-info-modal-content-position-info">
|
||||
<span className="job-info-modal-content-position-info-position">
|
||||
{data?.position}
|
||||
</span>
|
||||
<span className="job-info-modal-content-position-info-num">
|
||||
该岗位仅剩{data?.remainingPositions}人
|
||||
</span>
|
||||
<span className="job-info-modal-content-position-info-salary">
|
||||
{data?.salary}
|
||||
</span>
|
||||
</div>
|
||||
{data?.tags?.length > 0 && (
|
||||
<ul className="job-info-modal-info-tags">
|
||||
{data?.tags?.map((tag, index) => (
|
||||
<li key={index} className="job-info-modal-info-tag">
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{data?.details?.description && (
|
||||
<div className="job-info-modal-content-position-info-description">
|
||||
<p className="description-title">岗位描述</p>
|
||||
<p className="description-content">{data?.details?.description}</p>
|
||||
</div>
|
||||
)}
|
||||
{data?.details?.requirements?.length > 0 && (
|
||||
<div className="job-info-modal-content-position-info-requirements">
|
||||
<p className="requirements-title">岗位要求</p>
|
||||
<ul className="requirements-content">
|
||||
{data?.details?.requirements?.map((item, index) => (
|
||||
<li key={index} className="requirements-item">
|
||||
{index + 1}. {item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{/* {data?.details?.requirements?.length > 0 && (
|
||||
<div className="job-info-modal-content-position-info-requirements">
|
||||
<p className="requirements-title">岗位要求</p>
|
||||
<ul className="requirements-content">
|
||||
{data?.details?.requirements?.map((item, index) => (
|
||||
<li key={index} className="requirements-item">
|
||||
{index + 1}.{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)} */}
|
||||
{data?.details?.companyInfo && (
|
||||
<div className="job-info-modal-content-position-info-companyInfo">
|
||||
<p className="companyInfo-title">公司介绍</p>
|
||||
<p className="companyInfo-content">
|
||||
{data?.details?.companyInfo}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="job-info-modal-btn"
|
||||
onClick={handleClickDeliverBtn}
|
||||
>
|
||||
<i />
|
||||
<span>立即投递</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
<ResumeInfoModal
|
||||
visible={resumeInfoModalShow}
|
||||
data={resumeInfoData}
|
||||
onClose={() => {
|
||||
setResumeInfoModalShow(false);
|
||||
setResumeInfoData(null);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
129
src/pages/CompanyJobsPage/components/JobList/index.css
Normal file
129
src/pages/CompanyJobsPage/components/JobList/index.css
Normal file
@@ -0,0 +1,129 @@
|
||||
.company-jobs-page-left-list {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
padding: 5px 0;
|
||||
|
||||
.company-jobs-page-left-list-item {
|
||||
width: 528px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
border: 1px solid #e5e6eb;
|
||||
background-color: #e5f1ff;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/jobs_page_left_list_item_bg.png");
|
||||
background-size: 100% 100%;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -6px;
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
background-size: 100% 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.icon-fulltime {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/fulltime_icon.png");
|
||||
}
|
||||
.icon-internship {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/internship_icon.png");
|
||||
}
|
||||
|
||||
.company-jobs-info {
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.company-jobs-info-position {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.company-jobs-info-tags {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
|
||||
.company-jobs-info-tag {
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
padding: 1px 8px;
|
||||
color: #4e5969;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.company-jobs-info-position-count {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
color: #ff3f43;
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.company-jobs-btn-wrapper {
|
||||
flex: 1;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.company-jobs-info-position-salary {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #ff9a2d;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.company-jobs-btn {
|
||||
cursor: pointer;
|
||||
width: 64px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background-color: #0077ff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/btn_icon.png");
|
||||
background-size: 100% 100%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/pages/CompanyJobsPage/components/JobList/index.jsx
Normal file
96
src/pages/CompanyJobsPage/components/JobList/index.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useState } from "react";
|
||||
import toast from "@/components/Toast";
|
||||
import JobInfoModal from "../JobInfoModal";
|
||||
import { getJobsDetail } from "@/services";
|
||||
import { mapJob } from "@/utils/dataMapper";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ className = "", data = [], backgroundColor }) => {
|
||||
const [jobInfoData, setJobInfoData] = useState(undefined);
|
||||
const [jobInfoModalVisible, setJobInfoModalVisible] = useState(false);
|
||||
const [directToResume, setDirectToResume] = useState(false);
|
||||
|
||||
const handleJobClick = async (e, item) => {
|
||||
e.stopPropagation();
|
||||
const res = await getJobsDetail(item.id);
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,不需要映射
|
||||
setJobInfoData(res.data);
|
||||
setDirectToResume(false); // 点击岗位条目,显示详情
|
||||
setJobInfoModalVisible(true);
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickJobInfoModalClose = () => {
|
||||
setJobInfoData(undefined);
|
||||
setJobInfoModalVisible(false);
|
||||
};
|
||||
|
||||
// 直接投递按钮点击事件
|
||||
const handleDeliverClick = async (e, item) => {
|
||||
e.stopPropagation(); // 阻止冒泡到li的点击事件
|
||||
|
||||
// 获取岗位详情然后打开投递弹窗
|
||||
const res = await getJobsDetail(item.id);
|
||||
if (res.success) {
|
||||
setJobInfoData(res.data);
|
||||
setDirectToResume(true); // 点击投递按钮,直接显示简历选择界面
|
||||
setJobInfoModalVisible(true);
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className={`company-jobs-page-left-list ${className}`}>
|
||||
{data?.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className="company-jobs-page-left-list-item"
|
||||
style={{ backgroundColor }}
|
||||
onClick={(e) => handleJobClick(e, item)}
|
||||
>
|
||||
<i className={`icon icon-${item?.jobType}`}></i>
|
||||
<div className="company-jobs-info">
|
||||
<p className="company-jobs-info-position">{item?.position}</p>
|
||||
<ul className="company-jobs-info-tags">
|
||||
{item?.tags?.map((tag, index) => (
|
||||
<li
|
||||
key={`${item.id}-tag-${index}`}
|
||||
className="company-jobs-info-tag"
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="company-jobs-info-position-count">
|
||||
岗位招聘数量仅剩{item?.remainingPositions}名
|
||||
</p>
|
||||
</div>
|
||||
<div className="company-jobs-btn-wrapper">
|
||||
<p className="company-jobs-info-position-salary">
|
||||
{item?.salary}
|
||||
</p>
|
||||
<button
|
||||
className="company-jobs-btn"
|
||||
onClick={(e) => handleDeliverClick(e, item)}
|
||||
>
|
||||
<i />
|
||||
<span>投递</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<JobInfoModal
|
||||
data={jobInfoData}
|
||||
visible={jobInfoModalVisible}
|
||||
onClose={onClickJobInfoModalClose}
|
||||
directToResume={directToResume}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
378
src/pages/CompanyJobsPage/components/ResumeInfoModal/index.css
Normal file
378
src/pages/CompanyJobsPage/components/ResumeInfoModal/index.css
Normal file
@@ -0,0 +1,378 @@
|
||||
.resume-info-modal {
|
||||
width: 758px;
|
||||
height: 720px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
background-color: #f2f3f5;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-image: url("@/assets/images/Common/modal_bg.png");
|
||||
background-size: 100% 100%;
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url("@/assets/images/Common/close.png");
|
||||
background-size: 100% 100%;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
.resume-info-modal-radio-group {
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.resume-info-modal-title {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
text-align: left;
|
||||
color: #1d2129;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.resume-info-moda-list {
|
||||
width: 100%;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.resume-info-moda-item {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
|
||||
.resume-info-moda-item-title {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
line-height: 30px;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
color: #0275f2;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 3px;
|
||||
width: 79px;
|
||||
height: 5px;
|
||||
opacity: 0.1;
|
||||
border-radius: 5px;
|
||||
background-color: #0275f2;
|
||||
}
|
||||
}
|
||||
|
||||
.educational-experience-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.educational-experience-list-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.educational-experience-list-item {
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
> p {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.school-name {
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
text-align: left;
|
||||
color: #4e5969;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.study-time {
|
||||
width: 30%;
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
text-align: right;
|
||||
color: #86909c;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-experience-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.project-experience-list-item {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.project-info-wrapper {
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.project-info {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
> p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.project-company {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
|
||||
.project-time {
|
||||
text-align: right;
|
||||
width: 30%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
.project-desc {
|
||||
color: #1d2129;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.job-responsibilities-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
text-align: left;
|
||||
}
|
||||
> li {
|
||||
width: 100%;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.professional-skills-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.professional-skills-list-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.professional-skills-list-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.skill-name {
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.core-capabilities-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.core-capabilities-list-item {
|
||||
width: 100%;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-summary-list,
|
||||
.personal-summary-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> li,
|
||||
> p {
|
||||
width: 100%;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
.corresponding-course-units-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.corresponding-course-units-list-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.corresponding-course-units-list-item {
|
||||
width: 100%;
|
||||
min-height: 72px;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.tag {
|
||||
width: 47px;
|
||||
height: 20px;
|
||||
background-color: #e5e6eb;
|
||||
border-radius: 2px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
color: #4e5969;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.course-units-list {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.course-units-list-item {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #2c7aff;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #2c7aff;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
165
src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx
Normal file
165
src/pages/CompanyJobsPage/components/ResumeInfoModal/index.jsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { useState } from "react";
|
||||
import { Radio } from "@arco-design/web-react";
|
||||
import Modal from "@/components/Modal";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ visible, onClose, data }) => {
|
||||
const [position, setPosition] = useState("1");
|
||||
const onRadioChange = (value) => {
|
||||
setPosition(value);
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 获取当前简历数据
|
||||
const currentTemplate = data?.selectedTemplate;
|
||||
const studentInfo = currentTemplate?.studentInfo;
|
||||
const positionTitle = currentTemplate?.position || "岗位名称";
|
||||
|
||||
// 转换数据格式
|
||||
let resumeData = {
|
||||
educational_experience: ["相关专业大学 本科"],
|
||||
project_experience: [],
|
||||
core_skills: [],
|
||||
compound_skills: [],
|
||||
personal_summary: "暂无个人总结"
|
||||
};
|
||||
|
||||
if (studentInfo) {
|
||||
// 处理教育经历
|
||||
resumeData.educational_experience = studentInfo.educational_experience || ["相关专业大学 本科"];
|
||||
|
||||
// 处理项目经历
|
||||
if (studentInfo.project_experience) {
|
||||
if (Array.isArray(studentInfo.project_experience)) {
|
||||
// 如果已经是数组格式
|
||||
resumeData.project_experience = studentInfo.project_experience;
|
||||
} else if (typeof studentInfo.project_experience === 'object') {
|
||||
// 如果是对象格式,转换为数组
|
||||
const proj = studentInfo.project_experience;
|
||||
resumeData.project_experience = [
|
||||
{
|
||||
name: proj.project_name || proj.position || "实习项目",
|
||||
description: proj.description || "参与项目实施"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理核心技能和复合技能
|
||||
resumeData.core_skills = studentInfo.core_skills || [];
|
||||
resumeData.compound_skills = studentInfo.compound_skills || [];
|
||||
resumeData.personal_summary = studentInfo.personal_summary || "具有扎实的专业基础和实践经验";
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal visible={visible} onClose={handleCloseModal}>
|
||||
<div className="resume-info-modal">
|
||||
<i className="close-icon" onClick={handleCloseModal} />
|
||||
<Radio.Group
|
||||
type="button"
|
||||
name="position"
|
||||
className="resume-info-modal-radio-group"
|
||||
value={position}
|
||||
onChange={onRadioChange}
|
||||
>
|
||||
<Radio value="1">原始版</Radio>
|
||||
<Radio value="2">个人修改版</Radio>
|
||||
<Radio value="3">个人修改版</Radio>
|
||||
</Radio.Group>
|
||||
<p className="resume-info-modal-title">{positionTitle}</p>
|
||||
<ul className="resume-info-moda-list">
|
||||
{/* 教育经历 */}
|
||||
{resumeData.educational_experience && resumeData.educational_experience.length > 0 && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">教育经历</p>
|
||||
<ul className="educational-experience-list">
|
||||
{resumeData.educational_experience.map((edu, index) => (
|
||||
<li key={index} className="educational-experience-list-item">
|
||||
<p className="school-name">{edu}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
{/* 项目经历 */}
|
||||
{resumeData.project_experience && resumeData.project_experience.length > 0 && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">项目经历</p>
|
||||
<ul className="project-experience-list">
|
||||
{resumeData.project_experience.map((project, index) => (
|
||||
<li key={index} className="project-experience-list-item">
|
||||
<div className="project-info-wrapper">
|
||||
<div className="project-info">
|
||||
<p className="project-name">{project.name || `项目${index + 1}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="project-desc">
|
||||
{project.description}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
{/* 专业技能 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">专业技能</p>
|
||||
<ul className="professional-skills-list">
|
||||
{resumeData.core_skills && resumeData.core_skills.length > 0 && (
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">核心能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
{resumeData.core_skills.map((skill, index) => (
|
||||
<p key={index} className="core-capabilities-list-item">
|
||||
{skill}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
{resumeData.compound_skills && resumeData.compound_skills.length > 0 && (
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">复合能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
{resumeData.compound_skills.map((skill, index) => (
|
||||
<p key={index} className="core-capabilities-list-item">
|
||||
{skill}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
{/* 个人总结 */}
|
||||
{resumeData.personal_summary && resumeData.personal_summary.trim() !== '' && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">个人总结</p>
|
||||
<div className="personal-summary-content">
|
||||
<p>{resumeData.personal_summary}</p>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
{/* 对应课程单元 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">对应课程单元</p>
|
||||
<ul className="corresponding-course-units-list">
|
||||
<li className="corresponding-course-units-list-item">
|
||||
<div className="tag">方向1</div>
|
||||
<ul className="course-units-list">
|
||||
<li className="course-units-list-item">课程单元名称</li>
|
||||
<li className="course-units-list-item">课程单元名称</li>
|
||||
<li className="course-units-list-item">课程单元名称</li>
|
||||
<li className="course-units-list-item">课程单元名称</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
374
src/pages/CompanyJobsPage/index.css
Normal file
374
src/pages/CompanyJobsPage/index.css
Normal file
@@ -0,0 +1,374 @@
|
||||
.company-jobs-page-wrapper {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.company-jobs-page {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.company-jobs-page-spin {
|
||||
margin: 200px 500px;
|
||||
}
|
||||
|
||||
.company-jobs-page-title {
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
margin-bottom: 20px;
|
||||
color: #1d2129;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 10px;
|
||||
width: 32px;
|
||||
height: 3px;
|
||||
background-image: url("@/assets/images/Common/title_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-left {
|
||||
width: 570px;
|
||||
height: 860px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
.company-jobs-page-left-list-wrapper {
|
||||
width: 100%;
|
||||
height: 760px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-interview-wrapper {
|
||||
width: 572px;
|
||||
height: 860px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.company-jobs-page-interview-expand {
|
||||
height: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.company-jobs-page-interview {
|
||||
width: 100%;
|
||||
height: 504px;
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
|
||||
.company-jobs-page-interview-list {
|
||||
width: 540px;
|
||||
height: 90%;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.company-jobs-page-interview-item {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e6eb;
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
list-style: none;
|
||||
background-color: #e5f1ff;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/jobs_page_left_list_item_bg.png");
|
||||
background-size: 100% 100%;
|
||||
|
||||
.company-jobs-page-interview-item-info {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.company-jobs-page-interview-item-info-position {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
margin-bottom: 5px;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.company-jobs-page-interview-item-info-tags {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.company-jobs-page-interview-item-info-tag {
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
padding: 1px 8px;
|
||||
color: #4e5969;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-interview-item-info-salary {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #ff7d00;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-interview-item-btn-wrapper {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
border: 1px solid #94bfff;
|
||||
border-radius: 4px;
|
||||
background-color: #e8f3ff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
|
||||
> span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #4e5969;
|
||||
}
|
||||
.company-jobs-page-interview-item-btn {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #4e5969;
|
||||
}
|
||||
.company-jobs-page-interview-item-btn-active {
|
||||
color: #2c7aff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-process-wrapper-close {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 20px;
|
||||
right: 0;
|
||||
width: 96px;
|
||||
height: 66px;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process_wrapper_close_bg.png");
|
||||
background-size: 100% 100%;
|
||||
cursor: pointer;
|
||||
|
||||
.company-jobs-page-process-wrapper-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.company-jobs-page-process-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-process-wrapper-expand {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 572px;
|
||||
height: 340px;
|
||||
background-image: linear-gradient(270deg, #e6f2ff, #ffffff);
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
|
||||
.company-jobs-page-process-wrapper-title {
|
||||
width: 100%;
|
||||
padding-bottom: 40px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
margin-bottom: 20px;
|
||||
color: #1d2129;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/close_icon.png");
|
||||
background-size: 100% 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 40px;
|
||||
width: 32px;
|
||||
height: 3px;
|
||||
background-image: url("@/assets/images/Common/title_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-process-content {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 80px 20px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.company-jobs-page-process-item-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-size: 100% 100%;
|
||||
position: relative;
|
||||
|
||||
> p {
|
||||
width: 84px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -40px;
|
||||
transform: translateX(-50%);
|
||||
color: #4e5969;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-process-item-round-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process_dot.png");
|
||||
background-size: 100% 100%;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: -40px;
|
||||
transform: translateX(-50%);
|
||||
width: 132px;
|
||||
height: 25px;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 68px;
|
||||
height: 0px;
|
||||
border: 1px dashed #c9cdd4;
|
||||
}
|
||||
}
|
||||
.icon1 {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process1.png");
|
||||
}
|
||||
.icon2 {
|
||||
&::before {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process2.png");
|
||||
}
|
||||
}
|
||||
.icon3 {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process3.png");
|
||||
> p {
|
||||
bottom: -20px;
|
||||
}
|
||||
}
|
||||
.icon4 {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process4.png");
|
||||
margin: 0 48px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -68px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 68px;
|
||||
height: 0px;
|
||||
border: 1px dashed #c9cdd4;
|
||||
}
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -68px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 68px;
|
||||
height: 0px;
|
||||
border: 1px dashed #c9cdd4;
|
||||
}
|
||||
}
|
||||
.icon5 {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process5.png");
|
||||
> p {
|
||||
bottom: -20px;
|
||||
}
|
||||
}
|
||||
.icon6 {
|
||||
&::before {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process6.png");
|
||||
}
|
||||
}
|
||||
.icon7 {
|
||||
background-image: url("@/assets/images/CompanyJobsPage/process7.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
286
src/pages/CompanyJobsPage/index.jsx
Normal file
286
src/pages/CompanyJobsPage/index.jsx
Normal file
@@ -0,0 +1,286 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
// import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; // 不再需要映射器,mock数据已经是前端格式
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
import {
|
||||
getCompanyJobsPageData,
|
||||
getJobsList,
|
||||
getInterviewsList,
|
||||
} from "@/services";
|
||||
|
||||
import "./index.css";
|
||||
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);
|
||||
const [interviews, setInterviews] = useState([]);
|
||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 初始化页面数据 - 使用聚合接口
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await getCompanyJobsPageData({
|
||||
studentId: studentInfo?.id,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// 设置岗位数据
|
||||
if (res.data?.jobs) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const jobs = res.data.jobs.list || [];
|
||||
setJobs(jobs);
|
||||
setJobsListHasMore(res.data.jobs.hasMore);
|
||||
if (jobs.length > 0) {
|
||||
setJobsListPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
// 设置面试数据
|
||||
if (res.data?.interviews) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const interviews = res.data.interviews.list || [];
|
||||
setInterviews(interviews);
|
||||
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||
if (interviews.length > 0) {
|
||||
setInterviewsPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
setInitialDataLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch initial page data:", error);
|
||||
// 如果聚合接口失败,回退到原来的方式
|
||||
setInitialDataLoaded(true);
|
||||
// 显示错误信息给用户
|
||||
if (toast && toast.error) {
|
||||
toast.error("加载数据失败,请刷新重试");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, [studentInfo?.id]);
|
||||
|
||||
// 获取面试信息 - 用于分页加载更多
|
||||
const fetchInterviewsData = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (studentInfo?.id) {
|
||||
const res = await getInterviewsList({
|
||||
page: interviewsPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id,
|
||||
status: "SCHEDULED",
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const interviews = res.data || [];
|
||||
setInterviews((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(
|
||||
prevList.map((interview) => interview.id)
|
||||
);
|
||||
const newInterviews = interviews.filter(
|
||||
(interview) => !existingIds.has(interview.id)
|
||||
);
|
||||
|
||||
const newList = [...prevList, ...newInterviews];
|
||||
if (res.total <= newList?.length) {
|
||||
setInterviewsHasMore(false);
|
||||
} else {
|
||||
setInterviewsPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
} else {
|
||||
if (interviewsPage === 1) {
|
||||
setInterviews([]);
|
||||
}
|
||||
toast.error(res.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业内推岗位 - 用于分页加载更多
|
||||
const fetchJobsList = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复请求
|
||||
if (jobsListPage === 1 && jobs.length === 0) {
|
||||
return; // 初始数据应该通过聚合接口加载
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getJobsList({
|
||||
page: jobsListPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const jobs = res.data;
|
||||
setJobs((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(prevList.map((job) => job.id));
|
||||
const newJobs = jobs.filter((job) => !existingIds.has(job.id));
|
||||
|
||||
const newList = [...prevList, ...newJobs];
|
||||
if (res.total <= newList?.length) {
|
||||
setJobsListHasMore(false);
|
||||
} else {
|
||||
setJobsListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
if (jobsListPage === 1) {
|
||||
setJobs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleJobWrapperClick = () => {
|
||||
navigate("/company-jobs-list");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="company-jobs-page-wrapper">
|
||||
<div className="company-jobs-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="company-jobs-page-spin" />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="company-jobs-page-left"
|
||||
onClick={handleJobWrapperClick}
|
||||
>
|
||||
<p className="company-jobs-page-title">企业内推岗位库</p>
|
||||
<InfiniteScroll
|
||||
loadMore={fetchJobsList}
|
||||
hasMore={jobsListHasMore}
|
||||
className="company-jobs-page-left-list-wrapper"
|
||||
>
|
||||
<JobList data={jobs} />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-wrapper">
|
||||
<div
|
||||
className={`${
|
||||
isExpand
|
||||
? "company-jobs-page-interview"
|
||||
: "company-jobs-page-interview company-jobs-page-interview-expand"
|
||||
}`}
|
||||
>
|
||||
<p className="company-jobs-page-title">内推岗位面试</p>
|
||||
<InfiniteScroll
|
||||
loadMore={fetchInterviewsData}
|
||||
hasMore={interviewsHasMore}
|
||||
empty={interviews.length === 0}
|
||||
className="company-jobs-page-interview-list"
|
||||
>
|
||||
{interviews.map((item) => (
|
||||
<li
|
||||
className="company-jobs-page-interview-item"
|
||||
key={item.id}
|
||||
>
|
||||
<div className="company-jobs-page-interview-item-info">
|
||||
<p className="company-jobs-page-interview-item-info-position">
|
||||
{item.position}
|
||||
</p>
|
||||
{item.job?.tags?.length > 0 ? (
|
||||
<ul className="company-jobs-page-interview-item-info-tags">
|
||||
{item.job.tags.map((tag) => (
|
||||
<li
|
||||
className="company-jobs-page-interview-item-info-tag"
|
||||
key={tag}
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<span className="company-jobs-page-interview-item-info-salary">
|
||||
{item.job?.salary || "面议"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-item-btn-wrapper">
|
||||
<span>{item.interviewTime}</span>
|
||||
<div
|
||||
className={`company-jobs-page-interview-item-btn ${
|
||||
item.status !== "COMPLETED" &&
|
||||
"company-jobs-page-interview-item-btn-active"
|
||||
}`}
|
||||
>
|
||||
{item.statusText}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyJobsPage;
|
||||
Reference in New Issue
Block a user