chore: 迁移项目到新仓库并整理代码
- 更新多个组件的功能优化 - 整理简历映射数据 - 优化视频播放和面试模拟相关组件 - 更新就业策略和公司职位页面 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ const AgentPage = () => {
|
||||
return (
|
||||
<div className="agent-page-wrapper">
|
||||
<iframe
|
||||
src="http://192.168.2.33:4173"
|
||||
src="http://127.0.0.1:4173"
|
||||
className="agent-page-iframe"
|
||||
title="Agent"
|
||||
frameBorder="0"
|
||||
|
||||
@@ -1,44 +1,15 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { Empty } from "@arco-design/web-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import JobList from "@/pages/CompanyJobsPage/components/JobList";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import { getJobsList } from "@/services";
|
||||
|
||||
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,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,不需要映射
|
||||
setJobs((prevList) => {
|
||||
const newList = [...prevList, ...res.data];
|
||||
if (res.total === newList?.length) {
|
||||
setListHasMore(false);
|
||||
} else {
|
||||
setListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
}, [listPage]);
|
||||
|
||||
return (
|
||||
<div className="company-jobs-list-page">
|
||||
{/* 返回按钮 */}
|
||||
@@ -49,14 +20,16 @@ const CompanyJobsListPage = () => {
|
||||
</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 className="company-jobs-list-page-wrapper">
|
||||
<Empty
|
||||
description="暂无岗位数据"
|
||||
style={{
|
||||
paddingTop: '200px',
|
||||
fontSize: '16px',
|
||||
color: '#86909c'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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";
|
||||
|
||||
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,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,不需要映射
|
||||
setJobs((prevList) => {
|
||||
const newList = [...prevList, ...res.data];
|
||||
if (res.total === newList?.length) {
|
||||
setListHasMore(false);
|
||||
} else {
|
||||
setListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
}, [listPage]);
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyJobsListPage;
|
||||
@@ -448,4 +448,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.company-jobs-page-empty-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,375 +1,79 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Empty } from "@arco-design/web-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
// import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; // 不再需要映射器,mock数据已经是前端格式
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
import InterviewStatusAnimation from "./components/InterviewStatusAnimation";
|
||||
import JobInfoModal from "./components/JobInfoModal";
|
||||
import {
|
||||
getCompanyJobsPageData,
|
||||
getJobsList,
|
||||
getInterviewsList,
|
||||
getJobsDetail,
|
||||
} from "@/services";
|
||||
import { getJobByPosition } from "@/services/companyJobsNew";
|
||||
|
||||
import "./index.css";
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const CompanyJobsPage = () => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
const [jobs, setJobs] = useState([]);
|
||||
const [jobsListPage, setJobsListPage] = useState(1);
|
||||
const [jobsListHasMore, setJobsListHasMore] = useState(true);
|
||||
const [interviews, setInterviews] = useState([]);
|
||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedItemId, setExpandedItemId] = useState(null);
|
||||
const [jobDetailVisible, setJobDetailVisible] = useState(false);
|
||||
const [selectedJob, setSelectedJob] = useState(null);
|
||||
const [isFromInterview, setIsFromInterview] = useState(false); // 标识是否从面试状态卡片点击
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 初始化页面数据 - 使用聚合接口
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await getCompanyJobsPageData({
|
||||
studentId: studentInfo?.id,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// 设置面试数据
|
||||
let interviewsData = [];
|
||||
if (res.data?.interviews) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
interviewsData = res.data.interviews.list || [];
|
||||
setInterviews(interviewsData);
|
||||
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||
if (interviewsData.length > 0) {
|
||||
setInterviewsPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
// 设置岗位数据 - 包含已投递的岗位
|
||||
if (res.data?.jobs) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const jobsList = res.data.jobs.list || [];
|
||||
|
||||
// 从面试数据中提取已投递的岗位信息
|
||||
const deliveredJobs = interviewsData.map(interview => {
|
||||
// 确保有完整的岗位数据
|
||||
const jobData = interview.job || {};
|
||||
return {
|
||||
id: `delivered-${interview.id}`, // 使用特殊的ID标识已投递岗位
|
||||
position: interview.position,
|
||||
isDelivered: true, // 标记为已投递
|
||||
interviewTime: interview.interviewTime,
|
||||
interviewStatus: interview.statusText,
|
||||
originalInterviewId: interview.id,
|
||||
// 从job对象中提取所有必要字段
|
||||
salary: jobData.salary || "面议",
|
||||
tags: jobData.tags || [],
|
||||
location: jobData.location || "待定",
|
||||
education: jobData.education || "待定",
|
||||
jobCategory: jobData.jobCategory || "专业相关岗位",
|
||||
remainingPositions: jobData.remainingPositions || 5,
|
||||
deadline: jobData.deadline || "2025-12-31",
|
||||
jobType: jobData.jobType || "job",
|
||||
requirements: jobData.requirements || "",
|
||||
description: jobData.description || "",
|
||||
welfare: jobData.welfare || [],
|
||||
companyInfo: jobData.companyInfo || "",
|
||||
companyImages: jobData.companyImages || []
|
||||
};
|
||||
}).filter(job => job.position); // 过滤掉没有岗位信息的项
|
||||
|
||||
// 分离未投递和已过期的岗位
|
||||
const activeJobs = jobsList.filter(job => !job.isExpired && job.status !== 'expired');
|
||||
const expiredJobs = jobsList.filter(job => job.isExpired || job.status === 'expired');
|
||||
|
||||
// 按照顺序合并:未投递 -> 已投递 -> 已过期
|
||||
const allJobs = [...activeJobs, ...deliveredJobs, ...expiredJobs];
|
||||
setJobs(allJobs);
|
||||
setJobsListHasMore(res.data.jobs.hasMore);
|
||||
if (allJobs.length > 0) {
|
||||
setJobsListPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
setInitialDataLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch initial page data:", error);
|
||||
// 如果聚合接口失败,回退到原来的方式
|
||||
setInitialDataLoaded(true);
|
||||
// 显示错误信息给用户
|
||||
if (toast && toast.error) {
|
||||
toast.error("加载数据失败,请刷新重试");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, [studentInfo?.id]);
|
||||
|
||||
// 获取面试信息 - 用于分页加载更多
|
||||
const fetchInterviewsData = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (studentInfo?.id) {
|
||||
const res = await getInterviewsList({
|
||||
page: interviewsPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id,
|
||||
status: "SCHEDULED",
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const interviews = res.data || [];
|
||||
setInterviews((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(
|
||||
prevList.map((interview) => interview.id)
|
||||
);
|
||||
const newInterviews = interviews.filter(
|
||||
(interview) => !existingIds.has(interview.id)
|
||||
);
|
||||
|
||||
const newList = [...prevList, ...newInterviews];
|
||||
if (res.total <= newList?.length) {
|
||||
setInterviewsHasMore(false);
|
||||
} else {
|
||||
setInterviewsPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
} else {
|
||||
if (interviewsPage === 1) {
|
||||
setInterviews([]);
|
||||
}
|
||||
toast.error(res.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理面试状态点击
|
||||
const handleStatusClick = (e, item) => {
|
||||
e.stopPropagation();
|
||||
// 如果点击的是已展开的项,则收起;否则展开新项
|
||||
if (expandedItemId === item.id) {
|
||||
setExpandedItemId(null);
|
||||
} else {
|
||||
setExpandedItemId(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业内推岗位 - 用于分页加载更多
|
||||
const fetchJobsList = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复请求
|
||||
if (jobsListPage === 1 && jobs.length === 0) {
|
||||
return; // 初始数据应该通过聚合接口加载
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getJobsList({
|
||||
page: jobsListPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const jobs = res.data;
|
||||
setJobs((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(prevList.map((job) => job.id));
|
||||
const newJobs = jobs.filter((job) => !existingIds.has(job.id));
|
||||
|
||||
const newList = [...prevList, ...newJobs];
|
||||
if (res.total <= newList?.length) {
|
||||
setJobsListHasMore(false);
|
||||
} else {
|
||||
setJobsListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
if (jobsListPage === 1) {
|
||||
setJobs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleJobWrapperClick = () => {
|
||||
navigate("/company-jobs-list");
|
||||
};
|
||||
|
||||
// 处理岗位卡片点击,显示岗位详情
|
||||
const handleJobCardClick = async (item) => {
|
||||
// 直接从企业内推岗位库中查找对应的岗位
|
||||
if (item.position) {
|
||||
const jobData = getJobByPosition(item.position);
|
||||
|
||||
if (jobData) {
|
||||
setSelectedJob(jobData);
|
||||
setIsFromInterview(true); // 标记是从面试状态卡片点击的
|
||||
setJobDetailVisible(true);
|
||||
} else {
|
||||
toast.error("未找到对应的岗位详情");
|
||||
}
|
||||
} else {
|
||||
toast.error("无法获取岗位详情");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="company-jobs-page-wrapper">
|
||||
<div className="company-jobs-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="company-jobs-page-spin" />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="company-jobs-page-left"
|
||||
<div className="company-jobs-page-left">
|
||||
<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}
|
||||
>
|
||||
<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}
|
||||
className="company-jobs-page-left-list-wrapper"
|
||||
>
|
||||
<JobList data={jobs} />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-wrapper">
|
||||
<div
|
||||
className="company-jobs-page-interview"
|
||||
>
|
||||
<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}
|
||||
empty={interviews.length === 0}
|
||||
className="company-jobs-page-interview-list"
|
||||
>
|
||||
{interviews.map((item) => (
|
||||
<div key={item.id} className="interview-item-wrapper">
|
||||
<li
|
||||
className="company-jobs-page-interview-item"
|
||||
onClick={() => handleJobCardClick(item)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<div className="company-jobs-page-interview-item-info">
|
||||
<p className="company-jobs-page-interview-item-info-position">
|
||||
{item.position}
|
||||
</p>
|
||||
{item.job?.tags?.length > 0 ? (
|
||||
<ul className="company-jobs-page-interview-item-info-tags">
|
||||
{item.job.tags.map((tag) => (
|
||||
<li
|
||||
className="company-jobs-page-interview-item-info-tag"
|
||||
key={tag}
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<span className="company-jobs-page-interview-item-info-salary">
|
||||
{item.job?.salary || "面议"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-item-btn-wrapper">
|
||||
<span>{item.interviewTime}</span>
|
||||
<div
|
||||
className={`company-jobs-page-interview-item-btn ${
|
||||
item.status !== "COMPLETED" &&
|
||||
"company-jobs-page-interview-item-btn-active"
|
||||
}`}
|
||||
onClick={(e) => handleStatusClick(e, item)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{item.statusText}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<InterviewStatusAnimation
|
||||
statusText={item.statusText}
|
||||
isOpen={expandedItemId === item.id}
|
||||
stageDate={item.stageDate}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
查看全部岗位
|
||||
</button>
|
||||
</div>
|
||||
<div className="company-jobs-page-left-list-wrapper">
|
||||
<Empty
|
||||
description="暂无岗位数据"
|
||||
style={{
|
||||
paddingTop: '100px',
|
||||
fontSize: '14px',
|
||||
color: '#86909c'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 岗位详情弹窗 */}
|
||||
<JobInfoModal
|
||||
visible={jobDetailVisible}
|
||||
onClose={() => {
|
||||
setJobDetailVisible(false);
|
||||
setSelectedJob(null);
|
||||
setIsFromInterview(false); // 重置标志
|
||||
}}
|
||||
data={selectedJob}
|
||||
directToResume={false}
|
||||
hideDeliverButton={isFromInterview} // 传递是否隐藏投递按钮的标志
|
||||
/>
|
||||
<div className="company-jobs-page-interview-wrapper">
|
||||
<div className="company-jobs-page-interview">
|
||||
<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>
|
||||
<div className="company-jobs-page-interview-list">
|
||||
<Empty
|
||||
description="暂无面试数据"
|
||||
style={{
|
||||
paddingTop: '100px',
|
||||
fontSize: '14px',
|
||||
color: '#86909c'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
377
src/pages/CompanyJobsPage/index.jsx.backup_20251017_175941
Normal file
377
src/pages/CompanyJobsPage/index.jsx.backup_20251017_175941
Normal file
@@ -0,0 +1,377 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
// import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; // 不再需要映射器,mock数据已经是前端格式
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
import InterviewStatusAnimation from "./components/InterviewStatusAnimation";
|
||||
import JobInfoModal from "./components/JobInfoModal";
|
||||
import {
|
||||
getCompanyJobsPageData,
|
||||
getJobsList,
|
||||
getInterviewsList,
|
||||
getJobsDetail,
|
||||
} from "@/services";
|
||||
import { getJobByPosition } from "@/services/companyJobsNew";
|
||||
|
||||
import "./index.css";
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const CompanyJobsPage = () => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
const [jobs, setJobs] = useState([]);
|
||||
const [jobsListPage, setJobsListPage] = useState(1);
|
||||
const [jobsListHasMore, setJobsListHasMore] = useState(true);
|
||||
const [interviews, setInterviews] = useState([]);
|
||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedItemId, setExpandedItemId] = useState(null);
|
||||
const [jobDetailVisible, setJobDetailVisible] = useState(false);
|
||||
const [selectedJob, setSelectedJob] = useState(null);
|
||||
const [isFromInterview, setIsFromInterview] = useState(false); // 标识是否从面试状态卡片点击
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 初始化页面数据 - 使用聚合接口
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await getCompanyJobsPageData({
|
||||
studentId: studentInfo?.id,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// 设置面试数据
|
||||
let interviewsData = [];
|
||||
if (res.data?.interviews) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
interviewsData = res.data.interviews.list || [];
|
||||
setInterviews(interviewsData);
|
||||
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||
if (interviewsData.length > 0) {
|
||||
setInterviewsPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
// 设置岗位数据 - 包含已投递的岗位
|
||||
if (res.data?.jobs) {
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const jobsList = res.data.jobs.list || [];
|
||||
|
||||
// 从面试数据中提取已投递的岗位信息
|
||||
const deliveredJobs = interviewsData.map(interview => {
|
||||
// 确保有完整的岗位数据
|
||||
const jobData = interview.job || {};
|
||||
return {
|
||||
id: `delivered-${interview.id}`, // 使用特殊的ID标识已投递岗位
|
||||
position: interview.position,
|
||||
isDelivered: true, // 标记为已投递
|
||||
interviewTime: interview.interviewTime,
|
||||
interviewStatus: interview.statusText,
|
||||
originalInterviewId: interview.id,
|
||||
// 从job对象中提取所有必要字段
|
||||
salary: jobData.salary || "面议",
|
||||
tags: jobData.tags || [],
|
||||
location: jobData.location || "待定",
|
||||
education: jobData.education || "待定",
|
||||
jobCategory: jobData.jobCategory || "专业相关岗位",
|
||||
remainingPositions: jobData.remainingPositions || 5,
|
||||
deadline: jobData.deadline || "2025-12-31",
|
||||
jobType: jobData.jobType || "job",
|
||||
requirements: jobData.requirements || "",
|
||||
description: jobData.description || "",
|
||||
welfare: jobData.welfare || [],
|
||||
companyInfo: jobData.companyInfo || "",
|
||||
companyImages: jobData.companyImages || []
|
||||
};
|
||||
}).filter(job => job.position); // 过滤掉没有岗位信息的项
|
||||
|
||||
// 分离未投递和已过期的岗位
|
||||
const activeJobs = jobsList.filter(job => !job.isExpired && job.status !== 'expired');
|
||||
const expiredJobs = jobsList.filter(job => job.isExpired || job.status === 'expired');
|
||||
|
||||
// 按照顺序合并:未投递 -> 已投递 -> 已过期
|
||||
const allJobs = [...activeJobs, ...deliveredJobs, ...expiredJobs];
|
||||
setJobs(allJobs);
|
||||
setJobsListHasMore(res.data.jobs.hasMore);
|
||||
if (allJobs.length > 0) {
|
||||
setJobsListPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
setInitialDataLoaded(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch initial page data:", error);
|
||||
// 如果聚合接口失败,回退到原来的方式
|
||||
setInitialDataLoaded(true);
|
||||
// 显示错误信息给用户
|
||||
if (toast && toast.error) {
|
||||
toast.error("加载数据失败,请刷新重试");
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
}, [studentInfo?.id]);
|
||||
|
||||
// 获取面试信息 - 用于分页加载更多
|
||||
const fetchInterviewsData = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (studentInfo?.id) {
|
||||
const res = await getInterviewsList({
|
||||
page: interviewsPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id,
|
||||
status: "SCHEDULED",
|
||||
});
|
||||
if (res.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const interviews = res.data || [];
|
||||
setInterviews((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(
|
||||
prevList.map((interview) => interview.id)
|
||||
);
|
||||
const newInterviews = interviews.filter(
|
||||
(interview) => !existingIds.has(interview.id)
|
||||
);
|
||||
|
||||
const newList = [...prevList, ...newInterviews];
|
||||
if (res.total <= newList?.length) {
|
||||
setInterviewsHasMore(false);
|
||||
} else {
|
||||
setInterviewsPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
} else {
|
||||
if (interviewsPage === 1) {
|
||||
setInterviews([]);
|
||||
}
|
||||
toast.error(res.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理面试状态点击
|
||||
const handleStatusClick = (e, item) => {
|
||||
e.stopPropagation();
|
||||
// 如果点击的是已展开的项,则收起;否则展开新项
|
||||
if (expandedItemId === item.id) {
|
||||
setExpandedItemId(null);
|
||||
} else {
|
||||
setExpandedItemId(item.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业内推岗位 - 用于分页加载更多
|
||||
const fetchJobsList = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复请求
|
||||
if (jobsListPage === 1 && jobs.length === 0) {
|
||||
return; // 初始数据应该通过聚合接口加载
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getJobsList({
|
||||
page: jobsListPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const jobs = res.data;
|
||||
setJobs((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(prevList.map((job) => job.id));
|
||||
const newJobs = jobs.filter((job) => !existingIds.has(job.id));
|
||||
|
||||
const newList = [...prevList, ...newJobs];
|
||||
if (res.total <= newList?.length) {
|
||||
setJobsListHasMore(false);
|
||||
} else {
|
||||
setJobsListPage((prevPage) => prevPage + 1);
|
||||
}
|
||||
return newList;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
if (jobsListPage === 1) {
|
||||
setJobs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleJobWrapperClick = () => {
|
||||
navigate("/company-jobs-list");
|
||||
};
|
||||
|
||||
// 处理岗位卡片点击,显示岗位详情
|
||||
const handleJobCardClick = async (item) => {
|
||||
// 直接从企业内推岗位库中查找对应的岗位
|
||||
if (item.position) {
|
||||
const jobData = getJobByPosition(item.position);
|
||||
|
||||
if (jobData) {
|
||||
setSelectedJob(jobData);
|
||||
setIsFromInterview(true); // 标记是从面试状态卡片点击的
|
||||
setJobDetailVisible(true);
|
||||
} else {
|
||||
toast.error("未找到对应的岗位详情");
|
||||
}
|
||||
} else {
|
||||
toast.error("无法获取岗位详情");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="company-jobs-page-wrapper">
|
||||
<div className="company-jobs-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="company-jobs-page-spin" />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="company-jobs-page-left"
|
||||
>
|
||||
<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}
|
||||
className="company-jobs-page-left-list-wrapper"
|
||||
>
|
||||
<JobList data={jobs} />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-wrapper">
|
||||
<div
|
||||
className="company-jobs-page-interview"
|
||||
>
|
||||
<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}
|
||||
empty={interviews.length === 0}
|
||||
className="company-jobs-page-interview-list"
|
||||
>
|
||||
{interviews.map((item) => (
|
||||
<div key={item.id} className="interview-item-wrapper">
|
||||
<li
|
||||
className="company-jobs-page-interview-item"
|
||||
onClick={() => handleJobCardClick(item)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<div className="company-jobs-page-interview-item-info">
|
||||
<p className="company-jobs-page-interview-item-info-position">
|
||||
{item.position}
|
||||
</p>
|
||||
{item.job?.tags?.length > 0 ? (
|
||||
<ul className="company-jobs-page-interview-item-info-tags">
|
||||
{item.job.tags.map((tag) => (
|
||||
<li
|
||||
className="company-jobs-page-interview-item-info-tag"
|
||||
key={tag}
|
||||
>
|
||||
{tag}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<span className="company-jobs-page-interview-item-info-salary">
|
||||
{item.job?.salary || "面议"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="company-jobs-page-interview-item-btn-wrapper">
|
||||
<span>{item.interviewTime}</span>
|
||||
<div
|
||||
className={`company-jobs-page-interview-item-btn ${
|
||||
item.status !== "COMPLETED" &&
|
||||
"company-jobs-page-interview-item-btn-active"
|
||||
}`}
|
||||
onClick={(e) => handleStatusClick(e, item)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{item.statusText}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<InterviewStatusAnimation
|
||||
statusText={item.statusText}
|
||||
isOpen={expandedItemId === item.id}
|
||||
stageDate={item.stageDate}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 岗位详情弹窗 */}
|
||||
<JobInfoModal
|
||||
visible={jobDetailVisible}
|
||||
onClose={() => {
|
||||
setJobDetailVisible(false);
|
||||
setSelectedJob(null);
|
||||
setIsFromInterview(false); // 重置标志
|
||||
}}
|
||||
data={selectedJob}
|
||||
directToResume={false}
|
||||
hideDeliverButton={isFromInterview} // 传递是否隐藏投递按钮的标志
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyJobsPage;
|
||||
@@ -23,156 +23,32 @@ export default ({ selectedItem = "面试初体验" }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否是锁定的面试模拟项目
|
||||
// 所有视频都显示锁定样式
|
||||
const isLockedItem = () => {
|
||||
return selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
selectedItem === "第三次线下面试模拟";
|
||||
return true;
|
||||
};
|
||||
|
||||
// 根据选中项目获取评价数据
|
||||
// 根据选中项目获取评价数据 - 所有数据改为0或空
|
||||
const getEvaluationData = () => {
|
||||
if (selectedItem === "未来的自己") {
|
||||
return {
|
||||
totalScore: 97, // 根据JSON计算的真实总分
|
||||
professionalScore: 57, // (9+10+10+9+10+9)/6*10*0.6 = 57
|
||||
performanceScore: 40, // (10+10+10+10)/4*10*0.4 = 40
|
||||
radarData: [9, 10, 10, 9, 10, 9], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [10, 10, 10, 10], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
return {
|
||||
totalScore: 0,
|
||||
professionalScore: 0,
|
||||
performanceScore: 0,
|
||||
radarData: [0, 0, 0, 0, 0, 0],
|
||||
radarData2: [0, 0, 0, 0],
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 专业知识学得特别扎实,讲核心概念的时候又准又清楚,还能随手举例子把道理说透,不光能把知识点讲明白,还能用实例帮人理解,一看就是专业底子特别厚,也很会用学到的知识;
|
||||
2. 对行业里的产业链、还有未来的发展趋势摸得特别透,不光能说清产业链各个环节是怎么连起来的,还能具体讲明白这些趋势会影响到岗位工作、业务方向,聊到行业相关的话题,总能说出有想法的观点,跟行业里的人交流完全没障碍;
|
||||
3. 对企业从头到尾的工作流程门儿清,知道业务怎么推进、关键环节在哪,还有跟其他部门怎么配合,既能说清自己进了企业要干些啥,也能找准自己在流程里的位置,跟不同部门合作的关键点也拎得很清,跟实际工作场景特别适配;
|
||||
4. 碰到具体任务或问题,拆解得特别有条理,分析思路也很系统,能从实际需要出发理出解决办法,想的方案既靠谱能落地,又有新点子,还能用具体数据说明能达到啥效果,不管是想办法还是实际操作,都做得挺到位;
|
||||
5. 对目标岗位的工作职责、要干的活、需要啥能力都摸得很清楚,聊职业规划和自己跟岗位合不合适的时候,能精准说清自己能发挥啥作用,还能把自己的优势、经历跟岗位需求、业务目标绑在一起说,一看就跟岗位特别对路;
|
||||
6. 做过的项目又多又完整,说项目的时候能讲清背景、自己具体干了啥、负责哪些环节,还能好好总结从里面学到了啥、能力咋提升的,通过这些项目,能实实在在看出专业能力和动手做事的本事;
|
||||
暂无评价数据
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话表达能力特别好,嘴皮子利索还挺有劲儿,说事儿的时候结构很清楚,每个信息都能说到点子上,既能让人准确 get 到核心观点,又能高效把关键信息传出去,沟通起来又快又准;
|
||||
2. 心态特别稳,不管是被提问、有工作压力,还是碰到突发情况,一直都很自信、不慌不乱,思路也很清晰,既能把工作或回答做好,还能巧妙化解压力,扛事儿能力和临场应变都挺强;
|
||||
3. 跟人交流、做展示的时候,姿态特别专业,眼神交流自然又真诚,手势动作跟说话配合得刚好,既显得得体专业,又能通过动作让自己的观点更有说服力,让人觉得特别靠谱;
|
||||
4. 对时间和流程把控得特别好,不管是做事、聊天还是做展示,每个环节的时间都掐得准,环节之间过渡也很顺,还会留时间做总结,能保证整个过程顺畅推进,干活、沟通的效率都很高。
|
||||
暂无评价数据
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在专业基础、行业认知、懂企业流程、解决问题、适配岗位、项目经验、说话表达、心态调整、职业仪态还有时间管理这些关键方面,都表现得特别优秀,综合能力和职业素养都很突出。这些优点不光能让专业交流更顺畅、解决问题更合理,还能看出他特别有职业潜力,跟岗位也特别匹配,是个很有发展前途的好苗子。`
|
||||
};
|
||||
} else if (selectedItem === "第一次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 42, // 根据JSON计算的真实总分
|
||||
professionalScore: 28, // (4+6+5+4+6+3)/6*10*0.6 = 28
|
||||
performanceScore: 14, // (2+4+5+3)/4*10*0.4 = 14
|
||||
radarData: [4, 6, 5, 4, 6, 3], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [2, 4, 5, 3], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 对知识点的概念总弄混,回答问题也只停在表面,没法往深了说 —— 比如问个核心概念,要么跟别的概念搅在一起,要么就说几句皮毛,根本挖不出背后的门道,能看出来对知识的理解还差得远;
|
||||
2. 知道的行业知识都是零零散散的,没形成系统,尤其说不明白行业趋势跟岗位、业务的关系 —— 比如问某个趋势会影响工作内容不,他就答不上来,对行业的认知特别散,没串起来;
|
||||
3. 对工作流程的概念特别模糊,连自己该干啥都搞不清 —— 比如问企业里某个业务流程怎么走,他说不明白,再问他在里面要承担啥角色,更是没头绪,完全没找准自己的位置;
|
||||
4. 分析问题的时候特别局限,想的方案也很片面,连怎么落地的步骤都没有 —— 比如让他给个解决办法,只能说个大概方向,至于需要哪些资源、分几步做、怎么推进,根本没考虑,这样的方案根本没法用;
|
||||
5. 对目标岗位的认知特别模糊,连岗位的核心工作是啥、该干到啥程度、哪些活不归自己管,都弄不明白 —— 问他这个岗位主要负责啥,他说的颠三倒四,工作边界更是完全没概念,明显没搞懂岗位到底是干啥的;
|
||||
6. 做过的项目特别少,就算有一两个,也说不明白情况 —— 要么讲不清项目是做什么的,要么说不出自己在里面具体干了啥,连自己到底是啥角色都模模糊糊,根本没法用项目证明自己有能力;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别散乱,抓不住重点,逻辑还老跳 —— 比如跟他聊个事儿,他东说一句西说一句,关键信息没几句,还经常突然从一个话题跳到另一个,听的人根本跟不上,半天搞不清他想表达啥;
|
||||
2. 情绪波动特别大,一会儿好一会儿坏,特别影响沟通 —— 可能刚开始聊得好好的,稍微有点问题就慌了,或者没耐心了,跟他交流的时候,很容易因为他的情绪受影响,沟通效果特别差;
|
||||
3. 跟人说话或者坐着的时候,小动作特别多,坐姿也不稳 —— 一会儿摸笔、一会儿挠头,身子还总晃来晃去,这些动作特别容易分散别人的注意力,让人没法专心听他说话,印象分也会打折扣;
|
||||
4. 不管是做事、做展示还是跟人聊天,时间把控得特别差 —— 要么说起来没个完,严重超时;要么没说几句就结束了,整个过程一点条理都没有,结构乱得很,完全没规划。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识理解、行业认知、流程和岗位把握、方案设计、项目经验、表达逻辑,还有情绪管理、行为仪态、时间把控这些方面,都有挺明显的问题。这些问题不光让日常沟通和解决问题受影响,也能看出来他现在还不太能适应实际工作的要求,之后得重点补补知识的深度、多了解行业和岗位,再好好练练说话的逻辑和心态,慢慢把综合能力提上来才行。`
|
||||
};
|
||||
} else if (selectedItem === "第二次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 67, // 根据JSON计算的真实总分
|
||||
professionalScore: 41, // (7+7+6+6+7+8)/6*10*0.6 = 41
|
||||
performanceScore: 26, // (8+7+6+5)/4*10*0.4 = 26
|
||||
radarData: [7, 7, 6, 6, 7, 8], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [8, 7, 6, 5], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 关键知识掌握得挺全面的,大部分内容都能抓准,就是偶尔在小细节上有点马虎,比如个别知识点的细微区别会记混,但整体来看,知识的准确性还是不错的,核心内容都能掌握到位;
|
||||
2. 对市场上的主要动态有了解,比如行业里近期的热门方向、大家关注的重点,都能说出个大概,而且能简单讲讲这些动态对业务开展有啥意义,虽然说得不算深入,但至少能把 "动态和业务" 的关联点到,有基本的认知;
|
||||
3. 明白工作的主要流程是啥,比如一项业务从开始到结束要经过哪些关键步骤,都能说清楚,也知道自己在流程里负责哪个环节、要干些啥,但在细节上就有点粗糙了,比如环节之间怎么衔接、遇到小问题该怎么处理,就说不太细;
|
||||
4. 面对问题或者任务时,能有个初步的思路雏形,比如想解决某个问题,能先提出一两个大概的方向,但思路不够系统,没有把 "为什么这么做、步骤是啥、需要啥支持" 串成完整的逻辑,论证的时候也缺乏足够的理由或者例子支撑,说服力还差了点;
|
||||
5. 知道目标岗位的主要任务是啥,能说出大概的工作范围,比如日常要处理哪些事、负责哪些板块,但没法深入剖析岗位 —— 像岗位的核心价值是啥、不同任务之间的优先级怎么排、需要具备哪些隐藏技能,这些深入的内容就说不出来了;
|
||||
6. 也参与过一定数量的项目,不是没经验的,聊项目的时候能大体描述自己在里面做了啥任务,比如负责过数据整理、协助过方案讨论,但说到项目成果就有点笼统了,比如只说 "完成了任务",没说清 "任务带来了啥效果、自己的贡献让项目有啥提升",成果没法具体体现;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话的逻辑基本能让人听明白,不会让人抓不着重点,但偶尔会有重复的情况,比如同一句话换个说法又说一遍,或者讲到一半会停顿一下,想不起来下一句该说啥,不过整体的表达节奏还能跟上,不影响理解;
|
||||
2. 面对交流或者任务时,基本能保持镇定,不会慌慌张张的,就算偶尔有点紧张,比如说话声音稍微变小、语速变快,也能自己调整过来,很快恢复平稳的状态,不会让紧张影响整体表现;
|
||||
3. 平时的体态看起来挺得体的,坐姿、站姿都比较规范,跟人交流时也不会有太随意的动作,就是偶尔会有点僵硬,比如坐着的时候身体绷得太紧、手势不太自然,但这些小问题不影响整体的印象,还是显得比较专业的;
|
||||
4. 不管是做事、做展示还是跟人沟通,基本能在规定时间内完成,不会出现严重超时或者没做完的情况,就是偶尔会有点小偏差 —— 要么比规定时间多花个几分钟,要么为了赶时间稍微省略一点内容,但整体的进度和完整性还是有保障的。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识掌握、市场认知、流程理解、思路形成、岗位认知、项目经验、表达逻辑、心态调整、体态和时间把控上,都有基础的能力,没有特别明显的短板,但在 "细节、深度、系统性" 上还有提升空间。之后可以重点补补细节知识、多深入思考岗位和项目的核心价值、把思路梳理得更系统,这样综合能力就能再上一个台阶,也会更适配实际工作的要求。`
|
||||
};
|
||||
} else if (selectedItem === "第三次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 91, // 根据JSON计算的真实总分
|
||||
professionalScore: 54, // (8+10+9+8+10+9)/6*10*0.6 = 54
|
||||
performanceScore: 37, // (10+8+10+10)/4*10*0.4 = 38 (约37)
|
||||
radarData: [8, 10, 9, 8, 10, 9], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [10, 8, 10, 10], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 关键知识掌握得特别全面,不管是核心考点还是重要内容,都能稳稳抓住,就是偶尔在小细节上会有点疏漏,比如个别细碎知识点记不太准,但整体来看,知识的准确性特别好,不会出大差错;
|
||||
2. 对行业里的产业链和发展趋势摸得很透,不光能说清产业链各个环节怎么联动,还能具体讲明白这些趋势会给岗位工作、业务开展带来啥影响,比如哪种趋势会让岗位多些新任务,哪种趋势能帮业务找新方向,分析得特别实在;
|
||||
3. 能把企业从头到尾的工作流程说得明明白白,哪个环节该干啥、流程里的关键节点是啥,都门儿清,而且能找准自己在流程里的角色,就连跟其他部门怎么配合、配合的关键点是啥,也能说得很到位,完全不像没接触过实际工作的;
|
||||
4. 就算单说具体的主要流程,也能讲清楚自己负责的环节要做啥,比如流程里的资料整理、对接沟通这些活儿,都能说透,就是在细节上稍微有点粗糙,比如环节之间怎么交接更顺畅、遇到小问题怎么快速处理,说得没那么细;
|
||||
5. 对目标岗位的职责了解得特别全面,岗位要干的活儿、承担的责任都能说全,还能精准找到自己在岗位上的价值 —— 比如自己能帮岗位解决啥问题、能给团队带来啥助力,更厉害的是,能结合实际例子说明这些职责和价值怎么跟业务目标挂钩,比如做好某项工作能帮业务完成多少指标,逻辑特别顺;
|
||||
6. 做过的项目又多又完整,不管是校园里的实践项目,还是外面的实习项目,都有涉及,聊项目的时候,能清清楚楚说清自己在里面扮演啥角色、过程中具体做了哪些贡献,就连最后项目拿到啥成果、带来啥效果,也能说得明明白白,不会含糊其辞;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别流畅,而且很有劲儿,不管是回答问题还是分享想法,表达的结构都很严谨,不会东拉西扯,每个信息点都能精准说到点子上,让人一听就懂,还能快速 get 到核心内容,沟通效率特别高;
|
||||
2. 面对提问或者展示这些场景,基本能保持镇定,不会慌里慌张的,就算偶尔有点紧张,比如语速稍微变快、声音有点抖,也能自己快速调整过来,很快就恢复平稳状态,不会让紧张影响整体发挥;
|
||||
3. 跟人交流的时候,目光交流特别自然,不会躲躲闪闪,肢体动作也跟说话内容配合得刚好,比如讲重点的时候会配合手势强调,坐着的时候姿态也很放松,这些细节让说的话更有说服力,让人觉得特别靠谱;
|
||||
4. 不管是做展示、答问题,还是走流程,每个环节的时间都控制得特别准,不会出现超时或者没说完的情况,环节之间衔接得也很自然,不会有生硬的停顿,更难得的是,还会特意留时间做总结,把核心内容再梳理一遍,让人印象更深刻。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识掌握、行业认知、流程理解、岗位适配、项目经验、表达能力、心态调整、沟通仪态和时间把控上,都表现得特别出色,基础扎实还懂实际应用,就算偶尔有小瑕疵也不影响整体实力。这样的学生不管是继续学习还是去工作,都能快速适应,后续再把流程细节打磨打磨,综合能力还能再上一个大台阶,绝对是个好苗子。`
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
totalScore: 14, // 根据JSON计算的真实总分 (面试初体验)
|
||||
professionalScore: 7, // (2+1+1+1+1+1)/6*10*0.6 = 7
|
||||
performanceScore: 7, // (2+1+2+2)/4*10*0.4 = 7
|
||||
radarData: [2, 1, 1, 1, 1, 1], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [2, 1, 2, 2], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 基础概念掌握得很不好,经常犯错误,连最基本的知识点都拎不清,这样一来根本没办法展开有效的交流,说出来的内容因为概念错了,也没什么参考价值;
|
||||
2. 对行业基本情况几乎一无所知,不管问什么跟行业相关的问题,都答不上来,完全没接触过行业里的常识,聊起行业话题根本插不上话;
|
||||
3. 一点都不理解企业的工作流程,不知道一项业务是怎么推进的,也说不清楚如果自己进了企业,该在哪个环节做事、要干些什么,对实际工作场景完全没概念;
|
||||
4. 碰到问题或任务的时候,一点清晰的思路都没有,东想西想没章法,想出来的方案要么不切实际、没法落地,要么根本没解决核心问题,完全拿不出能用的办法;
|
||||
5. 对目标岗位的工作职责彻底不了解,不知道岗位要干哪些活、需要什么能力,聊到跟岗位相关的内容,根本说不出有用的信息,也没法表达自己跟岗位的关联;
|
||||
6. 要么就没做过什么项目,要么就是有项目经历也说不明白 —— 既讲不清项目背景,也说不出自己在里面干了啥,更总结不出从项目里学到了什么、能力有没有提升,完全没法用项目证明自己的能力;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别不连贯,一句完整的话都说不利索,想表达的观点颠三倒四,听的人得费劲猜才能勉强懂一点,信息传递特别低效,很容易让人误解;
|
||||
2. 心态特别差,一碰到提问或者有点压力的情况,就慌慌张张的,要么说不出话,要么越说越乱,根本没办法持续把问题答完,稍微有点压力就扛不住;
|
||||
3. 跟人交流或者做展示的时候,姿态特别不专业,要么不敢抬头看人、眼神躲躲闪闪,要么肢体动作很僵硬、很别扭,显得特别紧张、不靠谱,根本没法让人信服;
|
||||
4. 完全没有时间概念,不管是做事、聊天还是做展示,都把控不好时间 —— 要么一个环节拖半天,要么节奏乱得一塌糊涂,严重影响整个流程的推进,让整体效率特别低。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在专业基础、行业认知、企业流程理解、问题解决、岗位认知、项目经验、表达能力、心态调整、职业仪态和时间管理这些关键方面,都存在比较明显的不足。这些问题不光让专业交流没法顺利进行、碰到问题没法有效解决,也能看出目前他还不太适配实际工作场景,职业潜力还需要花很多功夫去挖掘和提升,得好好补补基础、多积累实践经验才行。`
|
||||
};
|
||||
}
|
||||
暂无评价数据`
|
||||
};
|
||||
};
|
||||
|
||||
// 判断是否应该显示评价内容
|
||||
@@ -199,44 +75,13 @@ export default ({ selectedItem = "面试初体验" }) => {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden'
|
||||
backgroundColor: '#ffffff',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{/* 背景图片 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundImage: `url(${
|
||||
selectedItem === "第一次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第一次线下面试模拟.jpg"
|
||||
: selectedItem === "第二次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第二次线下面试模拟.jpg"
|
||||
: "/src/assets/images/InterviewSimulationPage/第三次线下面试模拟.jpg"
|
||||
})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
filter: 'blur(20px)',
|
||||
transform: 'scale(1.1)'
|
||||
}} />
|
||||
|
||||
{/* 半透明遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||
}} />
|
||||
|
||||
{/* 锁图标和文字 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
@@ -248,15 +93,17 @@ export default ({ selectedItem = "面试初体验" }) => {
|
||||
style={{ width: '280px', height: '280px' }}
|
||||
/>
|
||||
<span style={{
|
||||
color: '#fff',
|
||||
color: '#000',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px'
|
||||
backgroundColor: '#fff',
|
||||
padding: '12px 24px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
whiteSpace: 'pre-line'
|
||||
}}>
|
||||
DEMO演示,非学员无查看权限
|
||||
该板块将于「垂直能力提升」阶段启动后开放{"\n"}届时,请留意教务系统通知,您可在线下站点进行线下面试模拟
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import ScoreChart from "../ScoreChart";
|
||||
import RadarChart from "../RadarChart";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ selectedItem = "面试初体验" }) => {
|
||||
// 根据选中项目获取对应的视频URL
|
||||
const getVideoUrl = () => {
|
||||
switch(selectedItem) {
|
||||
case "面试初体验":
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_ago.mov";
|
||||
case "未来的自己":
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_later.mov";
|
||||
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 "";
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否是锁定的面试模拟项目
|
||||
const isLockedItem = () => {
|
||||
return selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
selectedItem === "第三次线下面试模拟";
|
||||
};
|
||||
|
||||
// 根据选中项目获取评价数据
|
||||
const getEvaluationData = () => {
|
||||
if (selectedItem === "未来的自己") {
|
||||
return {
|
||||
totalScore: 97, // 根据JSON计算的真实总分
|
||||
professionalScore: 57, // (9+10+10+9+10+9)/6*10*0.6 = 57
|
||||
performanceScore: 40, // (10+10+10+10)/4*10*0.4 = 40
|
||||
radarData: [9, 10, 10, 9, 10, 9], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [10, 10, 10, 10], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 专业知识学得特别扎实,讲核心概念的时候又准又清楚,还能随手举例子把道理说透,不光能把知识点讲明白,还能用实例帮人理解,一看就是专业底子特别厚,也很会用学到的知识;
|
||||
2. 对行业里的产业链、还有未来的发展趋势摸得特别透,不光能说清产业链各个环节是怎么连起来的,还能具体讲明白这些趋势会影响到岗位工作、业务方向,聊到行业相关的话题,总能说出有想法的观点,跟行业里的人交流完全没障碍;
|
||||
3. 对企业从头到尾的工作流程门儿清,知道业务怎么推进、关键环节在哪,还有跟其他部门怎么配合,既能说清自己进了企业要干些啥,也能找准自己在流程里的位置,跟不同部门合作的关键点也拎得很清,跟实际工作场景特别适配;
|
||||
4. 碰到具体任务或问题,拆解得特别有条理,分析思路也很系统,能从实际需要出发理出解决办法,想的方案既靠谱能落地,又有新点子,还能用具体数据说明能达到啥效果,不管是想办法还是实际操作,都做得挺到位;
|
||||
5. 对目标岗位的工作职责、要干的活、需要啥能力都摸得很清楚,聊职业规划和自己跟岗位合不合适的时候,能精准说清自己能发挥啥作用,还能把自己的优势、经历跟岗位需求、业务目标绑在一起说,一看就跟岗位特别对路;
|
||||
6. 做过的项目又多又完整,说项目的时候能讲清背景、自己具体干了啥、负责哪些环节,还能好好总结从里面学到了啥、能力咋提升的,通过这些项目,能实实在在看出专业能力和动手做事的本事;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话表达能力特别好,嘴皮子利索还挺有劲儿,说事儿的时候结构很清楚,每个信息都能说到点子上,既能让人准确 get 到核心观点,又能高效把关键信息传出去,沟通起来又快又准;
|
||||
2. 心态特别稳,不管是被提问、有工作压力,还是碰到突发情况,一直都很自信、不慌不乱,思路也很清晰,既能把工作或回答做好,还能巧妙化解压力,扛事儿能力和临场应变都挺强;
|
||||
3. 跟人交流、做展示的时候,姿态特别专业,眼神交流自然又真诚,手势动作跟说话配合得刚好,既显得得体专业,又能通过动作让自己的观点更有说服力,让人觉得特别靠谱;
|
||||
4. 对时间和流程把控得特别好,不管是做事、聊天还是做展示,每个环节的时间都掐得准,环节之间过渡也很顺,还会留时间做总结,能保证整个过程顺畅推进,干活、沟通的效率都很高。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在专业基础、行业认知、懂企业流程、解决问题、适配岗位、项目经验、说话表达、心态调整、职业仪态还有时间管理这些关键方面,都表现得特别优秀,综合能力和职业素养都很突出。这些优点不光能让专业交流更顺畅、解决问题更合理,还能看出他特别有职业潜力,跟岗位也特别匹配,是个很有发展前途的好苗子。`
|
||||
};
|
||||
} else if (selectedItem === "第一次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 42, // 根据JSON计算的真实总分
|
||||
professionalScore: 28, // (4+6+5+4+6+3)/6*10*0.6 = 28
|
||||
performanceScore: 14, // (2+4+5+3)/4*10*0.4 = 14
|
||||
radarData: [4, 6, 5, 4, 6, 3], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [2, 4, 5, 3], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 对知识点的概念总弄混,回答问题也只停在表面,没法往深了说 —— 比如问个核心概念,要么跟别的概念搅在一起,要么就说几句皮毛,根本挖不出背后的门道,能看出来对知识的理解还差得远;
|
||||
2. 知道的行业知识都是零零散散的,没形成系统,尤其说不明白行业趋势跟岗位、业务的关系 —— 比如问某个趋势会影响工作内容不,他就答不上来,对行业的认知特别散,没串起来;
|
||||
3. 对工作流程的概念特别模糊,连自己该干啥都搞不清 —— 比如问企业里某个业务流程怎么走,他说不明白,再问他在里面要承担啥角色,更是没头绪,完全没找准自己的位置;
|
||||
4. 分析问题的时候特别局限,想的方案也很片面,连怎么落地的步骤都没有 —— 比如让他给个解决办法,只能说个大概方向,至于需要哪些资源、分几步做、怎么推进,根本没考虑,这样的方案根本没法用;
|
||||
5. 对目标岗位的认知特别模糊,连岗位的核心工作是啥、该干到啥程度、哪些活不归自己管,都弄不明白 —— 问他这个岗位主要负责啥,他说的颠三倒四,工作边界更是完全没概念,明显没搞懂岗位到底是干啥的;
|
||||
6. 做过的项目特别少,就算有一两个,也说不明白情况 —— 要么讲不清项目是做什么的,要么说不出自己在里面具体干了啥,连自己到底是啥角色都模模糊糊,根本没法用项目证明自己有能力;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别散乱,抓不住重点,逻辑还老跳 —— 比如跟他聊个事儿,他东说一句西说一句,关键信息没几句,还经常突然从一个话题跳到另一个,听的人根本跟不上,半天搞不清他想表达啥;
|
||||
2. 情绪波动特别大,一会儿好一会儿坏,特别影响沟通 —— 可能刚开始聊得好好的,稍微有点问题就慌了,或者没耐心了,跟他交流的时候,很容易因为他的情绪受影响,沟通效果特别差;
|
||||
3. 跟人说话或者坐着的时候,小动作特别多,坐姿也不稳 —— 一会儿摸笔、一会儿挠头,身子还总晃来晃去,这些动作特别容易分散别人的注意力,让人没法专心听他说话,印象分也会打折扣;
|
||||
4. 不管是做事、做展示还是跟人聊天,时间把控得特别差 —— 要么说起来没个完,严重超时;要么没说几句就结束了,整个过程一点条理都没有,结构乱得很,完全没规划。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识理解、行业认知、流程和岗位把握、方案设计、项目经验、表达逻辑,还有情绪管理、行为仪态、时间把控这些方面,都有挺明显的问题。这些问题不光让日常沟通和解决问题受影响,也能看出来他现在还不太能适应实际工作的要求,之后得重点补补知识的深度、多了解行业和岗位,再好好练练说话的逻辑和心态,慢慢把综合能力提上来才行。`
|
||||
};
|
||||
} else if (selectedItem === "第二次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 67, // 根据JSON计算的真实总分
|
||||
professionalScore: 41, // (7+7+6+6+7+8)/6*10*0.6 = 41
|
||||
performanceScore: 26, // (8+7+6+5)/4*10*0.4 = 26
|
||||
radarData: [7, 7, 6, 6, 7, 8], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [8, 7, 6, 5], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 关键知识掌握得挺全面的,大部分内容都能抓准,就是偶尔在小细节上有点马虎,比如个别知识点的细微区别会记混,但整体来看,知识的准确性还是不错的,核心内容都能掌握到位;
|
||||
2. 对市场上的主要动态有了解,比如行业里近期的热门方向、大家关注的重点,都能说出个大概,而且能简单讲讲这些动态对业务开展有啥意义,虽然说得不算深入,但至少能把 "动态和业务" 的关联点到,有基本的认知;
|
||||
3. 明白工作的主要流程是啥,比如一项业务从开始到结束要经过哪些关键步骤,都能说清楚,也知道自己在流程里负责哪个环节、要干些啥,但在细节上就有点粗糙了,比如环节之间怎么衔接、遇到小问题该怎么处理,就说不太细;
|
||||
4. 面对问题或者任务时,能有个初步的思路雏形,比如想解决某个问题,能先提出一两个大概的方向,但思路不够系统,没有把 "为什么这么做、步骤是啥、需要啥支持" 串成完整的逻辑,论证的时候也缺乏足够的理由或者例子支撑,说服力还差了点;
|
||||
5. 知道目标岗位的主要任务是啥,能说出大概的工作范围,比如日常要处理哪些事、负责哪些板块,但没法深入剖析岗位 —— 像岗位的核心价值是啥、不同任务之间的优先级怎么排、需要具备哪些隐藏技能,这些深入的内容就说不出来了;
|
||||
6. 也参与过一定数量的项目,不是没经验的,聊项目的时候能大体描述自己在里面做了啥任务,比如负责过数据整理、协助过方案讨论,但说到项目成果就有点笼统了,比如只说 "完成了任务",没说清 "任务带来了啥效果、自己的贡献让项目有啥提升",成果没法具体体现;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话的逻辑基本能让人听明白,不会让人抓不着重点,但偶尔会有重复的情况,比如同一句话换个说法又说一遍,或者讲到一半会停顿一下,想不起来下一句该说啥,不过整体的表达节奏还能跟上,不影响理解;
|
||||
2. 面对交流或者任务时,基本能保持镇定,不会慌慌张张的,就算偶尔有点紧张,比如说话声音稍微变小、语速变快,也能自己调整过来,很快恢复平稳的状态,不会让紧张影响整体表现;
|
||||
3. 平时的体态看起来挺得体的,坐姿、站姿都比较规范,跟人交流时也不会有太随意的动作,就是偶尔会有点僵硬,比如坐着的时候身体绷得太紧、手势不太自然,但这些小问题不影响整体的印象,还是显得比较专业的;
|
||||
4. 不管是做事、做展示还是跟人沟通,基本能在规定时间内完成,不会出现严重超时或者没做完的情况,就是偶尔会有点小偏差 —— 要么比规定时间多花个几分钟,要么为了赶时间稍微省略一点内容,但整体的进度和完整性还是有保障的。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识掌握、市场认知、流程理解、思路形成、岗位认知、项目经验、表达逻辑、心态调整、体态和时间把控上,都有基础的能力,没有特别明显的短板,但在 "细节、深度、系统性" 上还有提升空间。之后可以重点补补细节知识、多深入思考岗位和项目的核心价值、把思路梳理得更系统,这样综合能力就能再上一个台阶,也会更适配实际工作的要求。`
|
||||
};
|
||||
} else if (selectedItem === "第三次线下面试模拟") {
|
||||
return {
|
||||
totalScore: 91, // 根据JSON计算的真实总分
|
||||
professionalScore: 54, // (8+10+9+8+10+9)/6*10*0.6 = 54
|
||||
performanceScore: 37, // (10+8+10+10)/4*10*0.4 = 38 (约37)
|
||||
radarData: [8, 10, 9, 8, 10, 9], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [10, 8, 10, 10], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 关键知识掌握得特别全面,不管是核心考点还是重要内容,都能稳稳抓住,就是偶尔在小细节上会有点疏漏,比如个别细碎知识点记不太准,但整体来看,知识的准确性特别好,不会出大差错;
|
||||
2. 对行业里的产业链和发展趋势摸得很透,不光能说清产业链各个环节怎么联动,还能具体讲明白这些趋势会给岗位工作、业务开展带来啥影响,比如哪种趋势会让岗位多些新任务,哪种趋势能帮业务找新方向,分析得特别实在;
|
||||
3. 能把企业从头到尾的工作流程说得明明白白,哪个环节该干啥、流程里的关键节点是啥,都门儿清,而且能找准自己在流程里的角色,就连跟其他部门怎么配合、配合的关键点是啥,也能说得很到位,完全不像没接触过实际工作的;
|
||||
4. 就算单说具体的主要流程,也能讲清楚自己负责的环节要做啥,比如流程里的资料整理、对接沟通这些活儿,都能说透,就是在细节上稍微有点粗糙,比如环节之间怎么交接更顺畅、遇到小问题怎么快速处理,说得没那么细;
|
||||
5. 对目标岗位的职责了解得特别全面,岗位要干的活儿、承担的责任都能说全,还能精准找到自己在岗位上的价值 —— 比如自己能帮岗位解决啥问题、能给团队带来啥助力,更厉害的是,能结合实际例子说明这些职责和价值怎么跟业务目标挂钩,比如做好某项工作能帮业务完成多少指标,逻辑特别顺;
|
||||
6. 做过的项目又多又完整,不管是校园里的实践项目,还是外面的实习项目,都有涉及,聊项目的时候,能清清楚楚说清自己在里面扮演啥角色、过程中具体做了哪些贡献,就连最后项目拿到啥成果、带来啥效果,也能说得明明白白,不会含糊其辞;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别流畅,而且很有劲儿,不管是回答问题还是分享想法,表达的结构都很严谨,不会东拉西扯,每个信息点都能精准说到点子上,让人一听就懂,还能快速 get 到核心内容,沟通效率特别高;
|
||||
2. 面对提问或者展示这些场景,基本能保持镇定,不会慌里慌张的,就算偶尔有点紧张,比如语速稍微变快、声音有点抖,也能自己快速调整过来,很快就恢复平稳状态,不会让紧张影响整体发挥;
|
||||
3. 跟人交流的时候,目光交流特别自然,不会躲躲闪闪,肢体动作也跟说话内容配合得刚好,比如讲重点的时候会配合手势强调,坐着的时候姿态也很放松,这些细节让说的话更有说服力,让人觉得特别靠谱;
|
||||
4. 不管是做展示、答问题,还是走流程,每个环节的时间都控制得特别准,不会出现超时或者没说完的情况,环节之间衔接得也很自然,不会有生硬的停顿,更难得的是,还会特意留时间做总结,把核心内容再梳理一遍,让人印象更深刻。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在知识掌握、行业认知、流程理解、岗位适配、项目经验、表达能力、心态调整、沟通仪态和时间把控上,都表现得特别出色,基础扎实还懂实际应用,就算偶尔有小瑕疵也不影响整体实力。这样的学生不管是继续学习还是去工作,都能快速适应,后续再把流程细节打磨打磨,综合能力还能再上一个大台阶,绝对是个好苗子。`
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
totalScore: 14, // 根据JSON计算的真实总分 (面试初体验)
|
||||
professionalScore: 7, // (2+1+1+1+1+1)/6*10*0.6 = 7
|
||||
performanceScore: 7, // (2+1+2+2)/4*10*0.4 = 7
|
||||
radarData: [2, 1, 1, 1, 1, 1], // 六项专业能力指标(来自JSON)
|
||||
radarData2: [2, 1, 2, 2], // 四项现场表现指标(来自JSON)
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
1. 基础概念掌握得很不好,经常犯错误,连最基本的知识点都拎不清,这样一来根本没办法展开有效的交流,说出来的内容因为概念错了,也没什么参考价值;
|
||||
2. 对行业基本情况几乎一无所知,不管问什么跟行业相关的问题,都答不上来,完全没接触过行业里的常识,聊起行业话题根本插不上话;
|
||||
3. 一点都不理解企业的工作流程,不知道一项业务是怎么推进的,也说不清楚如果自己进了企业,该在哪个环节做事、要干些什么,对实际工作场景完全没概念;
|
||||
4. 碰到问题或任务的时候,一点清晰的思路都没有,东想西想没章法,想出来的方案要么不切实际、没法落地,要么根本没解决核心问题,完全拿不出能用的办法;
|
||||
5. 对目标岗位的工作职责彻底不了解,不知道岗位要干哪些活、需要什么能力,聊到跟岗位相关的内容,根本说不出有用的信息,也没法表达自己跟岗位的关联;
|
||||
6. 要么就没做过什么项目,要么就是有项目经历也说不明白 —— 既讲不清项目背景,也说不出自己在里面干了啥,更总结不出从项目里学到了什么、能力有没有提升,完全没法用项目证明自己的能力;
|
||||
|
||||
# 现场表现力
|
||||
|
||||
1. 说话特别不连贯,一句完整的话都说不利索,想表达的观点颠三倒四,听的人得费劲猜才能勉强懂一点,信息传递特别低效,很容易让人误解;
|
||||
2. 心态特别差,一碰到提问或者有点压力的情况,就慌慌张张的,要么说不出话,要么越说越乱,根本没办法持续把问题答完,稍微有点压力就扛不住;
|
||||
3. 跟人交流或者做展示的时候,姿态特别不专业,要么不敢抬头看人、眼神躲躲闪闪,要么肢体动作很僵硬、很别扭,显得特别紧张、不靠谱,根本没法让人信服;
|
||||
4. 完全没有时间概念,不管是做事、聊天还是做展示,都把控不好时间 —— 要么一个环节拖半天,要么节奏乱得一塌糊涂,严重影响整个流程的推进,让整体效率特别低。
|
||||
|
||||
# 综合评价
|
||||
|
||||
总的来说,这学生在专业基础、行业认知、企业流程理解、问题解决、岗位认知、项目经验、表达能力、心态调整、职业仪态和时间管理这些关键方面,都存在比较明显的不足。这些问题不光让专业交流没法顺利进行、碰到问题没法有效解决,也能看出目前他还不太适配实际工作场景,职业潜力还需要花很多功夫去挖掘和提升,得好好补补基础、多积累实践经验才行。`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否应该显示评价内容
|
||||
const shouldShowEvaluation = () => {
|
||||
return selectedItem === "面试初体验" ||
|
||||
selectedItem === "未来的自己" ||
|
||||
selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
selectedItem === "第三次线下面试模拟";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="interview-rating-wrapper">
|
||||
{/* 视频播放器区域 */}
|
||||
<div className="interview-rating-video-section">
|
||||
<div className="interview-rating-header">
|
||||
<span className="interview-rating-header-title">
|
||||
{selectedItem}
|
||||
</span>
|
||||
</div>
|
||||
<div className="interview-rating-video">
|
||||
{isLockedItem() ? (
|
||||
<div className="locked-video-container" style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 背景图片 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundImage: `url(${
|
||||
selectedItem === "第一次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第一次线下面试模拟.jpg"
|
||||
: selectedItem === "第二次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第二次线下面试模拟.jpg"
|
||||
: "/src/assets/images/InterviewSimulationPage/第三次线下面试模拟.jpg"
|
||||
})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
filter: 'blur(20px)',
|
||||
transform: 'scale(1.1)'
|
||||
}} />
|
||||
|
||||
{/* 半透明遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||
}} />
|
||||
|
||||
{/* 锁图标和文字 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '16px'
|
||||
}}>
|
||||
<img
|
||||
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
|
||||
alt="lock"
|
||||
style={{ width: '280px', height: '280px' }}
|
||||
/>
|
||||
<span style={{
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
DEMO演示,非学员无查看权限
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<video src={getVideoUrl()} controls></video>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 评价内容区域 - 仅在特定项目时显示 */}
|
||||
{shouldShowEvaluation() && (
|
||||
<>
|
||||
{/* 面试评分区域 */}
|
||||
<div className="interview-evaluation-charts-wrapper">
|
||||
<div className="interview-rating-header">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5uY7Ek50.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '10px' }} />
|
||||
<span className="interview-rating-header-title">面试评分</span>
|
||||
</div>
|
||||
<div className="charts-content">
|
||||
<div className="charts-content-top">
|
||||
<ScoreChart className="score-chart" value={getEvaluationData().totalScore} />
|
||||
<div className="score-info">
|
||||
<div className="score-info-item item1">
|
||||
<p className="score-info-item-title">
|
||||
专业能力评分
|
||||
<span className="score-info-item-value">{getEvaluationData().professionalScore}</span>
|
||||
</p>
|
||||
<p className="score-info-line">
|
||||
<i
|
||||
style={{
|
||||
width: `${(getEvaluationData().professionalScore / 60) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<span className="score-info-line-total-value">60</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="score-info-item item2">
|
||||
<p className="score-info-item-title">
|
||||
现场表现评分
|
||||
<span className="score-info-item-value">{getEvaluationData().performanceScore}</span>
|
||||
</p>
|
||||
<p className="score-info-line">
|
||||
<i
|
||||
style={{
|
||||
width: `${(getEvaluationData().performanceScore / 40) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<span className="score-info-line-total-value">40</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="charts-content-bottom">
|
||||
<RadarChart
|
||||
className="radar-chart"
|
||||
data={getEvaluationData().radarData}
|
||||
indicator={[
|
||||
{ name: "基础知识\n掌握水平", max: 10 },
|
||||
{ name: "产业链\n认知程度", max: 10 },
|
||||
{ name: "企业生产\n体系了解", max: 10 },
|
||||
{ name: "典型问题\n解决能力", max: 10 },
|
||||
{ name: "岗位职责\n理解程度", max: 10 },
|
||||
{ name: "项目经历\n丰富程度", max: 10 },
|
||||
]}
|
||||
/>
|
||||
<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="#E8F5E9"
|
||||
areaColor="#C8E6C9"
|
||||
areaBorderColor="#66BB6A"
|
||||
isGreenTheme={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 面试评价三板块区域 */}
|
||||
<div className="interview-evaluation-sections">
|
||||
{/* 专业能力板块 */}
|
||||
<div className="evaluation-section">
|
||||
<div className="section-header">
|
||||
<div className="section-icon professional-icon">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5vlvUj2X.png" alt="专业能力" />
|
||||
</div>
|
||||
<h3 className="section-title">专业能力</h3>
|
||||
<div className="section-score">
|
||||
<span className="score-label">得分</span>
|
||||
<span className="score-value">{getEvaluationData().professionalScore}</span>
|
||||
<span className="score-total">/60</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{(() => {
|
||||
const content = getEvaluationData().content;
|
||||
const sections = content.split('#');
|
||||
if (sections.length > 1) {
|
||||
const professionalContent = sections[1].split('\n\n');
|
||||
return professionalContent.slice(1, -1).join('\n\n');
|
||||
}
|
||||
return '';
|
||||
})()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 现场表现力板块 */}
|
||||
<div className="evaluation-section">
|
||||
<div className="section-header">
|
||||
<div className="section-icon performance-icon">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5uY7Ek50.png" alt="现场表现力" />
|
||||
</div>
|
||||
<h3 className="section-title">现场表现力</h3>
|
||||
<div className="section-score">
|
||||
<span className="score-label">得分</span>
|
||||
<span className="score-value">{getEvaluationData().performanceScore}</span>
|
||||
<span className="score-total">/40</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{getEvaluationData().content.split('# 现场表现力')[1].split('# 综合评价')[0].trim()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 综合评价板块 */}
|
||||
<div className="evaluation-section comprehensive-section">
|
||||
<div className="section-header">
|
||||
|
||||
<h3 className="section-title">综合评价</h3>
|
||||
<div className="section-badge">
|
||||
{getEvaluationData().totalScore >= 80 ? '优秀' :
|
||||
getEvaluationData().totalScore >= 60 ? '良好' :
|
||||
getEvaluationData().totalScore >= 40 ? '及格' : '需努力'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{getEvaluationData().content.split('# 综合评价')[1].trim()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,294 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import ScoreChart from "../ScoreChart";
|
||||
import RadarChart from "../RadarChart";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ selectedItem = "面试初体验" }) => {
|
||||
// 根据选中项目获取对应的视频URL
|
||||
const getVideoUrl = () => {
|
||||
switch(selectedItem) {
|
||||
case "面试初体验":
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_ago.mov";
|
||||
case "未来的自己":
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_later.mov";
|
||||
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 "";
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否是锁定的面试模拟项目
|
||||
const isLockedItem = () => {
|
||||
return selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
selectedItem === "第三次线下面试模拟";
|
||||
};
|
||||
|
||||
// 根据选中项目获取评价数据
|
||||
const getEvaluationData = () => {
|
||||
// 所有评分数据改为0,评价改为空
|
||||
return {
|
||||
totalScore: 0,
|
||||
professionalScore: 0,
|
||||
performanceScore: 0,
|
||||
radarData: [0, 0, 0, 0, 0, 0],
|
||||
radarData2: [0, 0, 0, 0],
|
||||
title: "面试评价",
|
||||
content: `# 专业能力
|
||||
|
||||
暂无评价数据
|
||||
|
||||
# 现场表现力
|
||||
|
||||
暂无评价数据
|
||||
|
||||
# 综合评价
|
||||
|
||||
暂无评价数据`
|
||||
};
|
||||
};
|
||||
|
||||
// 判断是否应该显示评价内容
|
||||
const shouldShowEvaluation = () => {
|
||||
return selectedItem === "面试初体验" ||
|
||||
selectedItem === "未来的自己" ||
|
||||
selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
selectedItem === "第三次线下面试模拟";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="interview-rating-wrapper">
|
||||
{/* 视频播放器区域 */}
|
||||
<div className="interview-rating-video-section">
|
||||
<div className="interview-rating-header">
|
||||
<span className="interview-rating-header-title">
|
||||
{selectedItem}
|
||||
</span>
|
||||
</div>
|
||||
<div className="interview-rating-video">
|
||||
{isLockedItem() ? (
|
||||
<div className="locked-video-container" style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 背景图片 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundImage: `url(${
|
||||
selectedItem === "第一次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第一次线下面试模拟.jpg"
|
||||
: selectedItem === "第二次线下面试模拟"
|
||||
? "/src/assets/images/InterviewSimulationPage/第二次线下面试模拟.jpg"
|
||||
: "/src/assets/images/InterviewSimulationPage/第三次线下面试模拟.jpg"
|
||||
})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
filter: 'blur(20px)',
|
||||
transform: 'scale(1.1)'
|
||||
}} />
|
||||
|
||||
{/* 半透明遮罩 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||
}} />
|
||||
|
||||
{/* 锁图标和文字 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '16px'
|
||||
}}>
|
||||
<img
|
||||
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
|
||||
alt="lock"
|
||||
style={{ width: '280px', height: '280px' }}
|
||||
/>
|
||||
<span style={{
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
DEMO演示,非学员无查看权限
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<video src={getVideoUrl()} controls></video>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 评价内容区域 - 仅在特定项目时显示 */}
|
||||
{shouldShowEvaluation() && (
|
||||
<>
|
||||
{/* 面试评分区域 */}
|
||||
<div className="interview-evaluation-charts-wrapper">
|
||||
<div className="interview-rating-header">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5uY7Ek50.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '10px' }} />
|
||||
<span className="interview-rating-header-title">面试评分</span>
|
||||
</div>
|
||||
<div className="charts-content">
|
||||
<div className="charts-content-top">
|
||||
<ScoreChart className="score-chart" value={getEvaluationData().totalScore} />
|
||||
<div className="score-info">
|
||||
<div className="score-info-item item1">
|
||||
<p className="score-info-item-title">
|
||||
专业能力评分
|
||||
<span className="score-info-item-value">{getEvaluationData().professionalScore}</span>
|
||||
</p>
|
||||
<p className="score-info-line">
|
||||
<i
|
||||
style={{
|
||||
width: `${(getEvaluationData().professionalScore / 60) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<span className="score-info-line-total-value">60</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="score-info-item item2">
|
||||
<p className="score-info-item-title">
|
||||
现场表现评分
|
||||
<span className="score-info-item-value">{getEvaluationData().performanceScore}</span>
|
||||
</p>
|
||||
<p className="score-info-line">
|
||||
<i
|
||||
style={{
|
||||
width: `${(getEvaluationData().performanceScore / 40) * 100}%`,
|
||||
}}
|
||||
/>
|
||||
<span className="score-info-line-total-value">40</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="charts-content-bottom">
|
||||
<RadarChart
|
||||
className="radar-chart"
|
||||
data={getEvaluationData().radarData}
|
||||
indicator={[
|
||||
{ name: "基础知识\n掌握水平", max: 10 },
|
||||
{ name: "产业链\n认知程度", max: 10 },
|
||||
{ name: "企业生产\n体系了解", max: 10 },
|
||||
{ name: "典型问题\n解决能力", max: 10 },
|
||||
{ name: "岗位职责\n理解程度", max: 10 },
|
||||
{ name: "项目经历\n丰富程度", max: 10 },
|
||||
]}
|
||||
/>
|
||||
<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="#E8F5E9"
|
||||
areaColor="#C8E6C9"
|
||||
areaBorderColor="#66BB6A"
|
||||
isGreenTheme={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 面试评价三板块区域 */}
|
||||
<div className="interview-evaluation-sections">
|
||||
{/* 专业能力板块 */}
|
||||
<div className="evaluation-section">
|
||||
<div className="section-header">
|
||||
<div className="section-icon professional-icon">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5vlvUj2X.png" alt="专业能力" />
|
||||
</div>
|
||||
<h3 className="section-title">专业能力</h3>
|
||||
<div className="section-score">
|
||||
<span className="score-label">得分</span>
|
||||
<span className="score-value">{getEvaluationData().professionalScore}</span>
|
||||
<span className="score-total">/60</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{(() => {
|
||||
const content = getEvaluationData().content;
|
||||
const sections = content.split('#');
|
||||
if (sections.length > 1) {
|
||||
const professionalContent = sections[1].split('\n\n');
|
||||
return professionalContent.slice(1, -1).join('\n\n');
|
||||
}
|
||||
return '';
|
||||
})()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 现场表现力板块 */}
|
||||
<div className="evaluation-section">
|
||||
<div className="section-header">
|
||||
<div className="section-icon performance-icon">
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5uY7Ek50.png" alt="现场表现力" />
|
||||
</div>
|
||||
<h3 className="section-title">现场表现力</h3>
|
||||
<div className="section-score">
|
||||
<span className="score-label">得分</span>
|
||||
<span className="score-value">{getEvaluationData().performanceScore}</span>
|
||||
<span className="score-total">/40</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{getEvaluationData().content.split('# 现场表现力')[1].split('# 综合评价')[0].trim()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 综合评价板块 */}
|
||||
<div className="evaluation-section comprehensive-section">
|
||||
<div className="section-header">
|
||||
|
||||
<h3 className="section-title">综合评价</h3>
|
||||
<div className="section-badge">
|
||||
{getEvaluationData().totalScore >= 80 ? '优秀' :
|
||||
getEvaluationData().totalScore >= 60 ? '良好' :
|
||||
getEvaluationData().totalScore >= 40 ? '及格' : '需努力'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-content">
|
||||
<ReactMarkdown>
|
||||
{getEvaluationData().content.split('# 综合评价')[1].trim()}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -39,21 +39,6 @@ export default ({ onItemSelect }) => {
|
||||
>
|
||||
<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">
|
||||
初次接触面试环境,体验真实面试流程,了解自身在面试中的基本表现和待提升的方面
|
||||
@@ -71,21 +56,6 @@ export default ({ onItemSelect }) => {
|
||||
>
|
||||
<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">
|
||||
经过系统训练后的面试表现,展示个人成长和进步,体现出更强的职场竞争力和专业素养
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { useState } from "react";
|
||||
import { Timeline } from "@arco-design/web-react";
|
||||
import IconFont from "@/components/IconFont";
|
||||
import "./index.css";
|
||||
|
||||
const TimelineItem = Timeline.Item;
|
||||
|
||||
export default ({ onItemSelect }) => {
|
||||
const [selectedItem, setSelectedItem] = useState("面试初体验");
|
||||
|
||||
const handleItemClick = (itemName) => {
|
||||
setSelectedItem(itemName);
|
||||
onItemSelect && onItemSelect(itemName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mock-interview-wrapper">
|
||||
<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">
|
||||
<i className="mock-interview-item-title-icon" />
|
||||
<span>懵懂初试</span>
|
||||
</p>
|
||||
<Timeline>
|
||||
<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>
|
||||
面试初体验
|
||||
<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>
|
||||
</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>
|
||||
未来的自己
|
||||
<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>
|
||||
</div>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
</li>
|
||||
<li className="mock-interview-item">
|
||||
<p className="mock-interview-item-title">
|
||||
<i className="mock-interview-item-title-icon" />
|
||||
<span>有备而战</span>
|
||||
</p>
|
||||
<Timeline>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,9 +6,9 @@ import "./index.css";
|
||||
export default ({ locked = true }) => {
|
||||
return (
|
||||
<div className="curved-employment-wrapper">
|
||||
<img src={CHARTIMG} />
|
||||
<img src={CHARTIMG} style={locked ? { filter: 'blur(10px)' } : {}} />
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" showBackground={false} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import CHARTIMG from "@/assets/images/JobStrategyDetailPage/bar_chart.png";
|
||||
import Locked from "@/components/Locked";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ locked = true }) => {
|
||||
return (
|
||||
<div className="curved-employment-wrapper">
|
||||
<img src={CHARTIMG} />
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -343,7 +343,7 @@ export default ({ locked = false }) => {
|
||||
>
|
||||
<div className="target-position-wrapper">
|
||||
<div className="target-position-content">
|
||||
<div className="batch-icon">
|
||||
<div className="batch-icon" style={locked ? { filter: 'blur(10px)' } : {}}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第一批次
|
||||
<Tooltip content="通过培训后能直接上岗的岗位,入职成功率最高。">
|
||||
@@ -392,7 +392,7 @@ export default ({ locked = false }) => {
|
||||
</div>
|
||||
|
||||
{/* 第一批次 */}
|
||||
<div className="batch-content batch-content1" ref={batch1Ref}>
|
||||
<div className="batch-content batch-content1" ref={batch1Ref} style={locked ? { filter: 'blur(10px)' } : {}}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch1.map(p => `batch1-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
@@ -409,7 +409,7 @@ export default ({ locked = false }) => {
|
||||
</div>
|
||||
|
||||
{/* 第二批次 */}
|
||||
<div className="batch-content batch-content2" ref={batch2Ref}>
|
||||
<div className="batch-content batch-content2" ref={batch2Ref} style={locked ? { filter: 'blur(10px)' } : {}}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch2.map(p => `batch2-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
@@ -426,7 +426,7 @@ export default ({ locked = false }) => {
|
||||
</div>
|
||||
|
||||
{/* 第三批次 */}
|
||||
<div className="batch-content batch-content3" ref={batch3Ref}>
|
||||
<div className="batch-content batch-content3" ref={batch3Ref} style={locked ? { filter: 'blur(10px)' } : {}}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch3.map(p => `batch3-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
@@ -443,7 +443,7 @@ export default ({ locked = false }) => {
|
||||
</div>
|
||||
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" showBackground={false} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,654 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Modal, Message, Tooltip } from "@arco-design/web-react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragOverlay,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
horizontalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import Locked from "@/components/Locked";
|
||||
import jobLevelData from "@/data/joblevel.json";
|
||||
import "./index.css";
|
||||
|
||||
// 可排序的岗位组件
|
||||
const SortablePosition = ({ id, position, getPositionAvatar }) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: 'grab',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="avatar-wrapper"
|
||||
>
|
||||
<div className="student-avatar">
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(position)}
|
||||
draggable={false}
|
||||
style={{ userSelect: 'none', WebkitUserSelect: 'none', pointerEvents: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
<span className="student-name" style={{ userSelect: 'none', WebkitUserSelect: 'none' }}>{position}</span>
|
||||
<div className="position-tooltip">{position}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ locked = false }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const batch1Ref = useRef(null);
|
||||
const batch2Ref = useRef(null);
|
||||
const batch3Ref = useRef(null);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
const [pendingNavigation, setPendingNavigation] = useState(null);
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
|
||||
// 处理拖拽开始
|
||||
const handleDragStart = (event) => {
|
||||
const { active } = event;
|
||||
setActiveId(active.id);
|
||||
};
|
||||
|
||||
// 处理拖拽结束
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
setActiveId(null);
|
||||
|
||||
if (!over) return;
|
||||
|
||||
const activePosition = active.id.split('-').slice(1).join('-');
|
||||
const activeBatch = active.id.split('-')[0];
|
||||
|
||||
// 确定目标批次
|
||||
let targetBatch = over.id.split('-')[0];
|
||||
let targetPosition = over.id.split('-').slice(1).join('-');
|
||||
|
||||
// 如果目标和源相同,不做任何操作
|
||||
if (active.id === over.id) return;
|
||||
|
||||
setBatchPositions((prev) => {
|
||||
const newPositions = { ...prev };
|
||||
|
||||
// 如果是同一批次内的移动
|
||||
if (activeBatch === targetBatch) {
|
||||
const batch = [...newPositions[activeBatch]];
|
||||
const activeIndex = batch.indexOf(activePosition);
|
||||
const overIndex = batch.indexOf(targetPosition);
|
||||
|
||||
if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
|
||||
// 移除原位置
|
||||
batch.splice(activeIndex, 1);
|
||||
// 计算新位置索引
|
||||
const newOverIndex = activeIndex < overIndex ? overIndex - 1 : overIndex;
|
||||
// 插入到新位置
|
||||
batch.splice(newOverIndex, 0, activePosition);
|
||||
newPositions[activeBatch] = batch;
|
||||
}
|
||||
} else {
|
||||
// 跨批次移动
|
||||
// 从原批次删除
|
||||
const sourceBatch = [...newPositions[activeBatch]];
|
||||
const activeIndex = sourceBatch.indexOf(activePosition);
|
||||
if (activeIndex !== -1) {
|
||||
sourceBatch.splice(activeIndex, 1);
|
||||
newPositions[activeBatch] = sourceBatch;
|
||||
}
|
||||
|
||||
// 添加到目标批次
|
||||
const targetBatchArray = [...newPositions[targetBatch]];
|
||||
const overIndex = targetBatchArray.indexOf(targetPosition);
|
||||
if (overIndex !== -1) {
|
||||
// 插入到特定位置
|
||||
targetBatchArray.splice(overIndex, 0, activePosition);
|
||||
} else {
|
||||
// 如果目标批次为空或找不到目标位置,添加到末尾
|
||||
targetBatchArray.push(activePosition);
|
||||
}
|
||||
newPositions[targetBatch] = targetBatchArray;
|
||||
}
|
||||
|
||||
return newPositions;
|
||||
});
|
||||
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
// 处理拖拽到其他批次 - 仅用于预览,不实际移动
|
||||
const handleDragOver = (event) => {
|
||||
// 空函数 - 我们只在dragEnd时处理实际的移动
|
||||
};
|
||||
|
||||
// 监听路由变化
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [hasChanges]);
|
||||
|
||||
// 拦截导航 - 监听所有可能的页面切换
|
||||
useEffect(() => {
|
||||
if (!hasChanges) return;
|
||||
|
||||
const handleNavigation = (e) => {
|
||||
// 如果点击的是弹窗内的元素,不拦截
|
||||
if (e.target.closest('.arco-modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是链接点击
|
||||
const link = e.target.closest('a') || (e.target.tagName === 'A' ? e.target : null);
|
||||
const button = e.target.closest('button') || (e.target.tagName === 'BUTTON' ? e.target : null);
|
||||
|
||||
// 检查是否是导航相关的元素
|
||||
if (link || (button && (button.textContent?.includes('返回') || button.onclick))) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowSaveModal(true);
|
||||
|
||||
if (link) {
|
||||
setPendingNavigation(link.href);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听点击事件(捕获阶段)
|
||||
document.addEventListener('click', handleNavigation, true);
|
||||
|
||||
// 监听浏览器后退/前进
|
||||
const handlePopState = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
setShowSaveModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleNavigation, true);
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
};
|
||||
}, [hasChanges]);
|
||||
|
||||
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 initialBatchPositions = {
|
||||
batch1: [
|
||||
"二次元周边店店员",
|
||||
"会展执行助理",
|
||||
"会展讲解员",
|
||||
"会展营销",
|
||||
"商业会展执行专员",
|
||||
"景区运营专员",
|
||||
"文旅运营总监助理",
|
||||
"品牌策划运营专员",
|
||||
"品牌推广专员",
|
||||
"ip运营",
|
||||
"文创产品设计师助理",
|
||||
"新媒体运营专员",
|
||||
"网络运营专员",
|
||||
"社群运营",
|
||||
"直播助理"
|
||||
],
|
||||
batch2: [
|
||||
"宠物店店长",
|
||||
"宠物营养师",
|
||||
"二次元周边选品专员",
|
||||
"二次元周边店店长",
|
||||
"会展策划师",
|
||||
"漫展策划师",
|
||||
"活动执行",
|
||||
"活动策划师",
|
||||
"酒店运营专员",
|
||||
"餐厅运营经理",
|
||||
"露营地运营专员",
|
||||
"旅游规划师",
|
||||
"文旅项目投资拓展管培生",
|
||||
"民宿管家",
|
||||
"民宿客房管家",
|
||||
"民宿运营专员",
|
||||
"品牌公关",
|
||||
"ip运营总监助理",
|
||||
"品牌公关管培生",
|
||||
"直播中控",
|
||||
"SEO专员",
|
||||
"SEM专员",
|
||||
"赛事经纪"
|
||||
],
|
||||
batch3: [
|
||||
"酒店餐饮主管",
|
||||
"客房经理",
|
||||
"酒店大堂副理",
|
||||
"旅游计调专员",
|
||||
"文创产品策划师",
|
||||
"文创产品设计师",
|
||||
"赛事礼仪",
|
||||
"赛事编辑",
|
||||
"艺人经纪人",
|
||||
"演出执行经理",
|
||||
"场馆运营人员"
|
||||
]
|
||||
};
|
||||
|
||||
const [batchPositions, setBatchPositions] = useState(initialBatchPositions);
|
||||
|
||||
// 拖拽传感器设置
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 8,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 获取所有岗位ID
|
||||
const getAllPositionIds = () => {
|
||||
const ids = [];
|
||||
Object.entries(batchPositions).forEach(([batch, positions]) => {
|
||||
positions.forEach(position => {
|
||||
ids.push(`${batch}-${position}`);
|
||||
});
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ userSelect: 'none', WebkitUserSelect: 'none', MozUserSelect: 'none', msUserSelect: 'none' }}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
<div className="target-position-wrapper">
|
||||
<div className="target-position-content">
|
||||
<div className="batch-icon">
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第一批次
|
||||
<Tooltip content="通过培训后能直接上岗的岗位,入职成功率最高。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第二批次
|
||||
<Tooltip content="需积累一定工作经验后可争取的晋升岗位方向。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第三批次
|
||||
<Tooltip content="需长期经验和能力沉淀,可作为学员的终极职业目标。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 第一批次 */}
|
||||
<div className="batch-content batch-content1" ref={batch1Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch1.map(p => `batch1-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch1.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch1-${position}`}
|
||||
id={`batch1-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第二批次 */}
|
||||
<div className="batch-content batch-content2" ref={batch2Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch2.map(p => `batch2-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch2.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch2-${position}`}
|
||||
id={`batch2-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第三批次 */}
|
||||
<div className="batch-content batch-content3" ref={batch3Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch3.map(p => `batch3-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch3.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch3-${position}`}
|
||||
id={`batch3-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 拖拽覆盖层 */}
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'grabbing',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
border: '2px solid #ffffff'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(activeId.split('-').slice(1).join('-'))}
|
||||
draggable={false}
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
|
||||
{/* 保存提示模态框 */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: '#1d2129',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15h-2v-2h2v2zm0-4h-2V5h2v6z" fill="#2c7fff"/>
|
||||
</svg>
|
||||
保存更改
|
||||
</div>
|
||||
}
|
||||
visible={showSaveModal}
|
||||
onCancel={() => {
|
||||
setShowSaveModal(false);
|
||||
setPendingNavigation(null);
|
||||
// 只关闭弹窗,保留hasChanges状态,下次点击返回还会弹出
|
||||
}}
|
||||
footer={[
|
||||
<button
|
||||
key="cancel"
|
||||
className="arco-btn arco-btn-secondary"
|
||||
onClick={() => {
|
||||
setShowSaveModal(false);
|
||||
setHasChanges(false);
|
||||
setPendingNavigation(null);
|
||||
navigate('/job-strategy'); // 返回定制求职策略页面
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 20px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e5e6eb',
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#4e5969',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f7f8fa';
|
||||
e.currentTarget.style.borderColor = '#c9cdd4';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#ffffff';
|
||||
e.currentTarget.style.borderColor = '#e5e6eb';
|
||||
}}
|
||||
>
|
||||
放弃更改
|
||||
</button>,
|
||||
<div
|
||||
key="save-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'inline-block'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'save-tooltip';
|
||||
tooltip.textContent = '非导师和学生本人无修改权限';
|
||||
tooltip.style.cssText = `
|
||||
position: absolute;
|
||||
bottom: 120%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, #1d2129 0%, #2e3440 100%);
|
||||
color: #ffffff;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(5px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 添加小箭头
|
||||
const arrow = document.createElement('div');
|
||||
arrow.style.cssText = `
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: #2e3440 transparent transparent transparent;
|
||||
`;
|
||||
tooltip.appendChild(arrow);
|
||||
e.currentTarget.appendChild(tooltip);
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const tooltip = e.currentTarget.querySelector('.save-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.remove();
|
||||
}
|
||||
const style = document.querySelector('style');
|
||||
if (style && style.textContent.includes('fadeIn')) {
|
||||
style.remove();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="arco-btn arco-btn-primary"
|
||||
disabled
|
||||
style={{
|
||||
padding: '8px 24px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: '#e5e6eb',
|
||||
color: '#86909c',
|
||||
cursor: 'not-allowed',
|
||||
border: 'none',
|
||||
opacity: '0.6',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
保存更改
|
||||
</button>
|
||||
</div>
|
||||
]}
|
||||
style={{
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: '12px'
|
||||
}}>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ marginTop: '2px', flexShrink: 0 }}>
|
||||
<path d="M9 13h2v2H9v-2zm0-8h2v6H9V5zm.99-5C4.47 0 0 4.48 0 10s4.47 10 9.99 10C15.52 20 20 15.52 20 10S15.52 0 9.99 0zM10 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" fill="#ff7d00"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style={{ margin: 0, fontWeight: '500', color: '#1d2129', marginBottom: '8px' }}>
|
||||
您对岗位顺序进行了修改
|
||||
</p>
|
||||
<p style={{ margin: 0, fontSize: '13px', color: '#86909c' }}>
|
||||
离开此页面前,是否要保存您的更改?未保存的更改将会丢失。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -54,8 +54,8 @@ const JobStrategyDetailPage = () => {
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="job-strategy-detail-content">
|
||||
{activeItem === "1" && <TargetPosition locked={false} />}
|
||||
{activeItem === "2" && <CurvedEmployment locked={false} />}
|
||||
{activeItem === "1" && <TargetPosition locked={true} />}
|
||||
{activeItem === "2" && <CurvedEmployment locked={true} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,7 @@ const JobStrategyPage = () => {
|
||||
return (
|
||||
<div className="job-strategy-page">
|
||||
<CoursesVideoPlayer
|
||||
isLock
|
||||
backgroundImage="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuWolCCOlOCz.jpg"
|
||||
isLock
|
||||
/>
|
||||
<LiveSummary showBtn />
|
||||
</div>
|
||||
|
||||
17
src/pages/JobStrategyPage/index.jsx.backup_20251017_192255
Normal file
17
src/pages/JobStrategyPage/index.jsx.backup_20251017_192255
Normal file
@@ -0,0 +1,17 @@
|
||||
import CoursesVideoPlayer from "@/components/CoursesVideoPlayer";
|
||||
import LiveSummary from "@/components/LiveSummary";
|
||||
import "./index.css";
|
||||
|
||||
const JobStrategyPage = () => {
|
||||
return (
|
||||
<div className="job-strategy-page">
|
||||
<CoursesVideoPlayer
|
||||
isLock
|
||||
backgroundImage="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuWolCCOlOCz.jpg"
|
||||
/>
|
||||
<LiveSummary showBtn />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobStrategyPage;
|
||||
Reference in New Issue
Block a user