UI优化更新:面试模拟、简历面试、项目库、求职策略等多个页面改进

主要更新:
- 面试模拟页:移除上滑查看评价,添加渐进式评分(72→81→89)
- 简历面试页:添加岗位头像、标签背景、面试题加粗等视觉优化
- 项目库页:添加"我完成的项目库"板块,增加hover效果
- 求职策略详情页:优化圆柱体和矩形对齐,添加CSV岗位数据,调整批次文字位置
- 企业岗位列表页:添加返回按钮功能
- 全局:统一岗位级别术语(普通岗/技术骨干岗/储备干部岗)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-05 20:46:03 +08:00
parent 1703894e74
commit 4e0e96e6b8
64 changed files with 7806 additions and 2112 deletions

View File

@@ -1,8 +1,61 @@
.company-jobs-list-page {
width: 100%;
height: 100%;
position: relative;
/* 返回按钮样式 */
.back-button-wrapper {
position: absolute;
top: 20px;
left: 20px; /* 与岗位卡片左对齐 */
z-index: 10;
}
.back-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #ffffff;
border: 1px solid #e5e6eb;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
color: #4e5969;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
&:hover {
background-color: #f7f8fa;
border-color: #2c7aff;
color: #2c7aff;
transform: translateX(-2px);
box-shadow: 0 4px 8px rgba(44, 122, 255, 0.15);
}
&:active {
transform: scale(0.98) translateX(-2px);
}
.back-icon {
font-size: 18px;
line-height: 1;
font-weight: 600;
}
.back-text {
font-size: 14px;
}
}
}
.company-jobs-list-page-wrapper {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px;
padding-top: 60px;
.jobs-list-margin {
> li {

View File

@@ -1,4 +1,5 @@
import { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import JobList from "@/pages/CompanyJobsPage/components/JobList";
import InfiniteScroll from "@/components/InfiniteScroll";
import { getJobsList } from "@/services";
@@ -8,10 +9,16 @@ import "./index.css";
const PAGE_SIZE = 20;
const CompanyJobsListPage = () => {
const navigate = useNavigate();
const [jobs, setJobs] = useState([]);
const [listPage, setListPage] = useState(1);
const [listHasMore, setListHasMore] = useState(true);
// 返回到企业内推岗位页面
const handleBack = () => {
navigate("/company-jobs");
};
const fetchJobs = useCallback(async () => {
const res = await getJobsList({
page: listPage,
@@ -33,14 +40,24 @@ const CompanyJobsListPage = () => {
}, [listPage]);
return (
<InfiniteScroll
loadMore={fetchJobs}
hasMore={listHasMore}
empty={jobs.length === 0}
className="company-jobs-list-page-wrapper"
>
<JobList data={jobs} className="jobs-list-margin" />
</InfiniteScroll>
<div className="company-jobs-list-page">
{/* 返回按钮 */}
<div className="back-button-wrapper">
<button className="back-button" onClick={handleBack}>
<span className="back-icon"></span>
<span className="back-text">返回</span>
</button>
</div>
<InfiniteScroll
loadMore={fetchJobs}
hasMore={listHasMore}
empty={jobs.length === 0}
className="company-jobs-list-page-wrapper"
>
<JobList data={jobs} className="jobs-list-margin" />
</InfiniteScroll>
</div>
);
};

View File

@@ -7,6 +7,10 @@
align-items: center;
justify-content: flex-start;
background-color: #f2f3f5;
background-image: url("@/assets/images/CompanyJobsPage/background.png");
background-size: auto;
background-position: top right;
background-repeat: no-repeat;
border-radius: 8px;
box-sizing: border-box;
overflow: auto;
@@ -61,6 +65,8 @@
.file-icon {
width: 68px;
height: 68px;
filter: none !important;
box-shadow: none !important;
}
.file-info {
@@ -103,6 +109,21 @@
color: #2c7aff;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
background-color: #ffffff;
font-weight: 500;
&:hover {
background-color: #2c7aff;
color: #ffffff;
box-shadow: 0 2px 8px rgba(44, 122, 255, 0.3);
transform: translateY(-1px);
}
&:active {
transform: scale(0.98);
box-shadow: 0 1px 4px rgba(44, 122, 255, 0.2);
}
}
}
}

View File

@@ -63,10 +63,7 @@ export default ({ visible, onClose, data, directToResume = false }) => {
setResumeModalShow(true);
};
const onSearch = (value) => {
// todo
console.log(value);
};
// 选择简历投递
const userResumesClick = async (item) => {
@@ -157,12 +154,7 @@ export default ({ visible, onClose, data, directToResume = false }) => {
<div className="job-info-modal-content">
{resumeModalShow ? (
<>
<InputSearch
className="job-info-modal-search"
onSearch={onSearch}
searchButton
placeholder="搜索简历"
/>
{
<InfiniteScroll
loadMore={queryResumeList}
@@ -178,7 +170,7 @@ export default ({ visible, onClose, data, directToResume = false }) => {
<li
key={item.id}
className="list-item"
onClick={() => userResumesClick(item)}
onClick={(e) => userResumesBtnClick(e, item)}
>
<div className="list-item-info">
<img src={FILEICON} className="file-icon" />
@@ -195,9 +187,12 @@ export default ({ visible, onClose, data, directToResume = false }) => {
</div>
<div
className="info-btn"
onClick={(e) => userResumesBtnClick(e, item)}
onClick={(e) => {
e.stopPropagation();
userResumesClick(item);
}}
>
简历详情
投递
</div>
</li>
))}

View File

@@ -24,6 +24,14 @@
background-color: #e5f1ff;
background-image: url("@/assets/images/CompanyJobsPage/jobs_page_left_list_item_bg.png");
background-size: 100% 100%;
transition: all 0.3s ease;
&:hover {
border-color: #4080ff;
box-shadow: 0 4px 12px rgba(44, 127, 255, 0.15);
transform: translateY(-2px);
background-color: #d9e9ff;
}
.icon {
position: absolute;
@@ -115,6 +123,13 @@
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
&:hover {
background-color: #0056b3;
box-shadow: 0 2px 6px rgba(0, 119, 255, 0.3);
transform: scale(1.05);
}
> i {
width: 12px;

View File

@@ -1,374 +1,433 @@
.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");
}
}
}
}
}
.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: 6px;
background-image: url("@/assets/images/Common/title_icon.png");
background-size: contain;
background-repeat: no-repeat;
}
}
.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-header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.company-jobs-page-title {
font-size: 20px;
font-weight: 600;
line-height: 30px;
color: #1d2129;
margin: 0;
flex: 1;
}
.view-all-jobs-btn {
padding: 6px 16px;
background-color: #ffffff;
border: 1px solid #2c7aff;
border-radius: 4px;
color: #2c7aff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
flex-shrink: 0;
margin-left: 20px;
&:hover {
background-color: #2c7aff;
color: #ffffff;
box-shadow: 0 2px 4px rgba(44, 122, 255, 0.2);
}
&:active {
transform: scale(0.98);
}
}
}
.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: 860px;
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: 760px;
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%;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
border-color: #4080ff;
box-shadow: 0 4px 12px rgba(44, 127, 255, 0.15);
transform: translateY(-2px);
background-color: #d9e9ff;
}
.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;
transition: all 0.3s ease;
&:hover {
color: #1967d2;
text-decoration: underline;
transform: translateX(2px);
}
}
}
}
}
}
}
.company-jobs-page-process-wrapper-close {
position: fixed;
z-index: 1000;
bottom: 20px;
right: 20px;
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: fixed;
z-index: 1000;
bottom: 20px;
right: 20px;
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");
}
}
}
}
}

View File

@@ -178,9 +178,28 @@ const CompanyJobsPage = () => {
<>
<div
className="company-jobs-page-left"
onClick={handleJobWrapperClick}
>
<p className="company-jobs-page-title">企业内推岗位库</p>
<div className="company-jobs-page-header">
<p className="company-jobs-page-title">
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5w4Kcw4H.png"
alt="icon"
style={{
width: '24px',
height: '24px',
marginRight: '8px',
verticalAlign: 'middle'
}}
/>
企业内推岗位库
</p>
<button
className="view-all-jobs-btn"
onClick={handleJobWrapperClick}
>
查看全部岗位
</button>
</div>
<InfiniteScroll
loadMore={fetchJobsList}
hasMore={jobsListHasMore}
@@ -191,13 +210,21 @@ const CompanyJobsPage = () => {
</div>
<div className="company-jobs-page-interview-wrapper">
<div
className={`${
isExpand
? "company-jobs-page-interview"
: "company-jobs-page-interview company-jobs-page-interview-expand"
}`}
className="company-jobs-page-interview"
>
<p className="company-jobs-page-title">内推岗位面试</p>
<p className="company-jobs-page-title">
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5wqNngw9.png"
alt="icon"
style={{
width: '24px',
height: '24px',
marginRight: '8px',
verticalAlign: 'middle'
}}
/>
岗位面试状态
</p>
<InfiniteScroll
loadMore={fetchInterviewsData}
hasMore={interviewsHasMore}

View File

@@ -1,77 +1,195 @@
.homework-page-wrapper {
width: 100%;
height: 100%;
background-color: #f5f5f5;
padding: 20px;
overflow-y: auto;
.homework-page-content {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
box-sizing: border-box;
.homework-page-content-list {
width: 1120px;
height: 360px;
border-radius: 8px;
background-color: #fff;
margin-bottom: 20px;
box-sizing: border-box;
padding: 20px 15px;
.homework-page-content-list-title {
font-size: 20px;
font-weight: 600;
color: #000;
}
.homework-page-content-list-class {
height: 280px;
margin-top: 20px;
overflow-x: auto;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
.homework-page-content-list-content-item {
flex-shrink: 0;
width: 164px;
height: 100%;
margin-right: 20px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.homework-page-content-list-content-item-icon {
width: 164px;
height: 184px;
}
.homework-page-content-list-content-item-name {
width: 100%;
margin: 10px 0;
text-align: center;
font-size: 12px;
font-weight: 600;
color: #1d2129;
}
.homework-page-content-list-content-item-btn {
cursor: pointer;
width: 109px;
height: 32px;
background-color: #2c7aff;
text-align: center;
line-height: 32px;
color: #fff;
border-radius: 2px;
}
}
}
}
}
}
.homework-page-wrapper {
width: 100%;
height: 100%;
background-color: #f5f5f5;
padding: 20px;
overflow-y: auto;
.homework-page-content {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
box-sizing: border-box;
.homework-page-content-list {
width: 1120px;
min-height: 360px;
border-radius: 8px;
background-color: #fff;
margin-bottom: 20px;
box-sizing: border-box;
padding: 20px 15px;
position: relative;
overflow: hidden; /* 重要:隐藏溢出内容 */
.homework-page-content-list-title {
font-size: 20px;
font-weight: 600;
color: #000;
}
.homework-page-content-list-class {
width: calc(100% + 30px); /* 扩展到padding外 */
height: 300px;
margin-top: 20px;
margin-left: -15px;
margin-right: -15px;
padding: 0 15px;
padding-bottom: 10px;
overflow-x: auto;
overflow-y: hidden;
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 20px;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
height: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c9cdd4;
border-radius: 4px;
&:hover {
background: #86909c;
}
}
.homework-page-content-list-content-item {
flex: 0 0 164px;
width: 164px;
height: 260px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.homework-page-content-list-content-item-icon {
width: 164px;
height: 184px;
}
.homework-page-content-list-content-item-name {
width: 100%;
margin: 10px 0;
text-align: center;
font-size: 12px;
font-weight: 600;
color: #1d2129;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 5px;
cursor: default;
}
.homework-page-content-list-content-item-btn {
cursor: pointer;
width: 109px;
height: 32px;
text-align: center;
line-height: 32px;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
&.completed {
background: linear-gradient(135deg, #2c7aff 0%, #4096ff 100%);
color: #fff;
box-shadow: 0 2px 4px rgba(44, 122, 255, 0.2);
&:hover {
background: linear-gradient(135deg, #4096ff 0%, #69b1ff 100%);
box-shadow: 0 4px 8px rgba(44, 122, 255, 0.3);
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
}
&.disabled {
background-color: #f5f5f5;
color: #c9cdd4;
border: 1px solid #e5e6eb;
cursor: not-allowed;
&:hover {
transform: none;
box-shadow: none;
}
}
}
}
}
}
}
}
/* iframe页面样式 */
.homework-page-iframe-wrapper {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #fff;
.homework-page-iframe-header {
height: 60px;
padding: 0 20px;
display: flex;
align-items: center;
border-bottom: 1px solid #e5e6eb;
background-color: #fff;
position: relative;
.homework-page-return-btn {
display: flex;
align-items: center;
padding: 8px 16px;
background: linear-gradient(135deg, #2c7aff 0%, #4096ff 100%);
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: linear-gradient(135deg, #4096ff 0%, #69b1ff 100%);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(44, 122, 255, 0.3);
}
&:active {
transform: translateY(0);
}
}
.homework-page-iframe-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
font-weight: 600;
color: #1d2129;
}
}
.homework-page-iframe-content {
flex: 1;
width: 100%;
border: none;
}
}

View File

@@ -1,3 +1,6 @@
import { useEffect, useRef, useState } from "react";
import { Tooltip } from "@arco-design/web-react";
import { IconArrowLeft } from "@arco-design/web-react/icon";
import { mockData } from "@/data/mockData";
import ICON1 from "@/assets/images/HomeworkPage/homework_page_icon1.png";
import ICON2 from "@/assets/images/HomeworkPage/homework_page_icon2.png";
@@ -5,27 +8,114 @@ import "./index.css";
const HomeworkPage = () => {
const { homework } = mockData;
const scrollContainerRef = useRef(null);
const [showIframe, setShowIframe] = useState(false);
// 调试:打印课程数量
console.log('作业数据:', homework);
if (homework && homework[0]) {
console.log('复合能力培养课程数量:', homework[0].list.length);
}
// 添加鼠标滚轮横向滚动支持(更丝滑的滚动)
useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
let animationId = null;
let targetScrollLeft = container.scrollLeft;
// 平滑滚动动画
const smoothScroll = () => {
const currentScrollLeft = container.scrollLeft;
const diff = targetScrollLeft - currentScrollLeft;
if (Math.abs(diff) > 0.5) {
container.scrollLeft = currentScrollLeft + diff * 0.15; // 缓动系数
animationId = requestAnimationFrame(smoothScroll);
} else {
container.scrollLeft = targetScrollLeft;
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
};
// 鼠标滚轮横向滚动
const handleWheel = (e) => {
e.preventDefault();
// 计算滚动距离,使用较小的值让滚动更平滑
const scrollAmount = e.deltaY * 0.8;
targetScrollLeft = container.scrollLeft + scrollAmount;
// 限制滚动范围
targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, container.scrollWidth - container.clientWidth));
// 如果没有正在进行的动画,启动平滑滚动
if (!animationId) {
animationId = requestAnimationFrame(smoothScroll);
}
};
container.addEventListener('wheel', handleWheel, { passive: false });
return () => {
container.removeEventListener('wheel', handleWheel);
if (animationId) {
cancelAnimationFrame(animationId);
}
};
}, []);
const handleClickBtn = (sectionId, itemId) => {
// 只有复合能力培养的第个项目(展会策划教学)可以点击
if (sectionId === 1 && itemId === 1) {
window.open("https://du9uay.github.io/zhanhui/", "_blank");
// 只有复合能力培养的第72个项目(展会策划教学)可以点击
if (sectionId === 1 && itemId === 72) {
setShowIframe(true);
}
};
// 判断是否应该显示灰色图片和禁用按钮
const isDisabled = (sectionId, itemId) => {
// 只有复合能力培养的第一个项目是启用状态
return !(sectionId === 1 && itemId === 1);
// 只有复合能力培养的展会策划教学第72个是启用状态
return !(sectionId === 1 && itemId === 72);
};
// 如果显示iframe渲染全页面的iframe
if (showIframe) {
return (
<div className="homework-page-iframe-wrapper">
<div className="homework-page-iframe-header">
<button
className="homework-page-return-btn"
onClick={() => setShowIframe(false)}
>
<IconArrowLeft style={{ marginRight: '8px' }} />
返回课后作业
</button>
<span className="homework-page-iframe-title">展会策划教学</span>
</div>
<iframe
src="https://du9uay.github.io/zhanhui/"
className="homework-page-iframe-content"
title="展会策划教学"
/>
</div>
);
}
// 正常渲染作业列表
return (
<div className="homework-page-wrapper">
<ul className="homework-page-content">
{homework.map((item) => (
<li key={item.id} className="homework-page-content-list">
<p className="homework-page-content-list-title">{item.name}</p>
<ul className="homework-page-content-list-class">
<ul
className="homework-page-content-list-class"
ref={item.id === 1 ? scrollContainerRef : null}
>
{item.list.map((contentItem) => (
<li
key={contentItem.id}
@@ -35,25 +125,19 @@ const HomeworkPage = () => {
alt="icon"
src={ICON1}
className="homework-page-content-list-content-item-icon"
style={{
filter: isDisabled(item.id, contentItem.id) ? "grayscale(100%)" : "none",
opacity: isDisabled(item.id, contentItem.id) ? 0.6 : 1
}}
/>
<p className="homework-page-content-list-content-item-name">
{contentItem.name}
</p>
<Tooltip content={contentItem.name} position="top">
<p className="homework-page-content-list-content-item-name">
{contentItem.name}
</p>
</Tooltip>
<div
className={`homework-page-content-list-content-item-btn ${
isDisabled(item.id, contentItem.id) ? "disabled" : ""
isDisabled(item.id, contentItem.id) ? "disabled" : "completed"
}`}
onClick={() => !isDisabled(item.id, contentItem.id) && handleClickBtn(item.id, contentItem.id)}
style={{
cursor: isDisabled(item.id, contentItem.id) ? "not-allowed" : "pointer",
opacity: isDisabled(item.id, contentItem.id) ? 0.5 : 1
}}
>
完成作业
完成
</div>
</li>
))}

View File

@@ -3,13 +3,15 @@
height: 860px;
display: flex;
justify-content: flex-start;
align-items: center;
align-items: flex-start;
flex-direction: column;
border-radius: 8px;
box-sizing: border-box;
background-color: #fff;
padding: 16px;
background-color: #f2f3f5;
padding: 0;
overflow-y: auto;
overflow-x: hidden;
gap: 20px;
.interview-rating-header {
width: 100%;
@@ -28,69 +30,52 @@
}
}
.interview-rating-video-content {
.interview-rating-video-section {
width: 100%;
position: relative;
background-color: #fff;
padding: 16px;
border-radius: 8px;
box-sizing: border-box;
.interview-rating-video {
width: 100%;
height: 670px;
height: 450px;
border-radius: 8px;
overflow: hidden;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
> video {
width: 100%;
height: 100%;
object-fit: cover;
}
> img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.interview-rating-slide {
width: 100%;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 2px;
background-color: #e8f3ff;
margin-top: 60px;
> i {
width: 18px;
height: 18px;
background-size: 100% 100%;
margin: 0 5px;
background-image: url("@/assets/images/InterviewSimulationPage/slide_up_icon.png");
}
> span {
font-size: 14px;
color: #2c7aff;
font-weight: 600;
}
}
}
/* 滑动后 */
.interview-evaluation-wrapper {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
overflow-y: auto;
/* 面试评分图表区域 */
.interview-rating-header {
justify-content: flex-start;
}
.interview-evaluation-charts-wrapper {
width: 100%;
height: 660px;
box-sizing: border-box;
background-color: #fff;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
flex-shrink: 0;
.interview-evaluation-charts-wrapper {
width: 100%;
box-sizing: border-box;
background-color: #fff;
padding: 16px;
border-radius: 8px;
flex-shrink: 0;
.charts-content {
width: 100%;
@@ -180,36 +165,32 @@
}
}
.interview-evaluation-text-wrapper {
width: 100%;
flex-shrink: 0;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
box-sizing: border-box;
background-color: #fff;
padding: 16px;
border-radius: 8px;
.interview-evaluation-text-wrapper {
width: 100%;
flex-shrink: 0;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
box-sizing: border-box;
background-color: #fff;
padding: 16px;
border-radius: 8px;
.interview-rating-text {
width: 100%;
border-radius: 8px;
background-color: #f2f3f5;
text-align: left;
box-sizing: border-box;
padding: 16px;
color: #1d2129;
font-size: 16px;
font-weight: 400;
}
.interview-rating-text {
width: 100%;
border-radius: 8px;
background-color: #f2f3f5;
text-align: left;
box-sizing: border-box;
padding: 16px;
color: #1d2129;
font-size: 16px;
font-weight: 400;
line-height: 1.6;
white-space: pre-wrap;
}
}
}
.interview-rating-wrapper2 {
padding: 0px;
border-radius: 0;
overflow: auto;
background-color: #f2f3f5;
}

View File

@@ -3,15 +3,7 @@ import ScoreChart from "../ScoreChart";
import RadarChart from "../RadarChart";
import "./index.css";
// 滑动阈值(向上滑动超过这个距离触发切换)
const SWIPE_THRESHOLD = 100;
export default ({ selectedItem = "求职面试初体验" }) => {
const [isSlide, setIsSlide] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [startY, setStartY] = useState(0);
const [currentY, setCurrentY] = useState(0);
const slideRef = useRef(null);
// 根据选中项目获取对应的视频URL
const getVideoUrl = () => {
switch(selectedItem) {
@@ -19,6 +11,12 @@ export default ({ selectedItem = "求职面试初体验" }) => {
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJSOKoqAm.mov";
case "未来的自己":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJT02CMM5.mp4";
case "第一次线下面试模拟":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJSOKoqAm.mov"; // 使用相同视频作为示例
case "第二次线下面试模拟":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJT02CMM5.mp4"; // 使用相同视频作为示例
case "第三次线下面试模拟":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJSOKoqAm.mov"; // 使用相同视频作为示例
default:
return "";
}
@@ -26,7 +24,9 @@ export default ({ selectedItem = "求职面试初体验" }) => {
// 判断是否是锁定的面试模拟项目
const isLockedItem = () => {
return selectedItem?.includes("线下面试模拟");
return selectedItem === "第一次线下面试模拟" ||
selectedItem === "第二次线下面试模拟" ||
selectedItem === "第三次线下面试模拟";
};
// 根据选中项目获取评价数据
@@ -42,11 +42,47 @@ export default ({ selectedItem = "求职面试初体验" }) => {
沟通表达上,候选人语言表达流畅自然,逻辑思维清晰有条理,能够积极主动地与面试官互动。在面对开放性问题时展现出良好的思维发散能力,回答层次分明,重点突出,表现出强烈的学习意愿和职业发展规划意识。整体表现超出预期,具备优秀的职场素养。`
};
} else if (selectedItem === "第一次线下面试模拟") {
return {
totalScore: 72,
professionalScore: 28.0,
performanceScore: 44.0,
radarData: [7, 6, 7, 7],
radarData2: [6, 6, 5, 6],
title: "初次模拟评价",
content: `在专业能力方面,候选人对基础知识有一定了解,但在深入问题上表现不够自信。回答问题时思路基本清晰,但缺乏具体案例支撑。对行业动态的了解较为表面,需要加强专业知识的深度学习。在描述项目经验时,表达略显生疏,未能充分展现个人价值。
沟通表达上,候选人表现出一定的紧张情绪,语速较快,有时会出现词不达意的情况。逻辑性有待加强,回答问题时容易偏离主题。肢体语言不够自然,眼神交流不足。建议多进行面试练习,提升表达的流畅度和自信心。整体来看,还有较大的提升空间。`
};
} else if (selectedItem === "第二次线下面试模拟") {
return {
totalScore: 81,
professionalScore: 32.5,
performanceScore: 48.5,
radarData: [8, 7, 8, 8],
radarData2: [7, 7, 6, 7],
title: "进步明显评价",
content: `在专业能力方面,候选人相比第一次有明显进步,对专业知识的理解更加深入。能够结合实际案例阐述观点,展现出一定的实践经验。对于技术问题的回答更有条理,能够抓住问题的关键点。行业认知有所提升,能够说出一些前沿趋势。
沟通表达上,紧张情绪明显缓解,语速适中,表达更加清晰。逻辑结构有所改善,能够按照"总-分-总"的结构组织答案。肢体语言更加自然,与面试官的互动增多。自信心有所提升,敢于表达自己的观点。但在一些压力问题上仍需加强应对能力。`
};
} else if (selectedItem === "第三次线下面试模拟") {
return {
totalScore: 89,
professionalScore: 36.5,
performanceScore: 52.5,
radarData: [9, 8, 9, 9],
radarData2: [8, 8, 7, 8],
title: "接近优秀评价",
content: `在专业能力方面,候选人展现出扎实的专业功底,能够深入浅出地解释复杂概念。项目经验描述详实,能够清楚说明技术难点和解决方案。对行业发展有独到见解,能够将理论与实践很好地结合。专业深度和广度都达到了较高水平。
沟通表达上,表现自然从容,完全没有紧张感。语言组织能力强,逻辑清晰,重点突出。能够根据面试官的反应及时调整表达方式,展现出良好的沟通技巧。肢体语言得体,眼神交流充分,整体气场稳定。已经基本具备了通过正式面试的能力,继续保持即可。`
};
} else {
return {
totalScore: 85,
professionalScore: 38.0,
performanceScore: 50.0,
professionalScore: 34.0,
performanceScore: 51.0,
radarData: [9, 8, 9, 9],
title: "基础面试评价",
content: `在专业能力方面,候选人对 [岗位相关专业知识] 有基本掌握,能够清晰回答与过往项目经验相关的问题,例如在描述 [具体项目] 时,能准确说明自己承担的职责和达成的成果,体现出一定的实践操作能力。但在深入探讨 [某一专业难点] 时,表述略显浅显,对行业前沿动态的了解不够全面,专业深度有提升空间。
@@ -56,164 +92,37 @@ export default ({ selectedItem = "求职面试初体验" }) => {
}
};
// 处理触摸/鼠标开始
const handleStart = (clientY) => {
setIsDragging(true);
setStartY(clientY);
setCurrentY(clientY);
if (slideRef.current) {
slideRef.current.style.transition = "none";
}
// 判断是否应该显示评价内容
const shouldShowEvaluation = () => {
return selectedItem === "求职面试初体验" ||
selectedItem === "未来的自己" ||
selectedItem === "第一次线下面试模拟" ||
selectedItem === "第二次线下面试模拟" ||
selectedItem === "第三次线下面试模拟";
};
// 处理触摸/鼠标移动
const handleMove = (clientY) => {
if (!isDragging) return;
setCurrentY(clientY);
const diffY = startY - clientY; // 正值表示向上滑动,负值表示向下滑动
if (isSlide) {
// 在评价界面时,只允许向下滑动
if (diffY < 0) {
const translateY = Math.min(Math.abs(diffY), 150);
if (slideRef.current) {
slideRef.current.style.transform = `translateY(${translateY}px)`;
}
}
} else {
// 在视频界面时,只允许向上滑动,且只对懵懂初试的项目生效
if (diffY > 0 && (selectedItem === "求职面试初体验" || selectedItem === "未来的自己")) {
const translateY = Math.min(diffY, 150);
if (slideRef.current) {
slideRef.current.style.transform = `translateY(-${translateY}px)`;
}
}
}
};
// 处理触摸/鼠标结束
const handleEnd = () => {
if (!isDragging) return;
setIsDragging(false);
const diffY = startY - currentY;
// 恢复位置
if (slideRef.current) {
slideRef.current.style.transition = "transform 0.3s ease";
slideRef.current.style.transform = "";
}
// 检查滑动方向和阈值
if (isSlide) {
// 在评价界面时,向下滑动返回视频界面
if (diffY < -SWIPE_THRESHOLD) {
handleSwipeDown();
}
} else {
// 在视频界面时,向上滑动到评价界面
if (diffY > SWIPE_THRESHOLD) {
handleSwipeUp();
}
}
};
// 向上滑动事件处理
const handleSwipeUp = () => {
// 只有懵懂初试的项目可以上滑查看评价
if (selectedItem === "求职面试初体验" || selectedItem === "未来的自己") {
console.log("触发向上滑动切换");
setIsSlide(true);
}
};
// 向下滑动事件处理
const handleSwipeDown = () => {
console.log("触发向下滑动返回");
setIsSlide(false);
};
// 鼠标事件处理
const handleMouseDown = (e) => {
handleStart(e.clientY);
};
const handleMouseMove = (e) => {
if (isDragging) {
e.preventDefault();
handleMove(e.clientY);
}
};
const handleMouseUp = () => {
handleEnd();
};
const handleMouseLeave = () => {
handleEnd();
};
// 触摸事件处理
const handleTouchStart = (e) => {
handleStart(e.touches[0].clientY);
};
const handleTouchMove = (e) => {
if (isDragging) {
e.preventDefault();
handleMove(e.touches[0].clientY);
}
};
const handleTouchEnd = () => {
handleEnd();
};
// 添加全局事件监听
useEffect(() => {
const handleGlobalMouseMove = (e) => handleMouseMove(e);
const handleGlobalMouseUp = () => handleMouseUp();
if (isDragging) {
document.addEventListener("mousemove", handleGlobalMouseMove);
document.addEventListener("mouseup", handleGlobalMouseUp);
document.body.style.cursor = "grabbing";
document.body.style.userSelect = "none";
}
return () => {
document.removeEventListener("mousemove", handleGlobalMouseMove);
document.removeEventListener("mouseup", handleGlobalMouseUp);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
}, [isDragging]);
return (
<div
className={`interview-rating-wrapper ${
isSlide ? "interview-rating-wrapper2" : ""
}`}
>
{isSlide ? (
<div
className="interview-evaluation-wrapper"
ref={slideRef}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
cursor: "grab",
userSelect: "none",
WebkitUserSelect: "none",
touchAction: "pan-x",
}}
>
<div className="interview-rating-wrapper">
{/* 视频播放器区域 */}
<div className="interview-rating-video-section">
<div className="interview-rating-header">
<span className="interview-rating-header-title">
钢铁是怎样炼成的
</span>
</div>
<div className="interview-rating-video">
{isLockedItem() ? (
<img src="/线下面试模拟锁定.png" alt="线下面试模拟锁定" style={{width: "100%", height: "100%", objectFit: "cover"}} />
) : (
<video src={getVideoUrl()} controls></video>
)}
</div>
</div>
{/* 评价内容区域 - 仅在特定项目时显示 */}
{shouldShowEvaluation() && (
<>
{/* 面试评分区域 */}
<div className="interview-evaluation-charts-wrapper">
<div className="interview-rating-header">
<span className="interview-rating-header-title">面试评分</span>
@@ -241,16 +150,30 @@ export default ({ selectedItem = "求职面试初体验" }) => {
className="radar-chart"
data={getEvaluationData().radarData}
indicator={[
{ name: "{a|核心知识掌握}\\n{b|与精准应用}", max: 10 },
{ name: "{a|问题分析方案设计}\\n{b|与成果表达}", max: 10 },
{ name: "{a|产业认知与行业}\\n{b|趋势洞察}", max: 10 },
{ name: "{a|企业工作流}\\n{b|理解与实践}", max: 10 },
{ name: "核心知识掌握与精准应用", max: 10 },
{ name: "问题分析方案设计与成果表达", max: 10 },
{ name: "产业认知与行业趋势洞察", max: 10 },
{ name: "企业工作流理解与实践", max: 10 },
]}
/>
<RadarChart className="radar-chart" />
<RadarChart
className="radar-chart"
data={getEvaluationData().radarData2 || [7, 8, 6, 7]}
indicator={[
{ name: "沟通表达与逻辑思维", max: 10 },
{ name: "团队协作与责任意识", max: 10 },
{ name: "学习能力与适应能力", max: 10 },
{ name: "职业素养与发展潜力", max: 10 },
]}
lineClolr="#FFE4D9"
areaColor="#FFD4C1"
areaBorderColor="#FFB89A"
/>
</div>
</div>
</div>
{/* 基础面试评价区域 */}
<div className="interview-evaluation-text-wrapper">
<div className="interview-rating-header">
<span className="interview-rating-header-title">{getEvaluationData().title}</span>
@@ -259,42 +182,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
{getEvaluationData().content}
</div>
</div>
</div>
) : (
<div
className="interview-rating-video-content"
ref={slideRef}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
cursor: "grab",
userSelect: "none",
WebkitUserSelect: "none",
touchAction: "pan-x",
}}
>
<div className="interview-rating-header">
<span className="interview-rating-header-title">
钢铁是怎样炼成的
</span>
</div>
<div className="interview-rating-video">
{isLockedItem() ? (
<img src="/线下面试模拟锁定.png" alt="线下面试模拟锁定" style={{width: "100%", height: "100%", objectFit: "cover"}} />
) : (
<video src={getVideoUrl()} controls></video>
)}
</div>
<div className="interview-rating-slide">
<i />
<span>上滑查看评价</span>
</div>
</div>
</>
)}
</div>
);

View File

@@ -18,22 +18,19 @@
position: relative;
box-sizing: border-box;
font-size: 20px;
font-weight: 600;
font-weight: 700;
line-height: 20px;
color: #1d2129;
padding-bottom: 10px;
border-bottom: 1px solid #e5e6eb;
margin-bottom: 20px;
display: flex;
align-items: center;
&::before {
content: "";
position: absolute;
left: 20px;
bottom: 5px;
width: 31px;
height: 3px;
background-image: url("@/assets/images/Common/title_icon.png");
background-size: 100% 100%;
.mock-interview-title-icon {
width: 24px;
height: 24px;
margin-right: 10px;
}
}

View File

@@ -1,5 +1,6 @@
import { useState } from "react";
import { Timeline } from "@arco-design/web-react";
import IconFont from "@/components/IconFont";
import "./index.css";
const TimelineItem = Timeline.Item;
@@ -14,7 +15,10 @@ export default ({ onItemSelect }) => {
return (
<div className="mock-interview-wrapper">
<p className="mock-interview-title">面试模拟</p>
<p className="mock-interview-title">
<IconFont className="mock-interview-title-icon" src="recuUY5uwYg4km" />
<span>面试模拟</span>
</p>
<ul className="mock-interview-list">
<li className="mock-interview-item">
<p className="mock-interview-item-title">
@@ -33,7 +37,24 @@ export default ({ onItemSelect }) => {
onClick={() => handleItemClick("求职面试初体验")}
style={{ cursor: "pointer" }}
>
<p>求职面试初体验</p>
<p>
求职面试初体验
<span style={{
marginLeft: '8px',
padding: '2px 8px',
background: 'linear-gradient(135deg, #5DADE2 0%, #2874A6 100%)',
borderRadius: '12px',
color: '#ffffff',
fontSize: '12px',
fontWeight: 'bold',
fontStyle: 'italic',
letterSpacing: '1px',
textTransform: 'uppercase',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
demo
</span>
</p>
<div className="time-line-item-info">
初次接触面试环境体验真实面试流程了解自身在面试中的基本表现和待提升的方面
</div>
@@ -48,7 +69,24 @@ export default ({ onItemSelect }) => {
onClick={() => handleItemClick("未来的自己")}
style={{ cursor: "pointer" }}
>
<p>未来的自己</p>
<p>
未来的自己
<span style={{
marginLeft: '8px',
padding: '2px 8px',
background: 'linear-gradient(135deg, #5DADE2 0%, #2874A6 100%)',
borderRadius: '12px',
color: '#ffffff',
fontSize: '12px',
fontWeight: 'bold',
fontStyle: 'italic',
letterSpacing: '1px',
textTransform: 'uppercase',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
demo
</span>
</p>
<div className="time-line-item-info">
经过系统训练后的面试表现展示个人成长和进步体现出更强的职场竞争力和专业素养
</div>
@@ -98,30 +136,6 @@ export default ({ onItemSelect }) => {
<p>第三次线下面试模拟</p>
</div>
</TimelineItem>
<TimelineItem
lineType="dashed"
dot={<div className={`time-line-dot-icon ${selectedItem === "第四次线下面试模拟" ? "time-line-dot-icon-active" : ""}`} />}
>
<div
className={`time-line-item ${selectedItem === "第四次线下面试模拟" ? "time-line-item-active" : ""}`}
onClick={() => handleItemClick("第四次线下面试模拟")}
style={{ cursor: "pointer" }}
>
<p>第四次线下面试模拟</p>
</div>
</TimelineItem>
<TimelineItem
lineType="dashed"
dot={<div className={`time-line-dot-icon ${selectedItem === "第五次线下面试模拟" ? "time-line-dot-icon-active" : ""}`} />}
>
<div
className={`time-line-item ${selectedItem === "第五次线下面试模拟" ? "time-line-item-active" : ""}`}
onClick={() => handleItemClick("第五次线下面试模拟")}
style={{ cursor: "pointer" }}
>
<p>第五次线下面试模拟</p>
</div>
</TimelineItem>
</Timeline>
</li>
</ul>

View File

@@ -1,69 +1,125 @@
import { useEffect, useRef } from "react";
import { useRef, useEffect } from "react";
import * as echarts from "echarts";
export default function RadarChart({
data = [4200, 3000, 20000, 35000, 50000, 18000],
data = [5, 8, 5, 8],
value = null,
indicator = [
{ name: "Sales", max: 6500 },
{ name: "Administration", max: 16000 },
{ name: "Information Technology", max: 30000 },
{ name: "Customer Support", max: 38000 },
{ name: "Development", max: 52000 },
{ name: "Marketing", max: 25000 },
],
className = "",
lineClolr = "#DCDFFF",
areaColor = "#CCCDFC",
areaBorderColor = "#BDB5FF",
}) {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (!chartRef.current) return;
chartInstance.current = echarts.init(chartRef.current);
const option = {
radar: {
const option = {
tooltip: { show: false },
grid: { show: false },
radar: [
{
center: ["50%", "55%"],
indicator,
radius: "65%", // 默认 65%,调大即可把文字整体外推
nameGap: 20, // 再额外增加 6-12px 间距
splitArea: { show: false }, // 关键:不显示花纹
shape: "circle", // 设置雷达图外圈为圆形
// 网格线样式配置
splitLine: {
lineStyle: {
color: lineClolr, // 网格线颜色
width: 2,
type: "solid", // 线条类型solid, dashed, dotted
},
},
splitArea: { show: false },
axisName: {
color: "#4E5969",
fontSize: 12,
align: "center", // 水平居中
verticalAlign: "middle", // 垂直居中
lineHeight: 14, // 与富文本行高一致
rich: {
a: { fontSize: 12, color: "#4E5969" },
b: { fontSize: 12, color: "#4E5969" },
formatter: function(value) {
// 移除富文本标记,只返回纯文本
return value.replace(/{[ab]\|([^}]+)}/g, '$1').replace(/\n/g, '');
},
},
axisLine: {
lineStyle: {
color: "#E5E6EB",
},
},
},
series: [
{
type: "radar",
data: [{ value: data }],
],
series: [
{
type: "radar",
radarIndex: 0,
data: [{
value: value || data,
label: {
// 关键:显示数值
show: true,
formatter: "{c}", // 取当前轴值
show: false, // 关键:隐藏雷达图内的标签
},
emphasis: {
label: {
show: false, // 确保 hover 时也不显示
},
fontSize: 12,
color: "#333",
},
areaStyle: {}, // 关键:显示面积
symbol: "none", // 关键:隐藏所有交点
},
],
};
chartInstance.current.setOption(option);
areaStyle: {
color: areaColor, // 面积填充颜色
}, // 关键:显示面积
lineStyle: {
color: areaBorderColor, // 连接线颜色(边框颜色)
width: 3, // 连接线宽度
type: "solid", // 线条类型
},
symbol: "circle", // 显示端点
symbolSize: 6, // 端点大小
itemStyle: {
color: "#fff", // 端点颜色
borderColor: areaColor, // 端点边框颜色
borderWidth: 2, // 端点边框宽度
},
}],
},
],
};
useEffect(() => {
if (!chartRef.current) return;
// 如果实例不存在,则初始化
if (!chartInstance.current) {
chartInstance.current = echarts.init(chartRef.current);
}
// 设置或更新配置,使用 notMerge: false 来避免重新触发动画
chartInstance.current.setOption(option, {
notMerge: false,
lazyUpdate: true,
silent: false,
});
const handleResize = () => {
chartInstance.current?.resize();
};
window.addEventListener("resize", handleResize);
// 自适应
const resize = () => chartInstance.current?.resize();
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
window.removeEventListener("resize", handleResize);
};
}, [data, value, indicator]);
// 组件卸载时清理
useEffect(() => {
return () => {
chartInstance.current?.dispose();
};
}, [data, indicator, className]);
}, []);
return <div ref={chartRef} className={className} />;
}
}

View File

@@ -0,0 +1,69 @@
import { useEffect, useRef } from "react";
import * as echarts from "echarts";
export default function RadarChart({
data = [4200, 3000, 20000, 35000, 50000, 18000],
indicator = [
{ name: "Sales", max: 6500 },
{ name: "Administration", max: 16000 },
{ name: "Information Technology", max: 30000 },
{ name: "Customer Support", max: 38000 },
{ name: "Development", max: 52000 },
{ name: "Marketing", max: 25000 },
],
className = "",
}) {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (!chartRef.current) return;
chartInstance.current = echarts.init(chartRef.current);
const option = {
radar: {
indicator,
radius: "65%", // 默认 65%,调大即可把文字整体外推
nameGap: 20, // 再额外增加 6-12px 间距
splitArea: { show: false }, // 关键:不显示花纹
axisName: {
color: "#4E5969",
fontSize: 12,
align: "center", // 水平居中
verticalAlign: "middle", // 垂直居中
lineHeight: 14, // 与富文本行高一致
rich: {
a: { fontSize: 12, color: "#4E5969" },
b: { fontSize: 12, color: "#4E5969" },
},
},
},
series: [
{
type: "radar",
data: [{ value: data }],
label: {
// 关键:显示数值
show: true,
formatter: "{c}", // 取当前轴值
fontSize: 12,
color: "#333",
},
areaStyle: {}, // 关键:显示面积
symbol: "none", // 关键:隐藏所有交点
},
],
};
chartInstance.current.setOption(option);
// 自适应
const resize = () => chartInstance.current?.resize();
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
chartInstance.current?.dispose();
};
}, [data, indicator, className]);
return <div ref={chartRef} className={className} />;
}

View File

@@ -1,19 +1,14 @@
import { useRef, useEffect, useState } from "react";
import { useRef, useEffect } from "react";
import * as echarts from "echarts";
const screenWidth = window.screen.width;
const ScoreChart = ({
className = "",
value = 70, // 当前得分0-100
colors = [
[1 / 6, "#FF4C3C"],
[2 / 6, "#FF8855"],
[3 / 6, "#FFBA62"],
[4 / 6, "#A5CC58"],
[5 / 6, "#66B86F"],
[1, "#49AB55"],
],
colors = new echarts.graphic.LinearGradient(0, 0, 1, 0, [
// 渐变色配置
{ offset: 0, color: "#2C7FFF" }, // 起始颜色(左端)
{ offset: 1, color: "#2CA9FF" }, // 结束颜色(右端)
]),
}) => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
@@ -24,56 +19,90 @@ const ScoreChart = ({
useEffect(() => {
if (!chartRef.current) return;
// 销毁旧实例
if (chartInstance.current) chartInstance.current.dispose();
// 新建实例
chartInstance.current = echarts.init(chartRef.current);
// 如果实例不存在,则初始化
if (!chartInstance.current) {
chartInstance.current = echarts.init(chartRef.current);
}
const option = {
series: [
{
type: "gauge",
center: ["50%", "80%"],
startAngle: 180,
endAngle: 360,
min: 0,
max: 120,
splitNumber: 10,
axisLine: {
lineStyle: {
width: 15,
max: 100,
radius: "100%",
splitNumber: 5,
itemStyle: {
color: "#FFAB91",
},
progress: {
show: true,
width: 20,
roundCap: true, // 开启圆角
itemStyle: {
color: colors,
},
},
pointer: {
length: "60%",
itemStyle: { color: "auto" },
show: false,
},
axisLine: {
lineStyle: {
width: 20,
},
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
detail: {
fontSize: 20,
offsetCenter: [0, "60%"],
valueAnimation: true,
formatter: "{value}.00\n总评分",
color: "inherit",
anchor: {
show: false,
},
radius: "100%",
data: [{ value }],
title: {
show: false,
},
detail: {
valueAnimation: true,
lineHeight: 40,
borderRadius: 8,
offsetCenter: [0, "-15%"],
fontSize: 24,
formatter: "{value}.00 ",
color: "#1D2129",
},
data: [
{
value,
},
],
},
],
};
chartInstance.current.setOption(option);
// 设置或更新配置,避免重新触发动画
chartInstance.current.setOption(option, {
notMerge: false,
lazyUpdate: true,
silent: false,
});
// 监听窗口变化
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
chartInstance.current?.dispose();
};
}, [value, colors]);
// 组件卸载时清理
useEffect(() => {
return () => {
chartInstance.current?.dispose();
};
}, []);
return <div ref={chartRef} className={className} />;
};
export default ScoreChart;
export default ScoreChart;

View File

@@ -0,0 +1,79 @@
import { useRef, useEffect, useState } from "react";
import * as echarts from "echarts";
const screenWidth = window.screen.width;
const ScoreChart = ({
className = "",
value = 70, // 当前得分0-100
colors = [
[1 / 6, "#FF4C3C"],
[2 / 6, "#FF8855"],
[3 / 6, "#FFBA62"],
[4 / 6, "#A5CC58"],
[5 / 6, "#66B86F"],
[1, "#49AB55"],
],
}) => {
const chartRef = useRef(null);
const chartInstance = useRef(null);
// 图表自适应容器
const resize = () => chartInstance.current?.resize();
useEffect(() => {
if (!chartRef.current) return;
// 销毁旧实例
if (chartInstance.current) chartInstance.current.dispose();
// 新建实例
chartInstance.current = echarts.init(chartRef.current);
const option = {
series: [
{
type: "gauge",
min: 0,
max: 120,
splitNumber: 10,
axisLine: {
lineStyle: {
width: 15,
color: colors,
},
},
pointer: {
length: "60%",
itemStyle: { color: "auto" },
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
detail: {
fontSize: 20,
offsetCenter: [0, "60%"],
valueAnimation: true,
formatter: "{value}.00\n总评分",
color: "inherit",
},
radius: "100%",
data: [{ value }],
},
],
};
chartInstance.current.setOption(option);
// 监听窗口变化
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
chartInstance.current?.dispose();
};
}, [value, colors]);
return <div ref={chartRef} className={className} />;
};
export default ScoreChart;

View File

@@ -5,17 +5,17 @@
.target-position-content {
width: 914px;
height: 346px;
margin-top: 150px;
height: 450px;
margin-top: 50px;
margin-left: 50px;
position: relative;
.batch-icon {
width: 190px;
width: 340px;
height: 100%;
position: absolute;
left: 0;
top: 0;
left: -40px;
top: -30px;
background-image: url("@/assets/images/JobStrategyDetailPage/batch.png");
background-size: 100% 100%;
display: flex;
@@ -27,70 +27,219 @@
font-size: 19px;
font-weight: 600;
color: #ffffff;
&:nth-child(1) {
margin-top: 60px; /* 第一批次文字下移到圆柱体中心 */
}
&:nth-child(2) {
margin-top: 25px; /* 第二批次保持原位 */
}
&:nth-child(3) {
margin-top: 25px; /* 第三批次保持原位 */
}
}
}
.batch-content {
width: 702px;
height: 102px;
height: 120px;
position: absolute;
left: 189px;
background-size: 100% 100%;
left: 299px;
display: flex;
justify-content: flex-start;
align-items: center;
box-sizing: border-box;
padding-left: 60px;
padding-left: 25px;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
overflow-x: auto;
overflow-y: hidden;
border-radius: 8px;
scroll-behavior: smooth;
/* 移除背景图片使用CSS渐变 */
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(233, 226, 255, 0.1);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(233, 226, 255, 0.5);
border-radius: 3px;
transition: background 0.3s;
&:hover {
background: rgba(233, 226, 255, 0.7);
}
}
/* Firefox滚动条 */
scrollbar-width: thin;
scrollbar-color: rgba(233, 226, 255, 0.5) rgba(233, 226, 255, 0.1);
.avatar-wrapper {
width: 78px;
height: 78px;
margin-top: -15px;
width: 85px;
height: 100px;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 20px;
margin-right: 15px;
flex-shrink: 0;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-3px);
.student-avatar {
box-shadow: 0 6px 16px rgba(44, 127, 255, 0.25);
}
}
/* 仅在悬停岗位名称时显示气泡 */
.student-name:hover + .position-tooltip {
display: block;
opacity: 1;
visibility: visible;
transform: translate(-50%, -8px);
}
.student-avatar {
position: relative;
width: 60px;
height: 60px;
width: 64px;
height: 64px;
border-radius: 50%;
overflow: hidden;
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 2px solid #ffffff;
transition: all 0.3s ease;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.student-name {
position: absolute;
bottom: -10px;
left: 0;
width: 78px;
height: 30px;
border-radius: 57px;
border: 1px solid #ffffff;
margin-top: 8px;
width: 85px;
height: 24px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.8);
text-align: center;
color: #1d2129;
font-size: 14px;
font-weight: 400;
line-height: 30px;
background-color: #fefcfc;
font-size: 12px;
font-weight: 500;
line-height: 24px;
background-color: rgba(255, 255, 255, 0.95);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 8px;
box-sizing: border-box;
}
/* 气泡提示框 */
.position-tooltip {
position: absolute;
bottom: 35px;
left: 50%;
transform: translate(-50%, 0);
background-color: rgba(0, 0, 0, 0.85);
color: #ffffff;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
z-index: 1000;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
/* 气泡箭头 */
&::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-style: solid;
border-width: 6px 6px 0 6px;
border-color: rgba(0, 0, 0, 0.85) transparent transparent transparent;
}
}
}
}
.batch-content1 {
top: 2px;
background-image: url("@/assets/images/JobStrategyDetailPage/batch1.png");
top: 5px;
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
background: linear-gradient(to right,
#E9E2FF 0%,
rgba(233, 226, 255, 0.95) 10%,
rgba(233, 226, 255, 0.9) 20%,
rgba(233, 226, 255, 0.85) 30%,
rgba(233, 226, 255, 0.75) 40%,
rgba(233, 226, 255, 0.65) 50%,
rgba(233, 226, 255, 0.5) 60%,
rgba(233, 226, 255, 0.35) 70%,
rgba(233, 226, 255, 0.2) 80%,
rgba(233, 226, 255, 0.1) 90%,
rgba(233, 226, 255, 0) 100%);
border: 2px solid #ffffff;
}
.batch-content2 {
top: 123px;
background-image: url("@/assets/images/JobStrategyDetailPage/batch2.png");
top: 140px;
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
background: linear-gradient(to right,
#E9E2FF 0%,
rgba(233, 226, 255, 0.95) 10%,
rgba(233, 226, 255, 0.9) 20%,
rgba(233, 226, 255, 0.85) 30%,
rgba(233, 226, 255, 0.75) 40%,
rgba(233, 226, 255, 0.65) 50%,
rgba(233, 226, 255, 0.5) 60%,
rgba(233, 226, 255, 0.35) 70%,
rgba(233, 226, 255, 0.2) 80%,
rgba(233, 226, 255, 0.1) 90%,
rgba(233, 226, 255, 0) 100%);
border: 2px solid #ffffff;
}
.batch-content3 {
top: 242px;
background-image: url("@/assets/images/JobStrategyDetailPage/batch3.png");
top: 275px;
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
background: linear-gradient(to right,
#E9E2FF 0%,
rgba(233, 226, 255, 0.95) 10%,
rgba(233, 226, 255, 0.9) 20%,
rgba(233, 226, 255, 0.85) 30%,
rgba(233, 226, 255, 0.75) 40%,
rgba(233, 226, 255, 0.65) 50%,
rgba(233, 226, 255, 0.5) 60%,
rgba(233, 226, 255, 0.35) 70%,
rgba(233, 226, 255, 0.2) 80%,
rgba(233, 226, 255, 0.1) 90%,
rgba(233, 226, 255, 0) 100%);
border: 2px solid #ffffff;
}
}
}

View File

@@ -1,8 +1,114 @@
import { useEffect, useRef } from "react";
import { Avatar } from "@arco-design/web-react";
import Locked from "@/components/Locked";
import jobLevelData from "@/data/joblevel.json";
import "./index.css";
export default ({ locked = false }) => {
const batch1Ref = useRef(null);
const batch2Ref = useRef(null);
const batch3Ref = useRef(null);
useEffect(() => {
// 添加鼠标滚轮事件监听,实现横向滚动
const handleWheel = (e, ref) => {
if (ref.current && ref.current.contains(e.target)) {
e.preventDefault();
ref.current.scrollLeft += e.deltaY;
}
};
const batch1El = batch1Ref.current;
const batch2El = batch2Ref.current;
const batch3El = batch3Ref.current;
const wheel1Handler = (e) => handleWheel(e, batch1Ref);
const wheel2Handler = (e) => handleWheel(e, batch2Ref);
const wheel3Handler = (e) => handleWheel(e, batch3Ref);
if (batch1El) batch1El.addEventListener('wheel', wheel1Handler, { passive: false });
if (batch2El) batch2El.addEventListener('wheel', wheel2Handler, { passive: false });
if (batch3El) batch3El.addEventListener('wheel', wheel3Handler, { passive: false });
return () => {
if (batch1El) batch1El.removeEventListener('wheel', wheel1Handler);
if (batch2El) batch2El.removeEventListener('wheel', wheel2Handler);
if (batch3El) batch3El.removeEventListener('wheel', wheel3Handler);
};
}, []);
// 根据岗位名称获取头像
const getPositionAvatar = (positionName) => {
const jobData = jobLevelData.data;
for (const [key, levelData] of Object.entries(jobData)) {
const found = levelData.list.find(item => item.position_name === positionName);
if (found) {
return found.img;
}
}
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png"; // 默认头像
};
// 定义三个批次的岗位数据
const batchPositions = {
batch1: [
"二次元周边店店员",
"会展执行助理",
"会展讲解员",
"会展营销",
"商业会展执行专员",
"景区运营专员",
"文旅运营总监助理",
"品牌策划运营专员",
"品牌推广专员",
"ip运营",
"文创产品设计师助理",
"新媒体运营专员",
"网络运营专员",
"社群运营",
"直播助理"
],
batch2: [
"宠物店店长",
"宠物营养师",
"二次元周边选品专员",
"二次元周边店店长",
"会展策划师",
"漫展策划师",
"活动执行",
"活动策划师",
"酒店运营专员",
"餐厅运营经理",
"露营地运营专员",
"旅游规划师",
"文旅项目投资拓展管培生",
"民宿管家",
"民宿客房管家",
"民宿运营专员",
"品牌公关",
"IP运营总监助理",
"品牌公关管培生",
"直播中控",
"SEO专员",
"SEM专员",
"赛事经纪"
],
batch3: [
"酒店餐饮主管",
"客房经理",
"酒店大堂副理",
"旅游计调专员",
"文创产品策划师",
"文创产品设计师",
"赛事礼仪",
"赛事编辑",
"艺人经纪人",
"演出执行经理",
"场馆运营人员"
]
};
return (
<div className="target-position-wrapper">
<div className="target-position-content">
@@ -11,19 +117,51 @@ export default ({ locked = false }) => {
<span>第二批次</span>
<span>第三批次</span>
</div>
<div className="batch-content batch-content1">
<div className="avatar-wrapper">
<Avatar className="student-avatar">
<img
alt="avatar"
src="//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
/>
</Avatar>
<span className="student-name">张三</span>
</div>
<div className="batch-content batch-content1" ref={batch1Ref}>
{batchPositions.batch1.map((position, index) => (
<div key={index} className="avatar-wrapper">
<div className="student-avatar">
<img
alt="avatar"
src={getPositionAvatar(position)}
/>
</div>
<span className="student-name">{position}</span>
{/* 完整名称的气泡提示 */}
<div className="position-tooltip">{position}</div>
</div>
))}
</div>
<div className="batch-content batch-content2" ref={batch2Ref}>
{batchPositions.batch2.map((position, index) => (
<div key={index} className="avatar-wrapper">
<div className="student-avatar">
<img
alt="avatar"
src={getPositionAvatar(position)}
/>
</div>
<span className="student-name">{position}</span>
{/* 完整名称的气泡提示 */}
<div className="position-tooltip">{position}</div>
</div>
))}
</div>
<div className="batch-content batch-content3" ref={batch3Ref}>
{batchPositions.batch3.map((position, index) => (
<div key={index} className="avatar-wrapper">
<div className="student-avatar">
<img
alt="avatar"
src={getPositionAvatar(position)}
/>
</div>
<span className="student-name">{position}</span>
{/* 完整名称的气泡提示 */}
<div className="position-tooltip">{position}</div>
</div>
))}
</div>
<div className="batch-content batch-content2"></div>
<div className="batch-content batch-content3"></div>
{locked && (
<Locked text="该板块将在「垂直能力提升」阶段开放完成线上1V1求职策略定制后解锁" />

View File

@@ -4,75 +4,203 @@
box-sizing: border-box;
padding: 20px;
/* 返回按钮样式 */
.back-button-wrapper {
position: absolute;
top: 30px;
left: 30px;
z-index: 10;
}
.back-button {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: #ffffff;
border: 1px solid #e5e6eb;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
color: #4e5969;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
&:hover {
background-color: #f7f8fa;
border-color: #2c7aff;
color: #2c7aff;
transform: translateX(-2px);
box-shadow: 0 4px 8px rgba(44, 122, 255, 0.15);
}
&:active {
transform: scale(0.98) translateX(-2px);
}
.back-icon {
font-size: 18px;
line-height: 1;
font-weight: 600;
}
.back-text {
font-size: 14px;
}
}
.job-strategy-detail-wrapper {
width: 100%;
height: 100%;
border-radius: 16px;
background-image: linear-gradient(180deg, #d7e5ff, #eff4fb);
box-sizing: border-box;
padding: 20px;
border: 2px solid #fff;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
position: relative;
.job-strategy-detail-header {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 310px;
height: 50px;
display: flex;
justify-content: space-around;
align-items: center;
background-color: #fff;
border-radius: 25px;
box-sizing: border-box;
padding: 10px;
z-index: 5;
.nav-item {
color: #86909c;
background-color: #f4f7f9;
font-size: 14px;
font-weight: 700;
width: 140px;
height: 34px;
line-height: 34px;
cursor: pointer;
border-radius: 25px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 0 12px;
&:hover {
background-color: #e8f3ff;
}
.nav-icon {
width: 16px;
height: 16px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: inline-block;
flex-shrink: 0;
}
.nav-text {
font-size: 14px;
font-weight: 700;
white-space: nowrap;
}
/* 优先目标岗位图标 */
.target-icon {
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5qlmzVhH.png");
}
/* 曲线就业方案图标 */
.curved-icon {
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVUShwMZfFm.png");
}
}
.item-active {
background-color: #0077ff !important;
color: #fff !important;
/* 选中状态的优先目标岗位图标 */
.target-icon {
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5s6knA9u.png");
}
/* 选中状态的曲线就业方案图标 */
.curved-icon {
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVUSJS109uJ.png");
}
}
}
.job-strategy-detail-content {
width: 100%;
height: calc(100% - 80px);
position: relative;
overflow-y: auto;
margin-top: 80px;
}
}
.job-strategy-detail-body {
width: 100%;
height: 100%;
border-radius: 8px;
background-color: #fff;
border-radius: 16px;
background-image: linear-gradient(180deg, #d7e5ff, #eff4fb);
box-sizing: border-box;
padding: 20px;
border: 2px solid #fff;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.job-strategy-detail-header {
width: 100%;
height: 32px;
width: 310px;
height: 50px;
display: flex;
justify-content: flex-start;
justify-content: space-around;
align-items: center;
background-color: #fff;
border-radius: 25px;
box-sizing: border-box;
padding: 10px;
> div {
color: #4e5969;
color: #86909c;
background-color: #f4f7f9;
font-size: 14px;
font-weight: 700;
line-height: 32px;
box-sizing: border-box;
padding: 0 12px;
width: 140px;
height: 34px;
line-height: 34px;
text-align: center;
cursor: pointer;
border-radius: 25px;
}
.item-active {
background-color: #f2f3f5;
border-radius: 100px;
color: #2c7aff;
color: #fff;
background-color: #0077ff;
}
}
}
.job-strategy-detail-content {
width: 100%;
height: 700px;
margin-top: 50px;
height: 100%;
position: relative;
.slide-wrapper {
width: 123px;
height: 125px;
position: absolute;
top: 20%;
right: 0;
cursor: pointer;
&::before {
content: "";
position: absolute;
right: 50%;
transform: translateX(50%);
top: 20px;
width: 18px;
height: 18px;
background-image: url("@/assets/images/JobStrategyDetailPage/slide_icon.png");
background-size: 100% 100%;
}
.slide-text {
position: absolute;
bottom: 30px;
color: #0275f2;
font-size: 14px;
font-weight: 600;
line-height: 22px;
width: 100%;
text-align: center;
}
}
}
}

View File

@@ -0,0 +1,78 @@
.job-strategy-detail-page {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px;
.job-strategy-detail-body {
width: 100%;
height: 100%;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 20px;
.job-strategy-detail-header {
width: 100%;
height: 32px;
display: flex;
justify-content: flex-start;
align-items: center;
> div {
color: #4e5969;
font-size: 14px;
font-weight: 700;
line-height: 32px;
box-sizing: border-box;
padding: 0 12px;
text-align: center;
cursor: pointer;
}
.item-active {
background-color: #f2f3f5;
border-radius: 100px;
color: #2c7aff;
}
}
}
.job-strategy-detail-content {
width: 100%;
height: 700px;
margin-top: 50px;
position: relative;
.slide-wrapper {
width: 123px;
height: 125px;
position: absolute;
top: 20%;
right: 0;
cursor: pointer;
&::before {
content: "";
position: absolute;
right: 50%;
transform: translateX(50%);
top: 20px;
width: 18px;
height: 18px;
background-image: url("@/assets/images/JobStrategyDetailPage/slide_icon.png");
background-size: 100% 100%;
}
.slide-text {
position: absolute;
bottom: 30px;
color: #0275f2;
font-size: 14px;
font-weight: 600;
line-height: 22px;
width: 100%;
text-align: center;
}
}
}
}

View File

@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import TargetPosition from "./components/TargetPosition";
import CurvedEmployment from "./components/CurvedEmployment";
import "./index.css";
@@ -7,165 +8,50 @@ import "./index.css";
const SWIPE_THRESHOLD = 120;
const JobStrategyDetailPage = () => {
const [activeItem, setActiveItem] = useState("1");
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [currentX, setCurrentX] = useState(0);
const slideWrapperRef = useRef(null);
const [hasSwiped, setHasSwiped] = useState(false);
const navigate = useNavigate();
// 处理鼠标/触摸开始
const handleStart = (clientX) => {
setIsDragging(true);
setStartX(clientX);
setCurrentX(clientX);
setHasSwiped(false);
// 返回上一页
const handleBack = () => {
navigate(-1);
};
// 处理鼠标/触摸移动
const handleMove = (clientX) => {
if (!isDragging) return;
setCurrentX(clientX);
const diffX = clientX - startX;
// 添加视觉反馈
if (slideWrapperRef.current) {
slideWrapperRef.current.style.transform = `translateX(${diffX}px)`;
slideWrapperRef.current.style.transition = "none";
}
};
// 处理鼠标/触摸结束
const handleEnd = () => {
if (!isDragging) return;
setIsDragging(false);
const diffX = currentX - startX;
// 恢复原始位置
if (slideWrapperRef.current) {
slideWrapperRef.current.style.transform = "";
slideWrapperRef.current.style.transition = "transform 0.3s ease";
}
// 检查是否达到滑动阈值
if (Math.abs(diffX) > SWIPE_THRESHOLD) {
if (diffX < 0) {
// 左滑
handleSwipeLeft();
} else {
// 右滑
handleSwipeRight();
}
setHasSwiped(true);
}
};
// 左滑事件处理
const handleSwipeLeft = () => {
console.log("触发左滑事件");
setActiveItem("2");
};
// 鼠标事件
const handleMouseDown = (e) => {
handleStart(e.clientX);
};
const handleMouseMove = (e) => {
if (isDragging) {
e.preventDefault();
handleMove(e.clientX);
}
};
const handleMouseUp = () => {
handleEnd();
};
const handleMouseLeave = () => {
handleEnd();
};
// 触摸事件
const handleTouchStart = (e) => {
handleStart(e.touches[0].clientX);
};
const handleTouchMove = (e) => {
if (isDragging) {
e.preventDefault();
handleMove(e.touches[0].clientX);
}
};
const handleTouchEnd = () => {
handleEnd();
};
// 添加全局事件监听
useEffect(() => {
const handleGlobalMouseMove = (e) => handleMouseMove(e);
const handleGlobalMouseUp = () => handleMouseUp();
if (isDragging) {
document.addEventListener("mousemove", handleGlobalMouseMove);
document.addEventListener("mouseup", handleGlobalMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleGlobalMouseMove);
document.removeEventListener("mouseup", handleGlobalMouseUp);
};
}, [isDragging]);
return (
<div className="job-strategy-detail-page">
<div className="job-strategy-detail-body">
<div className="job-strategy-detail-wrapper">
{/* 返回按钮 */}
<div className="back-button-wrapper">
<button className="back-button" onClick={handleBack}>
<span className="back-icon"></span>
<span className="back-text">返回</span>
</button>
</div>
{/* 顶部导航栏 */}
<div className="job-strategy-detail-header">
<div
className={` ${activeItem === "1" ? "item-active" : ""}`}
className={`nav-item ${activeItem === "1" ? "item-active" : ""}`}
onClick={() => setActiveItem("1")}
>
优先目标岗位
<span className="nav-icon target-icon"></span>
<span className="nav-text">优先目标岗位</span>
</div>
<div
className={` ${activeItem === "2" ? "item-active" : ""}`}
className={`nav-item ${activeItem === "2" ? "item-active" : ""}`}
onClick={() => setActiveItem("2")}
>
"曲线就业"方案
<span className="nav-icon curved-icon"></span>
<span className="nav-text">曲线就业方案</span>
</div>
</div>
<div
className="job-strategy-detail-content"
ref={slideWrapperRef}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
cursor: isDragging ? "grabbing" : "grab",
userSelect: "none",
WebkitUserSelect: "none",
touchAction: "pan-y",
}}
>
{activeItem === "1" && (
<>
<TargetPosition locked={true} />
<div className="slide-wrapper">
<div className="slide-text">左滑查看曲线就业方案</div>
</div>
</>
)}
{activeItem === "2" && <CurvedEmployment locked={true} />}
{/* 内容区域 */}
<div className="job-strategy-detail-content">
{activeItem === "1" && <TargetPosition locked={false} />}
{activeItem === "2" && <CurvedEmployment locked={false} />}
</div>
</div>
</div>
);
};
export default JobStrategyDetailPage;
export default JobStrategyDetailPage;

View File

@@ -11,7 +11,7 @@ const ProfileCard = () => {
<Avatar className="profile-card-user-avatar">
<img
alt="avatar"
src="//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
src={studentInfo?.avatar || "//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"}
/>
</Avatar>
<div className="profile-card-user-name">

View File

@@ -1,18 +1,54 @@
import { useState, useEffect } from "react";
import "./index.css";
const Portfolio = () => {
const [iframeSrc, setIframeSrc] = useState("");
useEffect(() => {
// 添加时间戳参数来破坏缓存
const timestamp = new Date().getTime();
setIframeSrc(`https://du9uay.github.io/personal-resume-wenlv/?t=${timestamp}`);
}, []);
const handleRefresh = () => {
// 手动刷新iframe内容
const timestamp = new Date().getTime();
setIframeSrc(`https://du9uay.github.io/personal-resume-wenlv/?t=${timestamp}`);
};
return (
<div className="user-portfolio-page">
<iframe
src="https://du9uay.github.io/personal-resume-wenlv/"
<button
onClick={handleRefresh}
style={{
width: "100%",
height: "100vh",
position: "absolute",
top: "10px",
right: "10px",
zIndex: 1000,
padding: "8px 16px",
backgroundColor: "#3491fa",
color: "white",
border: "none",
display: "block"
borderRadius: "4px",
cursor: "pointer",
fontSize: "14px"
}}
title="个人简历"
/>
>
刷新页面
</button>
{iframeSrc && (
<iframe
key={iframeSrc} // 使用key强制重新渲染iframe
src={iframeSrc}
style={{
width: "100%",
height: "100vh",
border: "none",
display: "block"
}}
title="个人简历"
/>
)}
</div>
);
};

View File

@@ -22,6 +22,21 @@
cursor: pointer;
}
.project-cases-modal-category-tag {
display: inline-block;
border: 1px solid #4080ff;
background-color: #ffffff;
height: 22px;
border-radius: 3px;
padding: 0 10px;
box-sizing: border-box;
color: #4080ff;
font-size: 12px;
font-weight: 500;
line-height: 20px;
margin-bottom: 10px;
}
.project-cases-modal-title {
width: 100%;
height: 30px;

View File

@@ -20,6 +20,9 @@ export default ({ visible, onClose, data }) => {
<Modal visible={visible} onClose={handleCloseModal}>
<div className="project-cases-modal">
<i className="close-icon" onClick={handleCloseModal} />
{data?.category && (
<span className="project-cases-modal-category-tag">{data.category}</span>
)}
<p className="project-cases-modal-title">{data?.title}</p>
<ul className="project-cases-modal-list">
{/* 项目概述 */}
@@ -35,7 +38,7 @@ export default ({ visible, onClose, data }) => {
<ul className="project-cases-modal-horizontal-list">
{data?.applicablePositions?.map((pos, index) => (
<li key={index} className="high-count-list-item">
<span className={pos.level === '初级' ? 'low' : pos.level === '中级' ? 'medium' : 'high'}>
<span className={pos.level === '普通岗' ? 'low' : pos.level === '技术骨干岗' ? 'medium' : 'high'}>
{pos.level}
</span>
<p>{pos.position}</p>

View File

@@ -1,111 +1,177 @@
.project-library-page {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px;
position: relative;
.project-library-wrapper {
width: 100%;
height: 790px;
background-color: #fff;
border-radius: 8px;
box-sizing: border-box;
padding: 20px;
overflow: hidden;
display: flex;
flex-direction: column;
.project-library-search-area {
width: 100%;
height: 36px;
.ser-portfolio-searc {
width: 100%;
height: 36px;
border: 1px solid #2c7aff;
span {
background-color: #fff;
}
input {
background-color: #fff;
}
}
}
.project-library-list {
overflow-y: auto;
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
padding: 20px 0;
.project-library-item:nth-child(3n) {
margin-right: 0;
}
.project-library-item {
flex-shrink: 0;
width: 340px;
height: 82px;
box-sizing: border-box;
padding: 15px 20px;
border-radius: 8px;
background-color: #f7f8fa;
border: 1px solid #e5e6eb;
margin-right: 20px;
margin-bottom: 20px;
overflow: hidden;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.project-library-item-title {
width: fit-content;
max-width: 100%;
border: 1px solid #2c7aff;
background-color: #e8f3ff;
height: 20px;
border-radius: 2px;
padding: 0 8px;
box-sizing: border-box;
color: #2c7aff;
font-size: 12px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> div {
width: 100%;
height: 24px;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
> p {
width: 80%;
height: 100%;
font-size: 16px;
font-weight: 600;
color: #1d2129;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> span {
cursor: pointer;
font-size: 12px;
font-weight: 400;
color: #2c7aff;
}
}
}
}
}
}
.project-library-page {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px;
position: relative;
.project-library-wrapper {
width: 1120px;
/* height: 790px; */
background-color: #fff;
border-radius: 8px;
box-sizing: border-box;
padding: 20px;
overflow: hidden;
.project-library-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;
padding-bottom: 10px;
&::after {
content: "";
position: absolute;
left: 20px;
bottom: 10px;
width: 32px;
height: 6px;
background-image: url("@/assets/images/Common/title_icon.png");
background-size: contain;
background-repeat: no-repeat;
}
}
.project-library-search-area {
width: 100%;
height: 36px;
.ser-portfolio-searc {
width: 100%;
height: 36px;
border: 1px solid #2c7aff;
span {
background-color: #fff;
}
input {
background-color: #fff;
}
}
}
.project-library-list {
width: 100%;
overflow-y: auto;
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
padding: 20px 0;
.project-library-item:nth-child(3n) {
margin-right: 0;
}
.project-library-item {
flex-shrink: 0;
width: 340px;
height: 100px;
box-sizing: border-box;
padding: 15px 20px;
border-radius: 8px;
background-color: #e5f1ff;
background-image: url("@/assets/images/CompanyJobsPage/jobs_page_left_list_item_bg.png");
background-size: 100% 100%;
background-repeat: no-repeat;
border: 1px solid #e5e6eb;
margin-right: 20px;
margin-bottom: 20px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
border-color: #4080ff;
box-shadow: 0 4px 12px rgba(44, 127, 255, 0.2);
transform: translateY(-2px);
background-color: #f0f7ff;
}
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.project-library-item-title {
display: inline-block;
border: 1px solid #4080ff;
background-color: #ffffff;
min-height: 22px;
height: 22px;
border-radius: 3px;
padding: 0 10px;
box-sizing: border-box;
color: #4080ff;
font-size: 12px;
font-weight: 500;
line-height: 22px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0 4px 0;
width: fit-content;
}
> div {
width: 100%;
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
> p {
width: 80%;
font-size: 16px;
font-weight: 600;
color: #1d2129;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
> span {
cursor: pointer;
font-size: 12px;
font-weight: 400;
color: #2c7aff;
flex-shrink: 0;
margin-left: 10px;
align-self: center;
}
}
}
}
}
.my-project-library {
margin-top: 20px;
.project-library-empty {
width: 100%;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
p {
color: #86909c;
font-size: 14px;
}
}
}
}

View File

@@ -0,0 +1,111 @@
.project-library-page {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px;
position: relative;
.project-library-wrapper {
width: 100%;
height: 790px;
background-color: #fff;
border-radius: 8px;
box-sizing: border-box;
padding: 20px;
overflow: hidden;
display: flex;
flex-direction: column;
.project-library-search-area {
width: 100%;
height: 36px;
.ser-portfolio-searc {
width: 100%;
height: 36px;
border: 1px solid #2c7aff;
span {
background-color: #fff;
}
input {
background-color: #fff;
}
}
}
.project-library-list {
overflow-y: auto;
box-sizing: border-box;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
padding: 20px 0;
.project-library-item:nth-child(3n) {
margin-right: 0;
}
.project-library-item {
flex-shrink: 0;
width: 340px;
height: 82px;
box-sizing: border-box;
padding: 15px 20px;
border-radius: 8px;
background-color: #f7f8fa;
border: 1px solid #e5e6eb;
margin-right: 20px;
margin-bottom: 20px;
overflow: hidden;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.project-library-item-title {
width: fit-content;
max-width: 100%;
border: 1px solid #2c7aff;
background-color: #e8f3ff;
height: 20px;
border-radius: 2px;
padding: 0 8px;
box-sizing: border-box;
color: #2c7aff;
font-size: 12px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> div {
width: 100%;
height: 24px;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
> p {
width: 80%;
height: 100%;
font-size: 16px;
font-weight: 600;
color: #1d2129;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> span {
cursor: pointer;
font-size: 12px;
font-weight: 400;
color: #2c7aff;
}
}
}
}
}
}

View File

@@ -1,105 +1,101 @@
import { useState } from "react";
import { Input } from "@arco-design/web-react";
import toast from "@/components/Toast";
import InfiniteScroll from "@/components/InfiniteScroll";
import ProjectCasesModal from "./components/ProjectCasesModal";
import { getProjectsList, getProjectsdetail } from "@/services/projectLibrary";
import "./index.css";
const InputSearch = Input.Search;
const PAGE_SIZE = 10;
const ProjectLibrary = () => {
const [modalData, setModalData] = useState(undefined);
const [projectList, setProjectList] = useState([]);
const [projectCasesModalVisible, setProjectCasesModalVisible] =
useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const onSearch = (value) => {
setProjectList([]);
setHasMore(true);
setPage(1);
fetchProjects(value, 1);
};
const handleProjectClick = async (item) => {
if (item?.id) {
const res = await getProjectsdetail(item.id);
if (res.success) {
setModalData(res.data);
setProjectCasesModalVisible(true);
} else {
toast.error(res.message);
}
} else {
toast.error("加载数据失败,请刷新重试");
}
};
const handleCloseModal = () => {
setProjectCasesModalVisible(false);
setModalData(undefined);
};
const fetchProjects = async (searchValue = "", pageNum) => {
try {
const res = await getProjectsList({
search: searchValue,
page: pageNum ?? page,
pageSize: PAGE_SIZE,
});
if (res.success) {
setProjectList((prevList) => {
const newList = [...prevList, ...res.data];
if (res.total === newList?.length) {
setHasMore(false);
} else {
setPage((prevPage) => prevPage + 1);
}
return newList;
});
}
} catch (error) {
console.error("Failed to fetch projects:", error);
}
};
return (
<div className="project-library-page">
<div className="project-library-wrapper">
<div className="project-library-search-area">
<InputSearch
className="ser-portfolio-search"
onSearch={onSearch}
searchButton="搜索"
/>
</div>
<InfiniteScroll
loadMore={fetchProjects}
hasMore={hasMore}
empty={projectList.length === 0}
className="project-library-list"
>
{projectList.map((item) => (
<li className="project-library-item" key={item.id}>
<p className="project-library-item-title">{item.description}</p>
<div>
<p>{item.name}</p>
<span onClick={() => handleProjectClick(item)}>详情 &gt;</span>
</div>
</li>
))}
</InfiniteScroll>
</div>
<ProjectCasesModal
data={modalData}
visible={projectCasesModalVisible}
onClose={handleCloseModal}
/>
</div>
);
};
export default ProjectLibrary;
import { useState } from "react";
import toast from "@/components/Toast";
import InfiniteScroll from "@/components/InfiniteScroll";
import ProjectCasesModal from "./components/ProjectCasesModal";
import { getProjectsList, getProjectsdetail } from "@/services/projectLibrary";
import "./index.css";
const PAGE_SIZE = 10;
const ProjectLibrary = () => {
const [modalData, setModalData] = useState(undefined);
const [projectList, setProjectList] = useState([]);
const [projectCasesModalVisible, setProjectCasesModalVisible] =
useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const handleProjectClick = async (item) => {
if (item?.id) {
const res = await getProjectsdetail(item.id);
if (res.success) {
setModalData(res.data);
setProjectCasesModalVisible(true);
} else {
toast.error(res.message);
}
} else {
toast.error("加载数据失败,请刷新重试");
}
};
const handleCloseModal = () => {
setProjectCasesModalVisible(false);
setModalData(undefined);
};
const fetchProjects = async (pageNum) => {
try {
const res = await getProjectsList({
page: pageNum ?? page,
pageSize: PAGE_SIZE,
});
if (res.success) {
setProjectList((prevList) => {
const newList = [...prevList, ...res.data];
if (res.total === newList?.length) {
setHasMore(false);
} else {
setPage((prevPage) => prevPage + 1);
}
return newList;
});
}
} catch (error) {
console.error("Failed to fetch projects:", error);
}
};
return (
<div className="project-library-page">
<div className="project-library-wrapper">
<p className="project-library-title">文旅班级项目库</p>
<InfiniteScroll
loadMore={fetchProjects}
hasMore={hasMore}
empty={projectList.length === 0}
className="project-library-list"
>
{projectList.map((item) => (
<li className="project-library-item" key={item.id}>
<p className="project-library-item-title">{item.description}</p>
<div>
<p>{item.name}</p>
<span onClick={() => handleProjectClick(item)}>详情 &gt;</span>
</div>
</li>
))}
</InfiniteScroll>
</div>
{/* 我的项目库板块 */}
<div className="project-library-wrapper my-project-library">
<p className="project-library-title">我完成的项目库</p>
<div className="project-library-empty">
<p>暂无项目</p>
</div>
</div>
<ProjectCasesModal
data={modalData}
visible={projectCasesModalVisible}
onClose={handleCloseModal}
/>
</div>
);
};
export default ProjectLibrary;

View File

@@ -1,289 +1,369 @@
.resume-interview-page {
width: 100%;
height: 100%;
box-sizing: border-box;
position: relative;
display: flex;
.resume-interview-spin,
.empty-data {
margin: auto;
}
.resume-interview-navigation {
position: fixed;
top: 0;
width: 100%;
height: 56px;
background-color: #ffffff;
display: flex;
justify-content: flex-start;
align-items: center;
overflow: hidden;
box-sizing: border-box;
padding: 0 20px;
z-index: 10;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
.active {
color: #2c7aff !important;
background-color: #f2f3f5;
}
.navigation-tabs {
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
width: 100%;
scroll-behavior: smooth;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
}
.resume-interview-navigation-item {
margin-right: 12px;
min-width: fit-content;
white-space: nowrap;
height: 32px;
line-height: 32px;
box-sizing: border-box;
border-radius: 100px;
padding: 0 20px;
font-size: 14px;
color: #4e5969;
font-weight: 600;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
&:hover {
background-color: #f7f8fa;
color: #2c7aff;
}
&:last-child {
margin-right: 20px;
}
}
}
.resume-interview-content-wrapper {
width: 100%;
box-sizing: border-box;
padding: 56px 20px 20px 20px;
overflow-y: auto;
.resume-interview-content-item-wrapper:nth-child(2n) {
background-image: url("@/assets/images/ResumeInterviewPage/bg_2.png") !important;
&::after {
background-image: url("@/assets/images/ResumeInterviewPage/icon_2.png") !important;
}
.job-item {
> span {
border: 1px solid #8d4eda !important;
background-color: #f5e8ff !important;
color: #8d4eda !important;
}
}
.resumes-list {
background-image: linear-gradient(
180deg,
#dcdffb 0%,
#dcdffb4d 30%
) !important;
}
}
.resume-interview-content-item-wrapper {
width: 100%;
height: 326px;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 8px;
border: 2px solid #fff;
padding: 16px;
background-image: url("@/assets/images/ResumeInterviewPage/bg_1.png");
background-size: 100% 100%;
position: relative;
margin-bottom: 20px;
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 132px;
height: 132px;
background-image: url("@/assets/images/ResumeInterviewPage/icon_1.png");
background-size: 100% 100%;
}
.item-title {
font-size: 24px;
color: #000000;
font-weight: 600;
width: 100%;
height: 36px;
line-height: 36px;
}
.item-subtitle {
font-size: 14px;
height: 22px;
line-height: 22px;
font-weight: 400;
color: #86909c;
margin-bottom: 10px;
}
.item-content-wrapper {
width: 100%;
height: 212px;
display: flex;
justify-content: space-between;
align-items: center;
.jobs-list {
width: 664px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 20px 15px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.job-item:nth-child(2n) {
margin-right: 0;
}
.job-item-change {
&::after {
content: "";
position: absolute;
top: -18px;
right: -12px;
width: 62px;
height: 66px;
background-image: url("@/assets/images/ResumeInterviewPage/change_icon.png");
background-size: 100% 100%;
}
}
.job-item {
width: 311px;
height: 80px;
box-sizing: border-box;
padding: 16px;
border-radius: 8px;
background-color: #f7f8fa;
margin-bottom: 5px;
border: 1px solid #e5e8ed;
margin-right: 10px;
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
position: relative;
> span {
box-sizing: border-box;
font-size: 12px;
font-weight: 600;
padding: 0 4px;
margin-bottom: 5px;
color: #3491fa;
background-color: #e8f7ff;
border: 1px solid #3491fa;
}
.job-name {
position: relative;
width: 100%;
> p {
width: 239px;
height: 24px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #1d2129;
font-size: 16px;
font-weight: 600;
}
> span {
position: absolute;
top: 0;
right: 0;
font-size: 12px;
font-weight: 400;
color: #2c7aff;
cursor: pointer;
}
}
}
.no-results {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
color: #86909c;
font-size: 14px;
}
}
.resumes-list {
position: relative;
z-index: 1;
width: 408px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 15px;
border: 1px solid #ffffff;
opacity: 0.91;
background-image: linear-gradient(180deg, #d2e5fc 0%, #d2e5fc80 50%);
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.resume-item {
cursor: pointer;
width: 100%;
height: 48px;
border-radius: 8px;
background-color: #fff;
margin-bottom: 20px;
> p {
text-align: center;
line-height: 48px;
color: #1d2129;
font-size: 16px;
font-weight: 400;
}
}
}
}
}
}
}
.resume-interview-page {
width: 100%;
height: 100%;
box-sizing: border-box;
position: relative;
display: flex;
.resume-interview-spin,
.empty-data {
margin: auto;
}
.resume-interview-navigation-wrapper {
position: fixed;
top: 0;
width: 100%;
height: 56px;
background-color: #ffffff;
z-index: 10;
display: flex;
align-items: center;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
.nav-scroll-btn {
position: absolute;
top: 0;
height: 56px;
width: 40px;
background: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0.8));
border: none;
cursor: pointer;
font-size: 24px;
color: #4e5969;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transition: all 0.2s ease;
&:hover {
background: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0.95));
color: #1d2129;
}
&.nav-scroll-btn-left {
left: 0;
background: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0));
}
&.nav-scroll-btn-right {
right: 0;
background: linear-gradient(to left, rgba(255,255,255,1), rgba(255,255,255,0));
}
}
}
.resume-interview-navigation {
width: 100%;
height: 56px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* Firefox - 隐藏滚动条 */
-ms-overflow-style: none; /* IE and Edge - 隐藏滚动条 */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera - 隐藏滚动条 */
}
.navigation-tabs {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
padding-right: 60px; /* 增加右侧内边距,确保最后一个按钮完全显示 */
min-width: 100%;
width: max-content;
box-sizing: content-box; /* 确保padding不影响width计算 */
}
.resume-interview-navigation-item {
display: inline-block;
flex-shrink: 0;
min-width: max-content;
height: 32px;
line-height: 30px;
box-sizing: border-box;
border-radius: 100px;
padding: 0 20px;
font-size: 14px;
color: #4e5969;
font-weight: 600;
text-align: center;
white-space: nowrap;
cursor: pointer;
transition: all 0.2s ease;
list-style: none;
&:hover {
background-color: #f7f8fa;
color: #1d2129;
}
&.active {
color: #2c7aff !important;
background-color: #f2f3f5;
}
}
}
.resume-interview-content-wrapper {
width: 100%;
box-sizing: border-box;
padding: 80px 20px 20px 20px; /* 增加顶部内边距,在导航栏和内容之间添加间隔 */
overflow-y: auto;
.resume-interview-content-item-wrapper {
width: 100%;
height: 326px;
box-sizing: border-box;
background-color: #E8F0FF;
border-radius: 8px;
border: 2px solid #fff;
padding: 16px;
position: relative;
margin-bottom: 20px;
overflow: hidden;
&::after {
content: "";
position: absolute;
top: -20px;
right: -20px;
width: 180px;
height: 180px;
background-image: url("@/assets/images/ResumeInterviewPage/icon_1.png");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.item-title {
font-size: 24px;
color: #000000;
font-weight: 600;
width: 100%;
height: 36px;
line-height: 36px;
}
.item-subtitle {
font-size: 14px;
height: 22px;
line-height: 22px;
font-weight: 400;
color: #86909c;
margin-bottom: 10px;
}
.item-content-wrapper {
width: 100%;
height: 212px;
display: flex;
justify-content: space-between;
align-items: center;
.jobs-list {
width: 664px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 255, 255, 0.6) 100%
);
opacity: 0.9;
border: 1px solid #ffffff;
box-sizing: border-box;
padding: 20px 15px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.job-item:nth-child(2n) {
margin-right: 0;
}
.job-item-change {
&::after {
content: "";
position: absolute;
top: -18px;
right: -12px;
width: 62px;
height: 66px;
background-image: url("@/assets/images/ResumeInterviewPage/change_icon.png");
background-size: 100% 100%;
}
}
.job-item {
width: 311px;
height: 80px;
box-sizing: border-box;
padding: 10px 12px;
border-radius: 8px;
background-color: #fff;
background-image: url("@/assets/images/ResumeInterviewPage/bg_1.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
margin-bottom: 5px;
border: 2px solid #ffffff;
margin-right: 10px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
position: relative;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
.job-avatar-wrapper {
width: 48px;
height: 48px;
margin-right: 10px;
flex-shrink: 0;
.job-avatar {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
border: 1px solid #f0f0f0;
}
.job-avatar-placeholder {
width: 100%;
height: 100%;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
}
.job-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
.job-level-tag {
width: auto;
height: 16px;
object-fit: contain;
align-self: flex-start;
}
.job-name {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
> p {
flex: 1;
color: #1d2129;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 8px;
}
.job-arrow {
font-size: 16px;
font-weight: 400;
color: #3491fa;
cursor: pointer;
flex-shrink: 0;
}
}
}
}
.no-results {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
color: #86909c;
font-size: 14px;
}
}
.resumes-list {
position: relative;
z-index: 1;
width: 408px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background-color: #ffffff;
box-sizing: border-box;
padding: 15px;
border: 1px solid #ffffff;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.resume-item {
cursor: pointer;
width: 100%;
height: 48px;
border-radius: 8px;
background-color: #EFF6FF;
background-image: url("@/assets/images/CompanyJobsPage/jobs_page_left_list_item_bg.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 12px;
gap: 8px;
transition: all 0.3s ease;
&:hover {
background-color: #E1EFFF;
transform: translateX(5px);
}
.question-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
object-fit: contain;
}
> p {
flex: 1;
line-height: 1.5;
color: #1d2129;
font-size: 14px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,289 @@
.resume-interview-page {
width: 100%;
height: 100%;
box-sizing: border-box;
position: relative;
display: flex;
.resume-interview-spin,
.empty-data {
margin: auto;
}
.resume-interview-navigation {
position: fixed;
top: 0;
width: 100%;
height: 56px;
background-color: #ffffff;
display: flex;
justify-content: flex-start;
align-items: center;
overflow: hidden;
box-sizing: border-box;
padding: 0 20px;
z-index: 10;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
.active {
color: #2c7aff !important;
background-color: #f2f3f5;
}
.navigation-tabs {
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
width: 100%;
scroll-behavior: smooth;
/* 隐藏滚动条但保留滚动功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
}
.resume-interview-navigation-item {
margin-right: 12px;
min-width: fit-content;
white-space: nowrap;
height: 32px;
line-height: 32px;
box-sizing: border-box;
border-radius: 100px;
padding: 0 20px;
font-size: 14px;
color: #4e5969;
font-weight: 600;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
&:hover {
background-color: #f7f8fa;
color: #2c7aff;
}
&:last-child {
margin-right: 20px;
}
}
}
.resume-interview-content-wrapper {
width: 100%;
box-sizing: border-box;
padding: 56px 20px 20px 20px;
overflow-y: auto;
.resume-interview-content-item-wrapper:nth-child(2n) {
background-image: url("@/assets/images/ResumeInterviewPage/bg_2.png") !important;
&::after {
background-image: url("@/assets/images/ResumeInterviewPage/icon_2.png") !important;
}
.job-item {
> span {
border: 1px solid #8d4eda !important;
background-color: #f5e8ff !important;
color: #8d4eda !important;
}
}
.resumes-list {
background-image: linear-gradient(
180deg,
#dcdffb 0%,
#dcdffb4d 30%
) !important;
}
}
.resume-interview-content-item-wrapper {
width: 100%;
height: 326px;
box-sizing: border-box;
background-color: #ffffff;
border-radius: 8px;
border: 2px solid #fff;
padding: 16px;
background-image: url("@/assets/images/ResumeInterviewPage/bg_1.png");
background-size: 100% 100%;
position: relative;
margin-bottom: 20px;
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 132px;
height: 132px;
background-image: url("@/assets/images/ResumeInterviewPage/icon_1.png");
background-size: 100% 100%;
}
.item-title {
font-size: 24px;
color: #000000;
font-weight: 600;
width: 100%;
height: 36px;
line-height: 36px;
}
.item-subtitle {
font-size: 14px;
height: 22px;
line-height: 22px;
font-weight: 400;
color: #86909c;
margin-bottom: 10px;
}
.item-content-wrapper {
width: 100%;
height: 212px;
display: flex;
justify-content: space-between;
align-items: center;
.jobs-list {
width: 664px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 20px 15px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
.job-item:nth-child(2n) {
margin-right: 0;
}
.job-item-change {
&::after {
content: "";
position: absolute;
top: -18px;
right: -12px;
width: 62px;
height: 66px;
background-image: url("@/assets/images/ResumeInterviewPage/change_icon.png");
background-size: 100% 100%;
}
}
.job-item {
width: 311px;
height: 80px;
box-sizing: border-box;
padding: 16px;
border-radius: 8px;
background-color: #f7f8fa;
margin-bottom: 5px;
border: 1px solid #e5e8ed;
margin-right: 10px;
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
position: relative;
> span {
box-sizing: border-box;
font-size: 12px;
font-weight: 600;
padding: 0 4px;
margin-bottom: 5px;
color: #3491fa;
background-color: #e8f7ff;
border: 1px solid #3491fa;
}
.job-name {
position: relative;
width: 100%;
> p {
width: 239px;
height: 24px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #1d2129;
font-size: 16px;
font-weight: 600;
}
> span {
position: absolute;
top: 0;
right: 0;
font-size: 12px;
font-weight: 400;
color: #2c7aff;
cursor: pointer;
}
}
}
.no-results {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
color: #86909c;
font-size: 14px;
}
}
.resumes-list {
position: relative;
z-index: 1;
width: 408px;
height: 100%;
overflow-y: auto;
border-radius: 8px;
background-color: #fff;
box-sizing: border-box;
padding: 15px;
border: 1px solid #ffffff;
opacity: 0.91;
background-image: linear-gradient(180deg, #d2e5fc 0%, #d2e5fc80 50%);
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
.resume-item {
cursor: pointer;
width: 100%;
height: 48px;
border-radius: 8px;
background-color: #fff;
margin-bottom: 20px;
> p {
text-align: center;
line-height: 48px;
color: #1d2129;
font-size: 16px;
font-weight: 400;
}
}
}
}
}
}
}

View File

@@ -4,6 +4,11 @@ import toast from "@/components/Toast";
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
import ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
import { getPageData } from "@/services/resumeInterview";
import jobLevelData from "@/data/joblevel.json";
import TagHigh from "@/assets/images/ResumeInterviewPage/Tag.png";
import TagMiddle from "@/assets/images/ResumeInterviewPage/Tag2.png";
import TagOrdinary from "@/assets/images/ResumeInterviewPage/Tag3.png";
import QuestionIcon from "@/assets/images/ResumeInterviewPage/question_icon2.png";
import "./index.css";
@@ -15,7 +20,50 @@ const ResumeInterviewPage = () => {
const [resumeModalData, setResumeModalData] = useState(undefined);
const [pageData, setPageData] = useState(null);
const [loading, setLoading] = useState(true);
const [showLeftBtn, setShowLeftBtn] = useState(false);
const [showRightBtn, setShowRightBtn] = useState(true);
const sectionsRef = useRef({});
const navRef = useRef(null);
// 获取岗位头像和级别信息
const getPositionInfo = (positionTitle) => {
const jobData = jobLevelData.data;
let positionInfo = null;
let levelName = "";
let levelKey = "";
// 遍历所有级别查找匹配的岗位
for (const [key, levelData] of Object.entries(jobData)) {
const found = levelData.list.find(item => item.position_name === positionTitle);
if (found) {
positionInfo = found;
levelName = levelData.name;
levelKey = key;
break;
}
}
// 根据级别返回对应的标签图片
let tagImage = "";
switch(levelKey) {
case "high":
tagImage = TagHigh;
break;
case "middle":
tagImage = TagMiddle;
break;
case "ordinary":
default:
tagImage = TagOrdinary;
break;
}
return {
avatar: positionInfo?.img || null,
levelName: levelName || "普通岗",
tagImage: tagImage
};
};
// 导航到指定行业段落
const handleNavClick = (industryId) => {
@@ -114,28 +162,76 @@ const ResumeInterviewPage = () => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [pageData?.industries]);
// 监听数据变化,更新滚动按钮状态
useEffect(() => {
if (pageData?.industries && navRef.current) {
setTimeout(() => {
checkScrollButtons();
}, 100);
}
}, [pageData?.industries]);
// 检查滚动按钮的显示状态
const checkScrollButtons = () => {
if (!navRef.current) return;
const { scrollLeft, scrollWidth, clientWidth } = navRef.current;
setShowLeftBtn(scrollLeft > 5); // 添加5px的容差
setShowRightBtn(scrollLeft < scrollWidth - clientWidth - 5); // 添加5px的容差确保能滚动到最后
};
// 左右滚动函数
const scrollNav = (direction) => {
if (!navRef.current) return;
const scrollAmount = 200; // 每次滚动的距离
navRef.current.scrollTo({
left: navRef.current.scrollLeft + (direction === 'left' ? -scrollAmount : scrollAmount),
behavior: 'smooth'
});
};
// 添加鼠标滚轮横向滚动功能
useEffect(() => {
const navigation = document.querySelector('.resume-interview-navigation');
if (!navigation) return;
// 延迟执行以确保DOM已经渲染
const timer = setTimeout(() => {
const navigation = navRef.current;
if (!navigation) return;
const handleWheel = (e) => {
const tabs = navigation.querySelector('.navigation-tabs');
if (!tabs) return;
// 检查是否在导航栏区域
if (e.currentTarget === navigation || navigation.contains(e.target)) {
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
const handleWheel = (e) => {
// 检查是否鼠标在导航栏上
if (navigation.contains(e.target)) {
// 阻止默认垂直滚动
e.preventDefault();
tabs.scrollLeft += e.deltaY * 0.5; // 减慢滚动速度使其更平滑
e.stopPropagation();
// 转换为横向滚动
const delta = e.deltaY || e.deltaX;
navigation.scrollLeft += delta;
// 更新按钮状态
checkScrollButtons();
}
}
};
};
navigation.addEventListener('wheel', handleWheel, { passive: false });
return () => navigation.removeEventListener('wheel', handleWheel);
}, []);
const handleScroll = () => {
checkScrollButtons();
};
// 使用捕获阶段以确保事件被正确处理
window.addEventListener('wheel', handleWheel, { passive: false, capture: true });
navigation.addEventListener('scroll', handleScroll);
// 初始检查
checkScrollButtons();
return () => {
window.removeEventListener('wheel', handleWheel, { capture: true });
navigation.removeEventListener('scroll', handleScroll);
};
}, 100);
return () => clearTimeout(timer);
}, [pageData]);
return (
<div className="resume-interview-page">
@@ -143,21 +239,39 @@ const ResumeInterviewPage = () => {
<Spin size={80} className="resume-interview-spin" />
) : pageData ? (
<>
<ul className="resume-interview-navigation">
<div className="navigation-tabs">
{pageData.industries.map((industry) => (
<li
key={industry.id}
className={`resume-interview-navigation-item ${
activeIndustry === industry.id ? "active" : ""
}`}
onClick={() => handleNavClick(industry.id)}
>
{industry.name}
</li>
))}
</div>
</ul>
<div className="resume-interview-navigation-wrapper">
{showLeftBtn && (
<button
className="nav-scroll-btn nav-scroll-btn-left"
onClick={() => scrollNav('left')}
>
</button>
)}
<ul className="resume-interview-navigation" ref={navRef}>
<div className="navigation-tabs">
{pageData.industries.map((industry) => (
<li
key={industry.id}
className={`resume-interview-navigation-item ${
activeIndustry === industry.id ? "active" : ""
}`}
onClick={() => handleNavClick(industry.id)}
>
{industry.name}
</li>
))}
</div>
</ul>
{showRightBtn && (
<button
className="nav-scroll-btn nav-scroll-btn-right"
onClick={() => scrollNav('right')}
>
</button>
)}
</div>
<ul className="resume-interview-content-wrapper">
{pageData.industries.map((item) => (
<li
@@ -169,19 +283,39 @@ const ResumeInterviewPage = () => {
<p className="item-subtitle">简历与面试题</p>
<div className="item-content-wrapper">
<ul className="jobs-list">
{filterPositions(item.positions).map((position) => (
<li
className="job-item job-item-change"
key={position.id}
onClick={() => handlePositionClick(position, item)}
>
<span>{position.level}</span>
<div className="job-name">
<p>{position.title}</p>
<span>详情 &gt;</span>
</div>
</li>
))}
{filterPositions(item.positions).map((position) => {
const positionInfo = getPositionInfo(position.title);
return (
<li
className="job-item job-item-change"
key={position.id}
onClick={() => handlePositionClick(position, item)}
>
<div className="job-avatar-wrapper">
{positionInfo.avatar ? (
<img
src={positionInfo.avatar}
alt={position.title}
className="job-avatar"
/>
) : (
<div className="job-avatar-placeholder" />
)}
</div>
<div className="job-info">
<img
src={positionInfo.tagImage}
alt={positionInfo.levelName}
className="job-level-tag"
/>
<div className="job-name">
<p>岗位名称{position.title}</p>
<span className="job-arrow"></span>
</div>
</div>
</li>
);
})}
</ul>
<ul className="resumes-list">
{item.questions.map((question) => (
@@ -192,6 +326,11 @@ const ResumeInterviewPage = () => {
handleQuestionClick({ ...item, questions: question.subQuestions || [question] })
}
>
<img
src={QuestionIcon}
alt="question"
className="question-icon"
/>
<p>{question.question}</p>
</li>
))}