feat: 完整的教务系统功能更新
- 添加面试模拟功能:视频播放、评分展示、滑动交互 - 实现1v1求职策略:导师信息更新、直播纪要功能 - 完善项目库和简历面试功能:添加详细的mock数据 - 更新课后作业页面:灰度显示、课程链接 - 个人档案和主页数据:万圆的完整个人信息 - 修复各类语法错误和数据结构问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ export default ({ className = "", isLock = false }) => {
|
||||
src="//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"
|
||||
/>
|
||||
</Avatar>
|
||||
<span className="teacher-name">赵老师</span>
|
||||
<span className="teacher-name">魏立慧</span>
|
||||
<span className="teacher-tag">教育体系认知</span>
|
||||
<div className="living-data">
|
||||
<div className="living-data-item">
|
||||
@@ -54,7 +54,7 @@ export default ({ className = "", isLock = false }) => {
|
||||
</div>
|
||||
<div className="living-data-item">
|
||||
<span>观看</span>
|
||||
<span>300000人</span>
|
||||
<span>3000人</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,17 +63,16 @@ export default ({ className = "", isLock = false }) => {
|
||||
<div className="courses-video-player-teacher-introduce">
|
||||
<p className="title icon1">导师介绍</p>
|
||||
<p className="teacher-introduce">
|
||||
这是一段导师介绍这是一段导师介绍这是一段导师介绍这是一段导师介绍这是一段导师介绍这是
|
||||
一段导师介绍这是一段导师介绍这是一段导师介绍这是一段导师介绍这是
|
||||
一段导师介绍这是一段导师介绍这是一段导师介绍这是一段导师介绍
|
||||
企业资深一线HR,专注于为求职者提供一对一的个性化指导。通过真实招聘视角,深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径,帮助学生快速提升求职竞争力。求职策略以实用落地为核心,注重互动交流与角色定位,让学员在轻松氛围中获得直击痛点的求职策略。
|
||||
</p>
|
||||
</div>
|
||||
<div className="courses-video-player-teacher-tags">
|
||||
<p className="title icon2">教师专长</p>
|
||||
<ul className="teacher-tags">
|
||||
<li>一般般</li>
|
||||
<li>一般般</li>
|
||||
<li>一般般</li>
|
||||
<li>深谙用人逻辑</li>
|
||||
<li>擅长挖掘优势</li>
|
||||
<li>沟通真诚自然</li>
|
||||
<li>点评直击要害</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
74
src/components/FileIcon/index.css
Normal file
74
src/components/FileIcon/index.css
Normal file
@@ -0,0 +1,74 @@
|
||||
.file-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.file-icon-fold {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 12px 12px 0;
|
||||
border-color: transparent #fff transparent transparent;
|
||||
}
|
||||
|
||||
.file-icon-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* PDF - 红色 */
|
||||
.file-icon-pdf {
|
||||
background: linear-gradient(135deg, #ff4d4d 0%, #cc0000 100%);
|
||||
}
|
||||
|
||||
.file-icon-pdf .file-icon-fold {
|
||||
border-right-color: #ffcccc;
|
||||
}
|
||||
|
||||
/* Word文档 - 蓝色 */
|
||||
.file-icon-doc {
|
||||
background: linear-gradient(135deg, #2196f3 0%, #1565c0 100%);
|
||||
}
|
||||
|
||||
.file-icon-doc .file-icon-fold {
|
||||
border-right-color: #bbdefb;
|
||||
}
|
||||
|
||||
/* PPT - 橙色 */
|
||||
.file-icon-ppt {
|
||||
background: linear-gradient(135deg, #ff9800 0%, #e65100 100%);
|
||||
}
|
||||
|
||||
.file-icon-ppt .file-icon-fold {
|
||||
border-right-color: #ffe0b2;
|
||||
}
|
||||
|
||||
/* Excel - 绿色 */
|
||||
.file-icon-excel {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
|
||||
}
|
||||
|
||||
.file-icon-excel .file-icon-fold {
|
||||
border-right-color: #c8e6c9;
|
||||
}
|
||||
|
||||
/* 默认 - 灰色 */
|
||||
.file-icon-default {
|
||||
background: linear-gradient(135deg, #9e9e9e 0%, #616161 100%);
|
||||
}
|
||||
|
||||
.file-icon-default .file-icon-fold {
|
||||
border-right-color: #e0e0e0;
|
||||
}
|
||||
48
src/components/FileIcon/index.jsx
Normal file
48
src/components/FileIcon/index.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import "./index.css";
|
||||
|
||||
const FileIcon = ({ type = "doc" }) => {
|
||||
const getIconClass = () => {
|
||||
switch(type) {
|
||||
case 'pdf':
|
||||
return 'file-icon-pdf';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return 'file-icon-doc';
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return 'file-icon-ppt';
|
||||
case 'excel':
|
||||
case 'xlsx':
|
||||
return 'file-icon-excel';
|
||||
default:
|
||||
return 'file-icon-default';
|
||||
}
|
||||
};
|
||||
|
||||
const getIconText = () => {
|
||||
switch(type) {
|
||||
case 'pdf':
|
||||
return 'PDF';
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return 'DOC';
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return 'PPT';
|
||||
case 'excel':
|
||||
case 'xlsx':
|
||||
return 'XLS';
|
||||
default:
|
||||
return 'FILE';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`file-icon ${getIconClass()}`}>
|
||||
<div className="file-icon-fold"></div>
|
||||
<div className="file-icon-text">{getIconText()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileIcon;
|
||||
@@ -1,43 +1,52 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { mockData } from "@/data/mockData";
|
||||
import "./index.css";
|
||||
|
||||
const LiveSummary = ({ className = "", showBtn = false, isLiving = true }) => {
|
||||
const navigate = useNavigate();
|
||||
const { jobStrategyNotes } = mockData;
|
||||
|
||||
const handleClickBtn = () => {
|
||||
navigate("/job-strategy-detail");
|
||||
};
|
||||
|
||||
// 根据type分组keyPoints
|
||||
const groupedPoints = jobStrategyNotes.keyPoints.reduce((acc, point) => {
|
||||
const typeMap = {
|
||||
strategy: "策略建议",
|
||||
advice: "专家建议",
|
||||
technique: "核心技巧",
|
||||
timeline: "时间规划",
|
||||
qa: "答疑解惑"
|
||||
};
|
||||
|
||||
const groupName = typeMap[point.type] || point.type;
|
||||
|
||||
if (!acc[groupName]) {
|
||||
acc[groupName] = [];
|
||||
}
|
||||
acc[groupName].push(point);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<div className={`${className} live-summary-wrapper`}>
|
||||
<p className={`live-summary-title ${!isLiving && "not-living"}`}>
|
||||
直播摘要
|
||||
{jobStrategyNotes.title}
|
||||
</p>
|
||||
<ul className="live-summary-list">
|
||||
<li className="live-summary-item">
|
||||
<p className="live-summary-item-title">通用技巧</p>
|
||||
<ul className="live-summary-item-content-list">
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li className="live-summary-item">
|
||||
<p className="live-summary-item-title">通用技巧</p>
|
||||
<ul className="live-summary-item-content-list">
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li className="live-summary-item">
|
||||
<p className="live-summary-item-title">通用技巧</p>
|
||||
<ul className="live-summary-item-content-list">
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
<li className="live-summary-item-content-item">1. 通用技巧1</li>
|
||||
</ul>
|
||||
</li>
|
||||
{Object.entries(groupedPoints).slice(0, 3).map(([groupName, points]) => (
|
||||
<li className="live-summary-item" key={groupName}>
|
||||
<p className="live-summary-item-title">{groupName}</p>
|
||||
<ul className="live-summary-item-content-list">
|
||||
{points.slice(0, 3).map((point) => (
|
||||
<li className="live-summary-item-content-item" key={point.id}>
|
||||
<span style={{fontWeight: "600"}}>{point.time}</span> - {point.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{showBtn && (
|
||||
<div
|
||||
@@ -51,4 +60,4 @@ const LiveSummary = ({ className = "", showBtn = false, isLiving = true }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveSummary;
|
||||
export default LiveSummary;
|
||||
1971
src/data/mockData.js
1971
src/data/mockData.js
File diff suppressed because it is too large
Load Diff
3575
src/data/mockData.js.backup_before_csv
Normal file
3575
src/data/mockData.js.backup_before_csv
Normal file
File diff suppressed because it is too large
Load Diff
4362
src/data/mockData.js.backup_before_remove_iot
Normal file
4362
src/data/mockData.js.backup_before_remove_iot
Normal file
File diff suppressed because it is too large
Load Diff
17
src/mocks/dashboardMock.js
Normal file
17
src/mocks/dashboardMock.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { mockData } from "@/data/mockData";
|
||||
|
||||
// Mock Dashboard Statistics
|
||||
export const getDashboardStatisticsMock = () => {
|
||||
return {
|
||||
success: true,
|
||||
data: mockData.dashboardStatistics,
|
||||
};
|
||||
};
|
||||
|
||||
// Mock Profile Overview
|
||||
export const getProfileOverviewMock = () => {
|
||||
return {
|
||||
success: true,
|
||||
data: mockData.profileOverview,
|
||||
};
|
||||
};
|
||||
487
src/mocks/projectLibraryMock.js
Normal file
487
src/mocks/projectLibraryMock.js
Normal file
File diff suppressed because one or more lines are too long
5507
src/mocks/resumeInterviewMock.js
Normal file
5507
src/mocks/resumeInterviewMock.js
Normal file
File diff suppressed because it is too large
Load Diff
5145
src/mocks/resumeInterviewMock.js.backup
Normal file
5145
src/mocks/resumeInterviewMock.js.backup
Normal file
File diff suppressed because it is too large
Load Diff
6369
src/mocks/resumeInterviewMock.js.backup2
Normal file
6369
src/mocks/resumeInterviewMock.js.backup2
Normal file
File diff suppressed because it is too large
Load Diff
5638
src/mocks/resumeInterviewMock.js.backup3
Normal file
5638
src/mocks/resumeInterviewMock.js.backup3
Normal file
File diff suppressed because it is too large
Load Diff
7372
src/mocks/resumeInterviewMock.js.backup_final
Normal file
7372
src/mocks/resumeInterviewMock.js.backup_final
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import { useState, useCallback } from "react";
|
||||
import JobList from "@/pages/CompanyJobsPage/components/JobList";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import { getJobsList } from "@/services";
|
||||
import { mapJobList } from "@/utils/dataMapper";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
@@ -19,9 +19,9 @@ const CompanyJobsListPage = () => {
|
||||
isActive: true,
|
||||
});
|
||||
if (res.success) {
|
||||
const mappedJobs = mapJobList(res.data);
|
||||
// Mock数据已经是前端格式,不需要映射
|
||||
setJobs((prevList) => {
|
||||
const newList = [...prevList, ...mappedJobs];
|
||||
const newList = [...prevList, ...res.data];
|
||||
if (res.total === newList?.length) {
|
||||
setListHasMore(false);
|
||||
} else {
|
||||
|
||||
@@ -1,26 +1,40 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Input } from "@arco-design/web-react";
|
||||
import Modal from "@/components/Modal";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import FILEICON from "@/assets/images/CompanyJobsPage/file_icon.png";
|
||||
import ResumeInfoModal from "../ResumeInfoModal";
|
||||
import { getResumesList } from "@/services";
|
||||
import { getResumesList, submitResume, getPageData } from "@/services";
|
||||
import "./index.css";
|
||||
|
||||
const InputSearch = Input.Search;
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
export default ({ visible, onClose, data }) => {
|
||||
export default ({ visible, onClose, data, directToResume = false }) => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
const [resumeModalShow, setResumeModalShow] = useState(false);
|
||||
const [resumeModalShow, setResumeModalShow] = useState(directToResume);
|
||||
const [resumeInfoModalShow, setResumeInfoModalShow] = useState(false);
|
||||
const [resumeInfoData, setResumeInfoData] = useState(null);
|
||||
const [resumeList, setResumeList] = useState([]); // 简历列表
|
||||
const [listPage, setListPage] = useState(1);
|
||||
const [listHasMore, setListHasMore] = useState(true);
|
||||
|
||||
// 处理directToResume参数变化
|
||||
useEffect(() => {
|
||||
if (visible && directToResume) {
|
||||
setResumeModalShow(true);
|
||||
} else if (visible && !directToResume) {
|
||||
setResumeModalShow(false);
|
||||
}
|
||||
}, [visible, directToResume]);
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setResumeModalShow(false);
|
||||
setResumeList([]); // 清空简历列表
|
||||
setListPage(1); // 重置分页
|
||||
setListHasMore(true); // 重置加载更多状态
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -28,7 +42,7 @@ export default ({ visible, onClose, data }) => {
|
||||
const res = await getResumesList({
|
||||
page: listPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
studentId: studentInfo?.id,
|
||||
studentId: studentInfo?.id
|
||||
});
|
||||
if (res.success) {
|
||||
setResumeList((prevList) => {
|
||||
@@ -55,16 +69,87 @@ export default ({ visible, onClose, data }) => {
|
||||
};
|
||||
|
||||
// 选择简历投递
|
||||
const userResumesClick = (item) => {
|
||||
// todo
|
||||
console.log(item);
|
||||
const userResumesClick = async (item) => {
|
||||
try {
|
||||
// 调用投递服务
|
||||
const result = await submitResume({
|
||||
resumeId: item.id,
|
||||
jobId: data?.id,
|
||||
studentId: studentInfo?.id,
|
||||
resumeTitle: item.title,
|
||||
jobPosition: data?.position,
|
||||
company: data?.company
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// 投递成功,显示成功提示
|
||||
toast.success(`简历"${item.title}"投递成功!`);
|
||||
|
||||
// 关闭模态框
|
||||
handleCloseModal();
|
||||
|
||||
// 输出投递成功信息
|
||||
console.log('投递成功', {
|
||||
applicationId: result.data.applicationId,
|
||||
resumeId: item.id,
|
||||
jobId: data?.id,
|
||||
resumeTitle: item.title,
|
||||
jobPosition: data?.position,
|
||||
submittedAt: result.data.submittedAt
|
||||
});
|
||||
} else {
|
||||
toast.error(result.message || '投递失败,请重试');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
toast.error('投递失败,请重试');
|
||||
console.error('投递失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 点击简历详情
|
||||
const userResumesBtnClick = (e, item) => {
|
||||
e.stopPropagation();
|
||||
setResumeInfoModalShow(true);
|
||||
};
|
||||
const userResumesBtnClick = async (e, item) => {
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
// 获取岗位与面试题页面的数据
|
||||
const pageDataResponse = await getPageData();
|
||||
|
||||
if (pageDataResponse.success) {
|
||||
const pageData = pageDataResponse.data;
|
||||
|
||||
// 直接使用简历列表中的模板数据
|
||||
const selectedTemplate = item.template;
|
||||
|
||||
// 找到对应的行业信息
|
||||
const matchedIndustry = pageData.industries?.find(industry =>
|
||||
industry.name === item.industry
|
||||
);
|
||||
|
||||
// 传递数据给 ResumeInfoModal
|
||||
const resumeData = {
|
||||
selectedTemplate,
|
||||
studentResume: pageData.myResume,
|
||||
industry: matchedIndustry,
|
||||
jobPosition: item.position
|
||||
};
|
||||
|
||||
console.log('加载简历数据:', {
|
||||
resumeTitle: item.title,
|
||||
position: item.position,
|
||||
industry: item.industry
|
||||
});
|
||||
|
||||
setResumeInfoData(resumeData);
|
||||
setResumeInfoModalShow(true);
|
||||
} else {
|
||||
toast.error('加载简历数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取简历数据失败:', error);
|
||||
toast.error('加载简历数据失败');
|
||||
}
|
||||
};;;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -141,16 +226,22 @@ export default ({ visible, onClose, data }) => {
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{data?.description && (
|
||||
{data?.details?.description && (
|
||||
<div className="job-info-modal-content-position-info-description">
|
||||
<p className="description-title">岗位描述</p>
|
||||
<p className="description-content">{data?.description}</p>
|
||||
<p className="description-content">{data?.details?.description}</p>
|
||||
</div>
|
||||
)}
|
||||
{data?.requirements && (
|
||||
<div className="job-info-modal-content-position-info-description">
|
||||
<p className="description-title">岗位要求</p>
|
||||
<p className="description-content">{data?.requirements}</p>
|
||||
{data?.details?.requirements?.length > 0 && (
|
||||
<div className="job-info-modal-content-position-info-requirements">
|
||||
<p className="requirements-title">岗位要求</p>
|
||||
<ul className="requirements-content">
|
||||
{data?.details?.requirements?.map((item, index) => (
|
||||
<li key={index} className="requirements-item">
|
||||
{index + 1}. {item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{/* {data?.details?.requirements?.length > 0 && (
|
||||
@@ -165,11 +256,11 @@ export default ({ visible, onClose, data }) => {
|
||||
</ul>
|
||||
</div>
|
||||
)} */}
|
||||
{data?.company?.industry && (
|
||||
{data?.details?.companyInfo && (
|
||||
<div className="job-info-modal-content-position-info-companyInfo">
|
||||
<p className="companyInfo-title">公司介绍</p>
|
||||
<p className="companyInfo-content">
|
||||
{data?.company?.industry}
|
||||
{data?.details?.companyInfo}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -186,8 +277,11 @@ export default ({ visible, onClose, data }) => {
|
||||
</Modal>
|
||||
<ResumeInfoModal
|
||||
visible={resumeInfoModalShow}
|
||||
data={null}
|
||||
onClose={() => setResumeInfoModalShow(false)}
|
||||
data={resumeInfoData}
|
||||
onClose={() => {
|
||||
setResumeInfoModalShow(false);
|
||||
setResumeInfoData(null);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -8,12 +8,15 @@ import "./index.css";
|
||||
export default ({ className = "", data = [], backgroundColor }) => {
|
||||
const [jobInfoData, setJobInfoData] = useState(undefined);
|
||||
const [jobInfoModalVisible, setJobInfoModalVisible] = useState(false);
|
||||
const [directToResume, setDirectToResume] = useState(false);
|
||||
|
||||
const handleJobClick = async (e, item) => {
|
||||
e.stopPropagation();
|
||||
const res = await getJobsDetail(item.id);
|
||||
if (res.success) {
|
||||
setJobInfoData(mapJob(res.data));
|
||||
// Mock数据已经是前端格式,不需要映射
|
||||
setJobInfoData(res.data);
|
||||
setDirectToResume(false); // 点击岗位条目,显示详情
|
||||
setJobInfoModalVisible(true);
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
@@ -25,6 +28,21 @@ export default ({ className = "", data = [], backgroundColor }) => {
|
||||
setJobInfoModalVisible(false);
|
||||
};
|
||||
|
||||
// 直接投递按钮点击事件
|
||||
const handleDeliverClick = async (e, item) => {
|
||||
e.stopPropagation(); // 阻止冒泡到li的点击事件
|
||||
|
||||
// 获取岗位详情然后打开投递弹窗
|
||||
const res = await getJobsDetail(item.id);
|
||||
if (res.success) {
|
||||
setJobInfoData(res.data);
|
||||
setDirectToResume(true); // 点击投递按钮,直接显示简历选择界面
|
||||
setJobInfoModalVisible(true);
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className={`company-jobs-page-left-list ${className}`}>
|
||||
@@ -56,7 +74,10 @@ export default ({ className = "", data = [], backgroundColor }) => {
|
||||
<p className="company-jobs-info-position-salary">
|
||||
{item?.salary}
|
||||
</p>
|
||||
<button className="company-jobs-btn">
|
||||
<button
|
||||
className="company-jobs-btn"
|
||||
onClick={(e) => handleDeliverClick(e, item)}
|
||||
>
|
||||
<i />
|
||||
<span>投递</span>
|
||||
</button>
|
||||
@@ -68,6 +89,7 @@ export default ({ className = "", data = [], backgroundColor }) => {
|
||||
data={jobInfoData}
|
||||
visible={jobInfoModalVisible}
|
||||
onClose={onClickJobInfoModalClose}
|
||||
directToResume={directToResume}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
|
||||
.educational-experience-list-item {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -105,7 +106,8 @@
|
||||
> p {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.school-name {
|
||||
@@ -146,7 +148,8 @@
|
||||
|
||||
.project-info-wrapper {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
min-height: 46px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
@@ -196,6 +199,8 @@
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -208,7 +213,8 @@
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
@@ -217,8 +223,9 @@
|
||||
}
|
||||
> li {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
@@ -249,8 +256,9 @@
|
||||
|
||||
.skill-name {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
@@ -267,8 +275,10 @@
|
||||
|
||||
.core-capabilities-list-item {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
@@ -278,21 +288,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.personal-summary-list {
|
||||
.personal-summary-list,
|
||||
.personal-summary-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> li {
|
||||
> li,
|
||||
> p {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
min-height: 22px;
|
||||
height: auto;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
.corresponding-course-units-list {
|
||||
@@ -308,7 +324,8 @@
|
||||
|
||||
.corresponding-course-units-list-item {
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
min-height: 72px;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -13,6 +13,47 @@ export default ({ visible, onClose, data }) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 获取当前简历数据
|
||||
const currentTemplate = data?.selectedTemplate;
|
||||
const studentInfo = currentTemplate?.studentInfo;
|
||||
const positionTitle = currentTemplate?.position || "岗位名称";
|
||||
|
||||
// 转换数据格式
|
||||
let resumeData = {
|
||||
educational_experience: ["相关专业大学 本科"],
|
||||
project_experience: [],
|
||||
core_skills: [],
|
||||
compound_skills: [],
|
||||
personal_summary: "暂无个人总结"
|
||||
};
|
||||
|
||||
if (studentInfo) {
|
||||
// 处理教育经历
|
||||
resumeData.educational_experience = studentInfo.educational_experience || ["相关专业大学 本科"];
|
||||
|
||||
// 处理项目经历
|
||||
if (studentInfo.project_experience) {
|
||||
if (Array.isArray(studentInfo.project_experience)) {
|
||||
// 如果已经是数组格式
|
||||
resumeData.project_experience = studentInfo.project_experience;
|
||||
} else if (typeof studentInfo.project_experience === 'object') {
|
||||
// 如果是对象格式,转换为数组
|
||||
const proj = studentInfo.project_experience;
|
||||
resumeData.project_experience = [
|
||||
{
|
||||
name: proj.project_name || proj.position || "实习项目",
|
||||
description: proj.description || "参与项目实施"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理核心技能和复合技能
|
||||
resumeData.core_skills = studentInfo.core_skills || [];
|
||||
resumeData.compound_skills = studentInfo.compound_skills || [];
|
||||
resumeData.personal_summary = studentInfo.personal_summary || "具有扎实的专业基础和实践经验";
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal visible={visible} onClose={handleCloseModal}>
|
||||
<div className="resume-info-modal">
|
||||
@@ -28,95 +69,80 @@ export default ({ visible, onClose, data }) => {
|
||||
<Radio value="2">个人修改版</Radio>
|
||||
<Radio value="3">个人修改版</Radio>
|
||||
</Radio.Group>
|
||||
<p className="resume-info-modal-title">非标自动化工程师</p>
|
||||
<p className="resume-info-modal-title">{positionTitle}</p>
|
||||
<ul className="resume-info-moda-list">
|
||||
{/* 教育经历 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">教育经历</p>
|
||||
<ul className="educational-experience-list">
|
||||
<li className="educational-experience-list-item">
|
||||
<p className="school-name">北京理工大学</p>
|
||||
<p className="study-time">2018.09 - 2022.07</p>
|
||||
</li>
|
||||
<li className="educational-experience-list-item">
|
||||
<p className="school-name">北京理工大学</p>
|
||||
<p className="study-time">2018.09 - 2022.07</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{resumeData.educational_experience && resumeData.educational_experience.length > 0 && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">教育经历</p>
|
||||
<ul className="educational-experience-list">
|
||||
{resumeData.educational_experience.map((edu, index) => (
|
||||
<li key={index} className="educational-experience-list-item">
|
||||
<p className="school-name">{edu}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
{/* 项目经历 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">项目经历</p>
|
||||
<ul className="project-experience-list">
|
||||
<li className="project-experience-list-item">
|
||||
<div className="project-info-wrapper">
|
||||
<div className="project-info">
|
||||
<p className="project-name">项目名称</p>
|
||||
<p className="project-company">单位:xxxxxx</p>
|
||||
</div>
|
||||
<p className="project-time">2022.09 - 2023.07</p>
|
||||
</div>
|
||||
<p className="project-desc">
|
||||
1. 负责室内平面设计项目的创意和实施2.
|
||||
与团队合作,确保设计质量和项目进度 3.
|
||||
持续关注设计趋势,提升设计技能 1.
|
||||
具备出色的沟通能力和团队协作精神 2.
|
||||
能够高效完成任务,对工作细节有高度关注 3.
|
||||
具有不断学习和适应新挑战的能力
|
||||
加分项:有以下行业经验:建筑设计
|
||||
</p>
|
||||
<ul className="job-responsibilities-list">
|
||||
<p>岗位职责</p>
|
||||
<li>1.负责室内平面设计项目的创意和实施</li>
|
||||
<li>2.与团队合作,确保设计质量和项目进度</li>
|
||||
<li>3.持续关注设计趋势,提升设计技能</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{resumeData.project_experience && resumeData.project_experience.length > 0 && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">项目经历</p>
|
||||
<ul className="project-experience-list">
|
||||
{resumeData.project_experience.map((project, index) => (
|
||||
<li key={index} className="project-experience-list-item">
|
||||
<div className="project-info-wrapper">
|
||||
<div className="project-info">
|
||||
<p className="project-name">{project.name || `项目${index + 1}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="project-desc">
|
||||
{project.description}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
{/* 专业技能 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">专业技能</p>
|
||||
<ul className="professional-skills-list">
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">核心能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
<p className="core-capabilities-list-item">
|
||||
1. 负责室内平面设计项目的创意和实施
|
||||
</p>
|
||||
<p className="core-capabilities-list-item">
|
||||
2. 与团队合作,确保设计质量和项目进度
|
||||
</p>
|
||||
<p className="core-capabilities-list-item">
|
||||
3. 持续关注设计趋势,提升设计技能
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">复核能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
<p className="core-capabilities-list-item">
|
||||
1. 负责室内平面设计项目的创意和实施
|
||||
</p>
|
||||
<p className="core-capabilities-list-item">
|
||||
2. 与团队合作,确保设计质量和项目进度
|
||||
</p>
|
||||
<p className="core-capabilities-list-item">
|
||||
3. 持续关注设计趋势,提升设计技能
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
{resumeData.core_skills && resumeData.core_skills.length > 0 && (
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">核心能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
{resumeData.core_skills.map((skill, index) => (
|
||||
<p key={index} className="core-capabilities-list-item">
|
||||
{skill}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
{resumeData.compound_skills && resumeData.compound_skills.length > 0 && (
|
||||
<li className="professional-skills-list-item">
|
||||
<p className="skill-name">复合能力</p>
|
||||
<div className="core-capabilities-list">
|
||||
{resumeData.compound_skills.map((skill, index) => (
|
||||
<p key={index} className="core-capabilities-list-item">
|
||||
{skill}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
{/* 个人总结 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">个人总结</p>
|
||||
<ul className="personal-summary-list">
|
||||
<li>1. 负责室内平面设计项目的创意和实施</li>
|
||||
<li>2. 与团队合作,确保设计质量和项目进度</li>
|
||||
<li>3. 持续关注设计趋势,提升设计技能</li>
|
||||
</ul>
|
||||
</li>
|
||||
{resumeData.personal_summary && resumeData.personal_summary.trim() !== '' && (
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">个人总结</p>
|
||||
<div className="personal-summary-content">
|
||||
<p>{resumeData.personal_summary}</p>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
{/* 对应课程单元 */}
|
||||
<li className="resume-info-moda-item">
|
||||
<p className="resume-info-moda-item-title">对应课程单元</p>
|
||||
|
||||
@@ -2,7 +2,7 @@ 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";
|
||||
// import { mapJobList, mapInterviewList } from "@/utils/dataMapper"; // 不再需要映射器,mock数据已经是前端格式
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
@@ -40,22 +40,22 @@ const CompanyJobsPage = () => {
|
||||
if (res?.success) {
|
||||
// 设置岗位数据
|
||||
if (res.data?.jobs) {
|
||||
const mappedJobs = mapJobList(res.data.jobs.list || []);
|
||||
setJobs(mappedJobs);
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const jobs = res.data.jobs.list || [];
|
||||
setJobs(jobs);
|
||||
setJobsListHasMore(res.data.jobs.hasMore);
|
||||
if (mappedJobs.length > 0) {
|
||||
if (jobs.length > 0) {
|
||||
setJobsListPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
|
||||
// 设置面试数据
|
||||
if (res.data?.interviews && studentInfo?.id) {
|
||||
const mappedInterviews = mapInterviewList(
|
||||
res.data.interviews.list || []
|
||||
);
|
||||
setInterviews(mappedInterviews);
|
||||
// Mock数据已经是前端格式,直接使用不需要映射
|
||||
const interviews = res.data.interviews.list || [];
|
||||
setInterviews(interviews);
|
||||
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||
if (mappedInterviews.length > 0) {
|
||||
if (interviews.length > 0) {
|
||||
setInterviewsPage(2); // 下次从第2页开始
|
||||
}
|
||||
}
|
||||
@@ -93,13 +93,14 @@ const CompanyJobsPage = () => {
|
||||
status: "SCHEDULED",
|
||||
});
|
||||
if (res.success) {
|
||||
const mappedInterviews = mapInterviewList(res.data || []);
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const interviews = res.data || [];
|
||||
setInterviews((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(
|
||||
prevList.map((interview) => interview.id)
|
||||
);
|
||||
const newInterviews = mappedInterviews.filter(
|
||||
const newInterviews = interviews.filter(
|
||||
(interview) => !existingIds.has(interview.id)
|
||||
);
|
||||
|
||||
@@ -140,11 +141,12 @@ const CompanyJobsPage = () => {
|
||||
});
|
||||
|
||||
if (res?.success) {
|
||||
const mappedJobs = mapJobList(res.data);
|
||||
// Mock数据已经是前端格式,直接使用
|
||||
const jobs = res.data;
|
||||
setJobs((prevList) => {
|
||||
// 去重处理:过滤掉已存在的数据
|
||||
const existingIds = new Set(prevList.map((job) => job.id));
|
||||
const newJobs = mappedJobs.filter((job) => !existingIds.has(job.id));
|
||||
const newJobs = jobs.filter((job) => !existingIds.has(job.id));
|
||||
|
||||
const newList = [...prevList, ...newJobs];
|
||||
if (res.total <= newList?.length) {
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
import { Avatar, Skeleton, Empty } from "@arco-design/web-react";
|
||||
import "./index.css";
|
||||
|
||||
const TaskList = ({ tasks = [], selectedDate, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">事项</p>
|
||||
<Skeleton loading={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const getTaskTypeText = (type) => {
|
||||
const typeMap = {
|
||||
HOMEWORK: "作业",
|
||||
PROJECT: "项目",
|
||||
REPORT: "报告",
|
||||
INTERVIEW: "面试",
|
||||
OTHER: "其他",
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
// const getPriorityClass = (priority) => {
|
||||
// const classMap = {
|
||||
// URGENT: "urgent",
|
||||
// HIGH: "high",
|
||||
// MEDIUM: "medium",
|
||||
// LOW: "low",
|
||||
// };
|
||||
// return classMap[priority] || "medium";
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">
|
||||
事项 - {formatDate(selectedDate)}
|
||||
{tasks.length > 0 && (
|
||||
<span className="task-count">({tasks.length})</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{tasks.length === 0 ? (
|
||||
<div className="no-tasks">
|
||||
<Empty description="该日无事项" />
|
||||
</div>
|
||||
) : (
|
||||
<ul className="module-tasks-list">
|
||||
{tasks.map((item, index) => (
|
||||
<li key={item.id} className="module-tasks-item">
|
||||
<div className="module-tasks-item-info">
|
||||
<Avatar className="module-tasks-item-info-avatar" size="small">
|
||||
{item?.teacherAvatar ? (
|
||||
<img alt="avatar" src={item.teacherAvatar} />
|
||||
) : (
|
||||
item?.teacherName?.charAt(0) || "T"
|
||||
)}
|
||||
</Avatar>
|
||||
<span className="module-tasks-item-info-teacher-name">
|
||||
{item?.teacherName || "未知教师"}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`module-tasks-item-content ${
|
||||
index === tasks.length - 1
|
||||
? "module-tasks-item-content-last"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="module-tasks-item-content-info">
|
||||
<p>
|
||||
<span className="task-type">
|
||||
{getTaskTypeText(item.type)}:
|
||||
</span>
|
||||
{item?.title}
|
||||
</p>
|
||||
<div>
|
||||
课程名称:{item?.courseName}
|
||||
<span className="module-tasks-item-content-info-duration">
|
||||
{item?.duration}
|
||||
</span>
|
||||
{/* <span className={`task-status status-${item.status?.toLowerCase()}`}>
|
||||
{item.status === 'PENDING' ? '待完成' :
|
||||
item.status === 'IN_PROGRESS' ? '进行中' :
|
||||
item.status === 'COMPLETED' ? '已完成' : '未知'}
|
||||
</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
||||
import { Avatar, Empty, Skeleton } from "@arco-design/web-react";
|
||||
import "./index.css";
|
||||
|
||||
const TaskList = ({ tasks = [], selectedDate, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">事项</p>
|
||||
<Skeleton loading={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.toLocaleDateString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const getTaskTypeText = (type) => {
|
||||
const typeMap = {
|
||||
HOMEWORK: "作业",
|
||||
PROJECT: "项目",
|
||||
REPORT: "报告",
|
||||
INTERVIEW: "面试",
|
||||
OTHER: "其他",
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
// const getPriorityClass = (priority) => {
|
||||
// const classMap = {
|
||||
// URGENT: "urgent",
|
||||
// HIGH: "high",
|
||||
// MEDIUM: "medium",
|
||||
// LOW: "low",
|
||||
// };
|
||||
// return classMap[priority] || "medium";
|
||||
// };
|
||||
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">
|
||||
事项 - {formatDate(selectedDate)}
|
||||
{tasks.length > 0 && (
|
||||
<span className="task-count">({tasks.length})</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
{tasks.length === 0 ? (
|
||||
<div className="no-tasks">
|
||||
<Empty description="该日无事项" />
|
||||
</div>
|
||||
) : (
|
||||
<ul className="module-tasks-list">
|
||||
{tasks.map((item, index) => (
|
||||
<li key={item.id} className="module-tasks-item">
|
||||
<div className="module-tasks-item-info">
|
||||
<Avatar className="module-tasks-item-info-avatar" size="small">
|
||||
{item?.teacherAvatar ? (
|
||||
<img alt="avatar" src={item.teacherAvatar} />
|
||||
) : (
|
||||
item?.teacherName?.charAt(0) || "T"
|
||||
)}
|
||||
</Avatar>
|
||||
<span className="module-tasks-item-info-teacher-name">
|
||||
{item?.teacherName || "未知教师"}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`module-tasks-item-content ${
|
||||
index === tasks.length - 1
|
||||
? "module-tasks-item-content-last"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="module-tasks-item-content-info">
|
||||
<p>
|
||||
<span className="task-type">
|
||||
{getTaskTypeText(item.type)}:
|
||||
</span>
|
||||
{item?.title}
|
||||
</p>
|
||||
<div>
|
||||
课程名称:{item?.courseName}
|
||||
<span className="module-tasks-item-content-info-duration">
|
||||
{item?.duration}
|
||||
</span>
|
||||
{/* <span className={`task-status status-${item.status?.toLowerCase()}`}>
|
||||
{item.status === 'PENDING' ? '待完成' :
|
||||
item.status === 'IN_PROGRESS' ? '进行中' :
|
||||
item.status === 'COMPLETED' ? '已完成' : '未知'}
|
||||
</span> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskList;
|
||||
|
||||
@@ -1,47 +1,68 @@
|
||||
import { mockData } from "@/data/mockData";
|
||||
import ICON1 from "@/assets/images/HomeworkPage/homework_page_icon1.png";
|
||||
import ICON2 from "@/assets/images/HomeworkPage/homework_page_icon2.png";
|
||||
import "./index.css";
|
||||
|
||||
const HomeworkPage = () => {
|
||||
const { homework } = mockData;
|
||||
|
||||
const handleClickBtn = () => {};
|
||||
|
||||
return (
|
||||
<div className="homework-page-wrapper">
|
||||
<ul className="homework-page-content">
|
||||
{homework.map((item) => (
|
||||
<li key={item.id} className="homework-page-content-list">
|
||||
<p className="homework-page-content-list-title">{item.name}</p>
|
||||
<ul className="homework-page-content-list-class">
|
||||
{item.list.map((contentItem) => (
|
||||
<li
|
||||
key={contentItem.id}
|
||||
className="homework-page-content-list-content-item"
|
||||
>
|
||||
<img
|
||||
alt="icon"
|
||||
src={ICON1}
|
||||
className="homework-page-content-list-content-item-icon"
|
||||
/>
|
||||
<p className="homework-page-content-list-content-item-name">
|
||||
{contentItem.name}
|
||||
</p>
|
||||
<div
|
||||
className="homework-page-content-list-content-item-btn"
|
||||
onClick={() => handleClickBtn()}
|
||||
>
|
||||
完成作业
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeworkPage;
|
||||
import { mockData } from "@/data/mockData";
|
||||
import ICON1 from "@/assets/images/HomeworkPage/homework_page_icon1.png";
|
||||
import ICON2 from "@/assets/images/HomeworkPage/homework_page_icon2.png";
|
||||
import "./index.css";
|
||||
|
||||
const HomeworkPage = () => {
|
||||
const { homework } = mockData;
|
||||
|
||||
const handleClickBtn = (sectionId, itemId) => {
|
||||
// 只有复合能力培养的第一个项目(展会策划教学)可以点击
|
||||
if (sectionId === 1 && itemId === 1) {
|
||||
window.open("https://du9uay.github.io/zhanhui/", "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否应该显示灰色图片和禁用按钮
|
||||
const isDisabled = (sectionId, itemId) => {
|
||||
// 只有复合能力培养的第一个项目是启用状态
|
||||
return !(sectionId === 1 && itemId === 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="homework-page-wrapper">
|
||||
<ul className="homework-page-content">
|
||||
{homework.map((item) => (
|
||||
<li key={item.id} className="homework-page-content-list">
|
||||
<p className="homework-page-content-list-title">{item.name}</p>
|
||||
<ul className="homework-page-content-list-class">
|
||||
{item.list.map((contentItem) => (
|
||||
<li
|
||||
key={contentItem.id}
|
||||
className="homework-page-content-list-content-item"
|
||||
>
|
||||
<img
|
||||
alt="icon"
|
||||
src={ICON1}
|
||||
className="homework-page-content-list-content-item-icon"
|
||||
style={{
|
||||
filter: isDisabled(item.id, contentItem.id) ? "grayscale(100%)" : "none",
|
||||
opacity: isDisabled(item.id, contentItem.id) ? 0.6 : 1
|
||||
}}
|
||||
/>
|
||||
<p className="homework-page-content-list-content-item-name">
|
||||
{contentItem.name}
|
||||
</p>
|
||||
<div
|
||||
className={`homework-page-content-list-content-item-btn ${
|
||||
isDisabled(item.id, contentItem.id) ? "disabled" : ""
|
||||
}`}
|
||||
onClick={() => !isDisabled(item.id, contentItem.id) && handleClickBtn(item.id, contentItem.id)}
|
||||
style={{
|
||||
cursor: isDisabled(item.id, contentItem.id) ? "not-allowed" : "pointer",
|
||||
opacity: isDisabled(item.id, contentItem.id) ? 0.5 : 1
|
||||
}}
|
||||
>
|
||||
完成作业
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeworkPage;
|
||||
@@ -5,13 +5,57 @@ import "./index.css";
|
||||
|
||||
// 滑动阈值(向上滑动超过这个距离触发切换)
|
||||
const SWIPE_THRESHOLD = 100;
|
||||
export default () => {
|
||||
export default ({ selectedItem = "求职面试初体验" }) => {
|
||||
const [isSlide, setIsSlide] = useState(false);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [startY, setStartY] = useState(0);
|
||||
const [currentY, setCurrentY] = useState(0);
|
||||
const slideRef = useRef(null);
|
||||
|
||||
// 根据选中项目获取对应的视频URL
|
||||
const getVideoUrl = () => {
|
||||
switch(selectedItem) {
|
||||
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";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否是锁定的面试模拟项目
|
||||
const isLockedItem = () => {
|
||||
return selectedItem?.includes("线下面试模拟");
|
||||
};
|
||||
|
||||
// 根据选中项目获取评价数据
|
||||
const getEvaluationData = () => {
|
||||
if (selectedItem === "未来的自己") {
|
||||
return {
|
||||
totalScore: 92,
|
||||
professionalScore: 39.5,
|
||||
performanceScore: 55.0,
|
||||
radarData: [10, 9, 10, 9],
|
||||
title: "优秀表现评价",
|
||||
content: `在专业能力方面,候选人对相关专业知识掌握扎实,能够深入且准确地回答与项目经验相关的复杂问题。在描述具体项目时,不仅能清晰说明自己的职责和成果,还能主动分析项目中的技术难点和解决方案,体现出优秀的实践操作能力和技术思维。对行业前沿动态有深入了解,能够结合实际工作提出创新性见解。
|
||||
|
||||
沟通表达上,候选人语言表达流畅自然,逻辑思维清晰有条理,能够积极主动地与面试官互动。在面对开放性问题时展现出良好的思维发散能力,回答层次分明,重点突出,表现出强烈的学习意愿和职业发展规划意识。整体表现超出预期,具备优秀的职场素养。`
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
totalScore: 85,
|
||||
professionalScore: 38.0,
|
||||
performanceScore: 50.0,
|
||||
radarData: [9, 8, 9, 9],
|
||||
title: "基础面试评价",
|
||||
content: `在专业能力方面,候选人对 [岗位相关专业知识] 有基本掌握,能够清晰回答与过往项目经验相关的问题,例如在描述 [具体项目] 时,能准确说明自己承担的职责和达成的成果,体现出一定的实践操作能力。但在深入探讨 [某一专业难点] 时,表述略显浅显,对行业前沿动态的了解不够全面,专业深度有提升空间。
|
||||
|
||||
沟通表达上,候选人语言流畅,逻辑较为清晰,能够主动回应面试官的问题,展现出良好的沟通意愿。不过在面对一些开放性问题时,思维发散性稍弱,回答的层次感不够突出,有时会出现表述重复的情况。`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 处理触摸/鼠标开始
|
||||
const handleStart = (clientY) => {
|
||||
setIsDragging(true);
|
||||
@@ -27,13 +71,23 @@ export default () => {
|
||||
if (!isDragging) return;
|
||||
|
||||
setCurrentY(clientY);
|
||||
const diffY = startY - clientY; // 计算向上滑动的距离
|
||||
const diffY = startY - clientY; // 正值表示向上滑动,负值表示向下滑动
|
||||
|
||||
// 只允许向上滑动,限制最大滑动距离
|
||||
if (diffY > 0) {
|
||||
const translateY = Math.min(diffY, 150);
|
||||
if (slideRef.current) {
|
||||
slideRef.current.style.transform = `translateY(-${translateY}px)`;
|
||||
if (isSlide) {
|
||||
// 在评价界面时,只允许向下滑动
|
||||
if (diffY < 0) {
|
||||
const translateY = Math.min(Math.abs(diffY), 150);
|
||||
if (slideRef.current) {
|
||||
slideRef.current.style.transform = `translateY(${translateY}px)`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 在视频界面时,只允许向上滑动,且只对懵懂初试的项目生效
|
||||
if (diffY > 0 && (selectedItem === "求职面试初体验" || selectedItem === "未来的自己")) {
|
||||
const translateY = Math.min(diffY, 150);
|
||||
if (slideRef.current) {
|
||||
slideRef.current.style.transform = `translateY(-${translateY}px)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -51,16 +105,33 @@ export default () => {
|
||||
slideRef.current.style.transform = "";
|
||||
}
|
||||
|
||||
// 检查是否达到滑动阈值
|
||||
if (diffY > SWIPE_THRESHOLD) {
|
||||
handleSwipeUp();
|
||||
// 检查滑动方向和阈值
|
||||
if (isSlide) {
|
||||
// 在评价界面时,向下滑动返回视频界面
|
||||
if (diffY < -SWIPE_THRESHOLD) {
|
||||
handleSwipeDown();
|
||||
}
|
||||
} else {
|
||||
// 在视频界面时,向上滑动到评价界面
|
||||
if (diffY > SWIPE_THRESHOLD) {
|
||||
handleSwipeUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 向上滑动事件处理
|
||||
const handleSwipeUp = () => {
|
||||
console.log("触发向上滑动切换");
|
||||
setIsSlide(true);
|
||||
// 只有懵懂初试的项目可以上滑查看评价
|
||||
if (selectedItem === "求职面试初体验" || selectedItem === "未来的自己") {
|
||||
console.log("触发向上滑动切换");
|
||||
setIsSlide(true);
|
||||
}
|
||||
};
|
||||
|
||||
// 向下滑动事件处理
|
||||
const handleSwipeDown = () => {
|
||||
console.log("触发向下滑动返回");
|
||||
setIsSlide(false);
|
||||
};
|
||||
|
||||
// 鼠标事件处理
|
||||
@@ -126,38 +197,54 @@ export default () => {
|
||||
}`}
|
||||
>
|
||||
{isSlide ? (
|
||||
<div className="interview-evaluation-wrapper">
|
||||
<div
|
||||
className="interview-evaluation-wrapper"
|
||||
ref={slideRef}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
style={{
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
touchAction: "pan-x",
|
||||
}}
|
||||
>
|
||||
<div className="interview-evaluation-charts-wrapper">
|
||||
<div className="interview-rating-header">
|
||||
<span className="interview-rating-header-title">面试评分</span>
|
||||
</div>
|
||||
<div className="charts-content">
|
||||
<div className="charts-content-top">
|
||||
<ScoreChart className="score-chart" value={85} />
|
||||
<ScoreChart className="score-chart" value={getEvaluationData().totalScore} />
|
||||
<div className="score-info">
|
||||
<div className="score-info-item item1">
|
||||
<span className="score-info-item-title">
|
||||
专业能力评分(40)
|
||||
</span>
|
||||
<span className="score-info-item-value">38.00</span>
|
||||
<span className="score-info-item-value">{getEvaluationData().professionalScore}</span>
|
||||
</div>
|
||||
<div className="score-info-item item2">
|
||||
<span className="score-info-item-title">
|
||||
现场表现评分(60)
|
||||
</span>
|
||||
<span className="score-info-item-value">50.00</span>
|
||||
<span className="score-info-item-value">{getEvaluationData().performanceScore}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="charts-content-bottom">
|
||||
<RadarChart
|
||||
className="radar-chart"
|
||||
data={[9, 8, 9, 9]}
|
||||
data={getEvaluationData().radarData}
|
||||
indicator={[
|
||||
{ name: "{a|核心知识掌握}\n{b|与精准应用}", max: 10 },
|
||||
{ name: "{a|问题分析、方案设计}\n{b|与成果表达}", max: 10 },
|
||||
{ name: "{a|产业认知与行业}\n{b|趋势洞察}", max: 10 },
|
||||
{ name: "{a|企业工作流}\n{b|理解与实践}", max: 10 },
|
||||
{ name: "{a|核心知识掌握}\\n{b|与精准应用}", max: 10 },
|
||||
{ name: "{a|问题分析、方案设计}\\n{b|与成果表达}", max: 10 },
|
||||
{ name: "{a|产业认知与行业}\\n{b|趋势洞察}", max: 10 },
|
||||
{ name: "{a|企业工作流}\\n{b|理解与实践}", max: 10 },
|
||||
]}
|
||||
/>
|
||||
<RadarChart className="radar-chart" />
|
||||
@@ -166,16 +253,10 @@ export default () => {
|
||||
</div>
|
||||
<div className="interview-evaluation-text-wrapper">
|
||||
<div className="interview-rating-header">
|
||||
<span className="interview-rating-header-title">面试评分</span>
|
||||
<span className="interview-rating-header-title">{getEvaluationData().title}</span>
|
||||
</div>
|
||||
<div className="interview-rating-text">
|
||||
在专业能力方面,候选人对 [岗位相关专业知识]
|
||||
有基本掌握,能够清晰回答与过往项目经验相关的问题,例如在描述
|
||||
[具体项目]
|
||||
时,能准确说明自己承担的职责和达成的成果,体现出一定的实践操作能力。但在深入探讨
|
||||
[某一专业难点]
|
||||
时,表述略显浅显,对行业前沿动态的了解不够全面,专业深度有提升空间。
|
||||
沟通表达上,候选人语言流畅,逻辑较为清晰,能够主动回应面试官的问题,展现出良好的沟通意愿。不过在面对一些开放性问题时,思维发散性稍弱,回答的层次感不够突出,有时会出现表述重复的情况。
|
||||
{getEvaluationData().content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -203,7 +284,11 @@ export default () => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="interview-rating-video">
|
||||
<video src="" controls></video>
|
||||
{isLockedItem() ? (
|
||||
<img src="/线下面试模拟锁定.png" alt="线下面试模拟锁定" style={{width: "100%", height: "100%", objectFit: "cover"}} />
|
||||
) : (
|
||||
<video src={getVideoUrl()} controls></video>
|
||||
)}
|
||||
</div>
|
||||
<div className="interview-rating-slide">
|
||||
<i />
|
||||
@@ -213,4 +298,4 @@ export default () => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,17 @@
|
||||
import { useState } from "react";
|
||||
import { Timeline } from "@arco-design/web-react";
|
||||
import "./index.css";
|
||||
|
||||
const TimelineItem = Timeline.Item;
|
||||
|
||||
export default ({ data }) => {
|
||||
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">面试模拟</p>
|
||||
@@ -17,24 +25,32 @@ export default ({ data }) => {
|
||||
<TimelineItem
|
||||
lineType="dashed"
|
||||
dot={
|
||||
<div className="time-line-dot-icon time-line-dot-icon-active" />
|
||||
<div className={`time-line-dot-icon ${selectedItem === "求职面试初体验" ? "time-line-dot-icon-active" : ""}`} />
|
||||
}
|
||||
>
|
||||
<div className="time-line-item time-line-item-active">
|
||||
<p>终生学习系统</p>
|
||||
<div
|
||||
className={`time-line-item ${selectedItem === "求职面试初体验" ? "time-line-item-active" : ""}`}
|
||||
onClick={() => handleItemClick("求职面试初体验")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<p>求职面试初体验</p>
|
||||
<div className="time-line-item-info">
|
||||
这里是文本评价,经过我们的交谈我们觉得你跟我们的意向很匹配
|
||||
初次接触面试环境,体验真实面试流程,了解自身在面试中的基本表现和待提升的方面
|
||||
</div>
|
||||
</div>
|
||||
</TimelineItem>
|
||||
<TimelineItem
|
||||
lineType="dashed"
|
||||
dot={<div className="time-line-dot-icon" />}
|
||||
dot={<div className={`time-line-dot-icon ${selectedItem === "未来的自己" ? "time-line-dot-icon-active" : ""}`} />}
|
||||
>
|
||||
<div className="time-line-item">
|
||||
<p>终生学习系统</p>
|
||||
<div
|
||||
className={`time-line-item ${selectedItem === "未来的自己" ? "time-line-item-active" : ""}`}
|
||||
onClick={() => handleItemClick("未来的自己")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<p>未来的自己</p>
|
||||
<div className="time-line-item-info">
|
||||
这里是文本评价,经过我们的交谈我们觉得你跟我们的意向很匹配
|
||||
经过系统训练后的面试表现,展示个人成长和进步,体现出更强的职场竞争力和专业素养
|
||||
</div>
|
||||
</div>
|
||||
</TimelineItem>
|
||||
@@ -43,31 +59,67 @@ export default ({ data }) => {
|
||||
<li className="mock-interview-item">
|
||||
<p className="mock-interview-item-title">
|
||||
<i className="mock-interview-item-title-icon" />
|
||||
<span>懵懂初试</span>
|
||||
<span>有备而战</span>
|
||||
</p>
|
||||
<Timeline>
|
||||
<TimelineItem
|
||||
lineType="dashed"
|
||||
dot={
|
||||
<div className="time-line-dot-icon time-line-dot-icon-active" />
|
||||
}
|
||||
dot={<div className={`time-line-dot-icon ${selectedItem === "第一次线下面试模拟" ? "time-line-dot-icon-active" : ""}`} />}
|
||||
>
|
||||
<div className="time-line-item time-line-item-active">
|
||||
<p>终生学习系统</p>
|
||||
<div className="time-line-item-info">
|
||||
这里是文本评价,经过我们的交谈我们觉得你跟我们的意向很匹配
|
||||
</div>
|
||||
<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" />}
|
||||
dot={<div className={`time-line-dot-icon ${selectedItem === "第二次线下面试模拟" ? "time-line-dot-icon-active" : ""}`} />}
|
||||
>
|
||||
<div className="time-line-item">
|
||||
<p>终生学习系统</p>
|
||||
<div className="time-line-item-info">
|
||||
这里是文本评价,经过我们的交谈我们觉得你跟我们的意向很匹配
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
@@ -75,4 +127,4 @@ export default ({ data }) => {
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1,14 +1,17 @@
|
||||
import MockInterview from "./components/MockInterview";
|
||||
import InterviewRating from "./components/InterviewRating";
|
||||
import "./index.css";
|
||||
|
||||
const InterviewSimulationPage = () => {
|
||||
return (
|
||||
<div className="interview-simulation-page">
|
||||
<MockInterview />
|
||||
<InterviewRating />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InterviewSimulationPage;
|
||||
import { useState } from "react";
|
||||
import MockInterview from "./components/MockInterview";
|
||||
import InterviewRating from "./components/InterviewRating";
|
||||
import "./index.css";
|
||||
|
||||
const InterviewSimulationPage = () => {
|
||||
const [selectedItem, setSelectedItem] = useState("求职面试初体验");
|
||||
|
||||
return (
|
||||
<div className="interview-simulation-page">
|
||||
<MockInterview onItemSelect={setSelectedItem} />
|
||||
<InterviewRating selectedItem={selectedItem} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InterviewSimulationPage;
|
||||
@@ -155,13 +155,13 @@ const JobStrategyDetailPage = () => {
|
||||
>
|
||||
{activeItem === "1" && (
|
||||
<>
|
||||
<TargetPosition />
|
||||
<TargetPosition locked={true} />
|
||||
<div className="slide-wrapper">
|
||||
<div className="slide-text">左滑查看曲线就业方案</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeItem === "2" && <CurvedEmployment />}
|
||||
{activeItem === "2" && <CurvedEmployment locked={true} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,13 @@ const PersonalProfile = () => {
|
||||
// 更新Redux中的学生信息
|
||||
const studentInfo = {
|
||||
...data.studentInfo,
|
||||
myRank: data.ranking.myRank,
|
||||
realName: data.studentInfo.name,
|
||||
studentNo: data.studentInfo.studentId,
|
||||
mbtiType: data.studentInfo.mbti || 'ENFJ',
|
||||
myRank: {
|
||||
...data.ranking.myRank,
|
||||
score: data.studentInfo.credits,
|
||||
},
|
||||
classInfo: data.ranking.classInfo,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,104 +1,7 @@
|
||||
.user-portfolio-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
|
||||
.user-portfolio-wrapper {
|
||||
width: 1120px;
|
||||
height: 790px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
.user-portfolio-search-area {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
|
||||
.ser-portfolio-searc {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #2c7aff;
|
||||
|
||||
span {
|
||||
background-color: #fff;
|
||||
}
|
||||
input {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-portfolio-list {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
padding: 20px 0;
|
||||
|
||||
.user-portfolio-item:nth-child(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.user-portfolio-item {
|
||||
flex-shrink: 0;
|
||||
width: 340px;
|
||||
height: 82px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
background-color: #f7f8fa;
|
||||
border: 1px solid #e5e6eb;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
> span {
|
||||
border: 1px solid #2c7aff;
|
||||
background-color: #e8f3ff;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
color: #2c7aff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.user-portfolio-item-content {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
|
||||
> p {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
> span {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #2c7aff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1,58 +1,17 @@
|
||||
import { Input } from "@arco-design/web-react";
|
||||
import { useState } from "react";
|
||||
import { mockData } from "@/data/mockData";
|
||||
import ProjectCasesModal from "@/pages/ProjectLibraryPage/components/ProjectCasesModal";
|
||||
import "./index.css";
|
||||
|
||||
const InputSearch = Input.Search;
|
||||
const { projectLibrary } = mockData;
|
||||
|
||||
const Portfolio = () => {
|
||||
const [modalData, setModalData] = useState(undefined);
|
||||
const [projectCasesModalVisible, setProjectCasesModalVisible] =
|
||||
useState(false);
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setProjectCasesModalVisible(false);
|
||||
setModalData(undefined);
|
||||
};
|
||||
|
||||
const onSearch = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
const handleClickBtn = (item) => {
|
||||
setModalData(item);
|
||||
setProjectCasesModalVisible(true);
|
||||
console.log("点击了详情按钮");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="user-portfolio-page">
|
||||
<div className="user-portfolio-wrapper">
|
||||
<div className="user-portfolio-search-area">
|
||||
<InputSearch
|
||||
className="ser-portfolio-search"
|
||||
onSearch={onSearch}
|
||||
searchButton="搜索"
|
||||
/>
|
||||
</div>
|
||||
<ul className="user-portfolio-list">
|
||||
{projectLibrary?.projects?.map((item) => (
|
||||
<li className="user-portfolio-item" key={item.id}>
|
||||
<span>{item.subtitle}</span>
|
||||
<div className="user-portfolio-item-content">
|
||||
<p>{item.title}</p>
|
||||
<span onClick={() => handleClickBtn(item)}>详情 > </span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<ProjectCasesModal
|
||||
data={modalData}
|
||||
visible={projectCasesModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
<iframe
|
||||
src="https://du9uay.github.io/personal-resume-wenlv/"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100vh",
|
||||
border: "none",
|
||||
display: "block"
|
||||
}}
|
||||
title="个人简历"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -60,13 +60,15 @@
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 3px;
|
||||
width: 79px;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
opacity: 0.1;
|
||||
border-radius: 5px;
|
||||
@@ -191,17 +193,19 @@
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
||||
.attachment-list-item {
|
||||
width: 337px;
|
||||
min-width: 300px;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: 80px;
|
||||
border: 1px solid #e5e6eb;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
background-color: #f7f8fa;
|
||||
margin-right: 20px;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
padding: 10px 40px 10px 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -223,22 +227,35 @@
|
||||
.attachment-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin-right: 5px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.attachment-info {
|
||||
width: 200px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
margin-right: 10px;
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
line-height: 28px;
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #09090b;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
> span {
|
||||
line-height: 20px;
|
||||
@@ -250,6 +267,122 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Markdown内容样式 */
|
||||
.project-cases-modal-markdown-content {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
color: #1d2129;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #1d2129;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h1 { font-size: 24px; }
|
||||
h2 { font-size: 20px; }
|
||||
h3 { font-size: 18px; }
|
||||
h4 { font-size: 16px; }
|
||||
h5 { font-size: 14px; }
|
||||
h6 { font-size: 14px; }
|
||||
|
||||
p {
|
||||
margin: 8px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 4px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
ol li {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f7f8fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f7f8fa;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #0275f2;
|
||||
padding-left: 16px;
|
||||
margin: 12px 0;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #e5e6eb;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #e5e6eb;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f7f8fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0275f2;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Modal from "@/components/Modal";
|
||||
import PDFICON from "@/assets/images/Common/pdf_icon.png";
|
||||
import FileIcon from "@/components/FileIcon";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ visible, onClose, data }) => {
|
||||
@@ -7,6 +9,13 @@ export default ({ visible, onClose, data }) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 将换行符转换为Markdown格式
|
||||
const formatMarkdownContent = (content) => {
|
||||
if (!content) return "";
|
||||
// 将 \n 替换为实际的换行符
|
||||
return content.replace(/\\n/g, '\n');
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal visible={visible} onClose={handleCloseModal}>
|
||||
<div className="project-cases-modal">
|
||||
@@ -17,81 +26,100 @@ export default ({ visible, onClose, data }) => {
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">项目概述</p>
|
||||
<p className="project-cases-modal-item-text">
|
||||
本项目是长安汽车换代CS95(代号CD569)中的车机系统测试项目,基于腾讯车载互联平台,整合QQ音乐、喜马拉雅FM、酷我音乐、导航、远程控制等29个智能功能模块,
|
||||
旨在为车主提供便捷舒适的智能出行体验。项目中本人重点负责影音娱乐、行车记录及微信APP模块的测试工作,主要通过台架测试与仿真手段,验证系统功能稳定性与使用便捷性。
|
||||
{data?.overview || "暂无项目概述"}
|
||||
</p>
|
||||
</li>
|
||||
{/* 适用岗位 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">适用岗位</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
<li className="high-count-list-item">
|
||||
<span className="high">高阶岗</span>
|
||||
<p>测试工程师</p>
|
||||
</li>
|
||||
<li className="high-count-list-item">
|
||||
<span className="medium">高阶岗</span>
|
||||
<p>测试工程师</p>
|
||||
</li>
|
||||
<li className="high-count-list-item">
|
||||
<span className="low">高阶岗</span>
|
||||
<p>测试工程师</p>
|
||||
</li>
|
||||
<li className="high-count-list-item">
|
||||
<span className="low">高阶岗</span>
|
||||
<p>测试工程师</p>
|
||||
</li>
|
||||
{data?.applicablePositions?.map((pos, index) => (
|
||||
<li key={index} className="high-count-list-item">
|
||||
<span className={pos.level === '初级' ? 'low' : pos.level === '中级' ? 'medium' : 'high'}>
|
||||
{pos.level}
|
||||
</span>
|
||||
<p>{pos.position}</p>
|
||||
</li>
|
||||
)) || (
|
||||
<li className="high-count-list-item">
|
||||
<span className="medium">暂无</span>
|
||||
<p>适用岗位信息</p>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
{/* 对应课程 */}
|
||||
{/* 对应单元 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">适用岗位</p>
|
||||
<p className="project-cases-modal-item-title">对应单元</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
<li className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>物联网就业管家课程</span>
|
||||
</div>
|
||||
</li>
|
||||
{data?.units?.map((unit, index) => (
|
||||
<li key={index} className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>{unit}</span>
|
||||
</div>
|
||||
</li>
|
||||
)) || (
|
||||
<li className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>暂无对应单元信息</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
{/* 项目整体流程介绍 */}
|
||||
{/* 项目整体流程介绍 - Markdown格式 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">项目整体流程介绍</p>
|
||||
<p className="project-cases-modal-item-text">
|
||||
1. 需求分析:
|
||||
测试流程的起点,需深入理解产品需求文档和系统设计说明,明确各功能模块的业务逻辑、接口调用和交互方式。
|
||||
例如,在车载系统中,需要分析导航、影音、远程控制等模块的使用场景、输入输出以及与硬件(如摄像头、音响、控制器)的依赖关系。
|
||||
2. 需求分析:
|
||||
测试流程的起点,需深入理解产品需求文档和系统设计说明,明确各功能模块的业务逻辑、接口调用和交互方式。
|
||||
例如,在车载系统中,需要分析导航、影音、远程控制等模块的使用场景、输入输出以及与硬件(如摄像头、音响、控制器)的依赖关系。
|
||||
</p>
|
||||
<div className="project-cases-modal-markdown-content">
|
||||
{data?.process ? (
|
||||
<ReactMarkdown>{formatMarkdownContent(data.process)}</ReactMarkdown>
|
||||
) : (
|
||||
<p className="project-cases-modal-item-text">暂无项目流程介绍</p>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
{/* 项目案例关键技术点 */}
|
||||
{/* 项目案例关键技术点 - Markdown格式 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">项目案例关键技术点</p>
|
||||
<p className="project-cases-modal-item-text">
|
||||
1. 需求分析: 测试流程的起点{" "}
|
||||
</p>
|
||||
<p className="project-cases-modal-item-text">
|
||||
1. 需求分析: 测试流程的起点{" "}
|
||||
</p>
|
||||
<div className="project-cases-modal-markdown-content">
|
||||
{data?.keyPoints ? (
|
||||
<ReactMarkdown>{formatMarkdownContent(data.keyPoints)}</ReactMarkdown>
|
||||
) : (
|
||||
<p className="project-cases-modal-item-text">暂无关键技术点</p>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
{/* 附件 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">附件</p>
|
||||
<ul className="project-cases-modal-attachment-list">
|
||||
<li className="attachment-list-item">
|
||||
<img src={PDFICON} alt="icon" className="attachment-icon" />
|
||||
<div className="attachment-info">
|
||||
<p>项目测试用例.pdf</p>
|
||||
<span>2220kb</span>
|
||||
</div>
|
||||
</li>
|
||||
{data?.attachments?.map((attachment, index) => (
|
||||
<li key={index} className="attachment-list-item">
|
||||
{attachment.type ? (
|
||||
<FileIcon type={attachment.type} />
|
||||
) : (
|
||||
<img src={PDFICON} alt="icon" className="attachment-icon" />
|
||||
)}
|
||||
<div className="attachment-info">
|
||||
<p>{attachment.name}</p>
|
||||
<span>{attachment.size}</span>
|
||||
</div>
|
||||
</li>
|
||||
)) || (
|
||||
<li className="attachment-list-item">
|
||||
<img src={PDFICON} alt="icon" className="attachment-icon" />
|
||||
<div className="attachment-info">
|
||||
<p>暂无附件</p>
|
||||
<span>0kb</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -64,7 +64,8 @@
|
||||
flex-direction: column;
|
||||
|
||||
.project-library-item-title {
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
border: 1px solid #2c7aff;
|
||||
background-color: #e8f3ff;
|
||||
height: 20px;
|
||||
|
||||
@@ -28,9 +28,8 @@ const ProjectLibrary = () => {
|
||||
if (item?.id) {
|
||||
const res = await getProjectsdetail(item.id);
|
||||
if (res.success) {
|
||||
// todo
|
||||
// setProjectList();
|
||||
// setProjectCasesModalVisible(true);
|
||||
setModalData(res.data);
|
||||
setProjectCasesModalVisible(true);
|
||||
} else {
|
||||
toast.error(res.message);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
.interview-questions-modal-list {
|
||||
width: 100%;
|
||||
max-height: 600px;
|
||||
max-height: 560px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -88,10 +88,23 @@
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
margin-top: 10px;
|
||||
line-height: 1.6;
|
||||
> span {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.interview-questions-modal-meta {
|
||||
font-size: 14px;
|
||||
color: #86909c;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
margin-top: 10px;
|
||||
> span {
|
||||
font-weight: 600;
|
||||
color: #4e5969;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,34 @@ export default ({ visible, onClose, data }) => {
|
||||
<Modal visible={visible} onClose={handleCloseModal}>
|
||||
<div className="interview-questions-modal">
|
||||
<i className="close-icon" onClick={handleCloseModal} />
|
||||
<p className="interview-questions-modal-title">{data?.name}面试题</p>
|
||||
<p className="interview-questions-modal-title">{data?.name}岗位群面试题</p>
|
||||
<p className="interview-questions-modal-subtitle">
|
||||
在1V1定制求职策略阶段,企业HR会为您讲解面试题
|
||||
</p>
|
||||
<ul className="interview-questions-modal-list">
|
||||
<li className="interview-questions-modal-item">
|
||||
<p className="interview-questions-modal-question">
|
||||
<span>问题:</span>
|
||||
{data?.question?.question}
|
||||
</p>
|
||||
<p className="interview-questions-modal-answer">
|
||||
<span>解答:</span>
|
||||
这是一个{data?.question?.difficulty?.toLowerCase()}难度的
|
||||
{data?.question?.category}相关问题。
|
||||
针对这类问题,建议从以下几个方面来回答: 1.
|
||||
理解问题的核心概念和背景 2. 结合实际项目经验进行说明 3.
|
||||
展示对相关技术的深入理解 4. 提及可能的优化或改进方案
|
||||
</p>
|
||||
</li>
|
||||
{data?.questions?.map((question, index) => (
|
||||
<li key={question.id} className="interview-questions-modal-item">
|
||||
<p className="interview-questions-modal-question">
|
||||
<span>问题{index + 1}:</span>
|
||||
{question.question}
|
||||
</p>
|
||||
<p className="interview-questions-modal-answer">
|
||||
<span>解答:</span>
|
||||
{question.answer}
|
||||
</p>
|
||||
{question.difficulty && (
|
||||
<p className="interview-questions-modal-meta">
|
||||
<span>难度:</span>{question.difficulty}
|
||||
{question.tags && question.tags.length > 0 && (
|
||||
<>
|
||||
<span style={{marginLeft: '20px'}}>标签:</span>
|
||||
{question.tags.join('、')}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
height: 56px;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
|
||||
.active {
|
||||
color: #2c7aff !important;
|
||||
@@ -31,20 +32,45 @@
|
||||
.navigation-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
}
|
||||
|
||||
.resume-interview-navigation-item {
|
||||
margin-right: 20px;
|
||||
width: 101px;
|
||||
margin-right: 12px;
|
||||
min-width: fit-content;
|
||||
white-space: nowrap;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100px;
|
||||
padding: 5 16px;
|
||||
padding: 0 20px;
|
||||
font-size: 14px;
|
||||
color: #4e5969;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: #f7f8fa;
|
||||
color: #2c7aff;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,196 +1,222 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
import toast from "@/components/Toast";
|
||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||
import ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
|
||||
import { getPageData } from "@/services/resumeInterview";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const ResumeInterviewPage = () => {
|
||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||
const [interviewModalVisible, setInterviewModalVisible] = useState(false);
|
||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||
const [interviewModalData, setInterviewModalData] = useState(undefined);
|
||||
const [resumeModalData, setResumeModalData] = useState(undefined);
|
||||
const [pageData, setPageData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const sectionsRef = useRef({});
|
||||
|
||||
// 导航到指定行业段落
|
||||
const handleNavClick = (industryId) => {
|
||||
setActiveIndustry(industryId);
|
||||
sectionsRef.current[industryId]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
};
|
||||
|
||||
// 面试题点击处理
|
||||
const handleQuestionClick = (item) => {
|
||||
if (item) {
|
||||
setInterviewModalVisible(true);
|
||||
setInterviewModalData(item);
|
||||
} else {
|
||||
toast.error("加载数据失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 职位点击处理
|
||||
const handlePositionClick = (position, industry) => {
|
||||
// Find resume templates for this industry
|
||||
const templates = pageData.resumeTemplates[industry.name] || [];
|
||||
const selectedTemplate =
|
||||
templates.find((t) => t.level === position.level) || templates[0];
|
||||
|
||||
setResumeModalData({
|
||||
selectedTemplate,
|
||||
studentResume: pageData.myResume,
|
||||
});
|
||||
setResumeModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCloseInterviewModal = () => {
|
||||
setInterviewModalVisible(false);
|
||||
setInterviewModalData(undefined);
|
||||
};
|
||||
|
||||
const handleCloseResumeModal = () => {
|
||||
setResumeModalVisible(false);
|
||||
setResumeModalData(undefined);
|
||||
};
|
||||
|
||||
const filterPositions = (positions) => {
|
||||
return positions.filter((position) => position.name.toLowerCase());
|
||||
};
|
||||
|
||||
// 获取页面数据
|
||||
useEffect(() => {
|
||||
const fetchPageData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getPageData();
|
||||
if (response.success) {
|
||||
setPageData(response.data);
|
||||
// 设置默认选中第一个行业
|
||||
if (response.data.industries && response.data.industries.length > 0) {
|
||||
setActiveIndustry(response.data.industries[0].id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch page data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageData();
|
||||
}, []);
|
||||
|
||||
// 监听滚动位置更新导航状态
|
||||
useEffect(() => {
|
||||
if (!pageData?.industries) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
const scrollPosition = window.scrollY + 200;
|
||||
|
||||
pageData.industries.forEach((industry) => {
|
||||
const section = sectionsRef.current[industry.id];
|
||||
if (section) {
|
||||
const sectionTop = section.offsetTop;
|
||||
const sectionBottom = sectionTop + section.offsetHeight;
|
||||
|
||||
if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
|
||||
setActiveIndustry(industry.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [pageData?.industries]);
|
||||
|
||||
return (
|
||||
<div className="resume-interview-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="resume-interview-spin" />
|
||||
) : pageData ? (
|
||||
<>
|
||||
<ul className="resume-interview-navigation">
|
||||
<div className="navigation-tabs">
|
||||
{pageData.industries.map((industry) => (
|
||||
<li
|
||||
key={industry.id}
|
||||
className={`resume-interview-navigation-item ${
|
||||
activeIndustry === industry.id ? "active" : ""
|
||||
}`}
|
||||
onClick={() => handleNavClick(industry.id)}
|
||||
>
|
||||
{industry.name}
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
</ul>
|
||||
<ul className="resume-interview-content-wrapper">
|
||||
{pageData.industries.map((item) => (
|
||||
<li
|
||||
className="resume-interview-content-item-wrapper"
|
||||
key={item.id}
|
||||
ref={(el) => (sectionsRef.current[item.id] = el)}
|
||||
>
|
||||
<p className="item-title">{item.name}</p>
|
||||
<p className="item-subtitle">简历与面试题</p>
|
||||
<div className="item-content-wrapper">
|
||||
<ul className="jobs-list">
|
||||
{filterPositions(item.positions).map((position) => (
|
||||
<li
|
||||
className="job-item job-item-change"
|
||||
key={position.id}
|
||||
onClick={() => handlePositionClick(position, item)}
|
||||
>
|
||||
<span>{position.level}</span>
|
||||
<div className="job-name">
|
||||
<p>{position.name}</p>
|
||||
<span>详情 ></span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="resumes-list">
|
||||
{item.questions.map((question) => (
|
||||
<li
|
||||
key={question.id}
|
||||
className="resume-item"
|
||||
onClick={() =>
|
||||
handleQuestionClick({ ...item, question })
|
||||
}
|
||||
>
|
||||
<p>{question.question}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<Empty description="暂无数据" className="empty-data" />
|
||||
)}
|
||||
|
||||
<InterviewQuestionsModal
|
||||
visible={interviewModalVisible}
|
||||
onClose={handleCloseInterviewModal}
|
||||
data={interviewModalData}
|
||||
/>
|
||||
<ResumeInfoModal
|
||||
visible={resumeModalVisible}
|
||||
onClose={handleCloseResumeModal}
|
||||
data={resumeModalData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResumeInterviewPage;
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
import toast from "@/components/Toast";
|
||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||
import ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
|
||||
import { getPageData } from "@/services/resumeInterview";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const ResumeInterviewPage = () => {
|
||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||
const [interviewModalVisible, setInterviewModalVisible] = useState(false);
|
||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||
const [interviewModalData, setInterviewModalData] = useState(undefined);
|
||||
const [resumeModalData, setResumeModalData] = useState(undefined);
|
||||
const [pageData, setPageData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const sectionsRef = useRef({});
|
||||
|
||||
// 导航到指定行业段落
|
||||
const handleNavClick = (industryId) => {
|
||||
setActiveIndustry(industryId);
|
||||
sectionsRef.current[industryId]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
};
|
||||
|
||||
// 面试题点击处理
|
||||
const handleQuestionClick = (item) => {
|
||||
if (item) {
|
||||
setInterviewModalVisible(true);
|
||||
setInterviewModalData(item);
|
||||
} else {
|
||||
toast.error("加载数据失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 职位点击处理
|
||||
const handlePositionClick = (position, industry) => {
|
||||
// Find resume templates for this industry
|
||||
const templates = pageData.resumeTemplates[industry.name] || [];
|
||||
// 首先根据岗位名称精确匹配
|
||||
const selectedTemplate =
|
||||
templates.find((t) => t.position === position.title) ||
|
||||
templates.find((t) => t.level === position.level) ||
|
||||
templates[0];
|
||||
|
||||
setResumeModalData({
|
||||
selectedTemplate,
|
||||
studentResume: pageData.myResume,
|
||||
});
|
||||
setResumeModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCloseInterviewModal = () => {
|
||||
setInterviewModalVisible(false);
|
||||
setInterviewModalData(undefined);
|
||||
};
|
||||
|
||||
const handleCloseResumeModal = () => {
|
||||
setResumeModalVisible(false);
|
||||
setResumeModalData(undefined);
|
||||
};
|
||||
|
||||
const filterPositions = (positions) => {
|
||||
return positions.filter((position) => position.title?.toLowerCase());
|
||||
};
|
||||
|
||||
// 获取页面数据
|
||||
useEffect(() => {
|
||||
const fetchPageData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getPageData();
|
||||
if (response.success) {
|
||||
console.log('页面数据加载成功:', response.data);
|
||||
setPageData(response.data);
|
||||
// 设置默认选中第一个行业
|
||||
if (response.data.industries && response.data.industries.length > 0) {
|
||||
setActiveIndustry(response.data.industries[0].id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch page data:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPageData();
|
||||
}, []);
|
||||
|
||||
// 监听滚动位置更新导航状态
|
||||
useEffect(() => {
|
||||
if (!pageData?.industries) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
const scrollPosition = window.scrollY + 200;
|
||||
|
||||
pageData.industries.forEach((industry) => {
|
||||
const section = sectionsRef.current[industry.id];
|
||||
if (section) {
|
||||
const sectionTop = section.offsetTop;
|
||||
const sectionBottom = sectionTop + section.offsetHeight;
|
||||
|
||||
if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
|
||||
setActiveIndustry(industry.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [pageData?.industries]);
|
||||
|
||||
// 添加鼠标滚轮横向滚动功能
|
||||
useEffect(() => {
|
||||
const navigation = document.querySelector('.resume-interview-navigation');
|
||||
if (!navigation) return;
|
||||
|
||||
const handleWheel = (e) => {
|
||||
const tabs = navigation.querySelector('.navigation-tabs');
|
||||
if (!tabs) return;
|
||||
|
||||
// 检查是否在导航栏区域
|
||||
if (e.currentTarget === navigation || navigation.contains(e.target)) {
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
e.preventDefault();
|
||||
tabs.scrollLeft += e.deltaY * 0.5; // 减慢滚动速度使其更平滑
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
navigation.addEventListener('wheel', handleWheel, { passive: false });
|
||||
return () => navigation.removeEventListener('wheel', handleWheel);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="resume-interview-page">
|
||||
{loading ? (
|
||||
<Spin size={80} className="resume-interview-spin" />
|
||||
) : pageData ? (
|
||||
<>
|
||||
<ul className="resume-interview-navigation">
|
||||
<div className="navigation-tabs">
|
||||
{pageData.industries.map((industry) => (
|
||||
<li
|
||||
key={industry.id}
|
||||
className={`resume-interview-navigation-item ${
|
||||
activeIndustry === industry.id ? "active" : ""
|
||||
}`}
|
||||
onClick={() => handleNavClick(industry.id)}
|
||||
>
|
||||
{industry.name}
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
</ul>
|
||||
<ul className="resume-interview-content-wrapper">
|
||||
{pageData.industries.map((item) => (
|
||||
<li
|
||||
className="resume-interview-content-item-wrapper"
|
||||
key={item.id}
|
||||
ref={(el) => (sectionsRef.current[item.id] = el)}
|
||||
>
|
||||
<p className="item-title">{item.name}</p>
|
||||
<p className="item-subtitle">简历与面试题</p>
|
||||
<div className="item-content-wrapper">
|
||||
<ul className="jobs-list">
|
||||
{filterPositions(item.positions).map((position) => (
|
||||
<li
|
||||
className="job-item job-item-change"
|
||||
key={position.id}
|
||||
onClick={() => handlePositionClick(position, item)}
|
||||
>
|
||||
<span>{position.level}</span>
|
||||
<div className="job-name">
|
||||
<p>{position.title}</p>
|
||||
<span>详情 ></span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="resumes-list">
|
||||
{item.questions.map((question) => (
|
||||
<li
|
||||
key={question.id}
|
||||
className="resume-item"
|
||||
onClick={() =>
|
||||
handleQuestionClick({ ...item, questions: question.subQuestions || [question] })
|
||||
}
|
||||
>
|
||||
<p>{question.question}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<Empty description="暂无数据" className="empty-data" />
|
||||
)}
|
||||
|
||||
<InterviewQuestionsModal
|
||||
visible={interviewModalVisible}
|
||||
onClose={handleCloseInterviewModal}
|
||||
data={interviewModalData}
|
||||
/>
|
||||
<ResumeInfoModal
|
||||
visible={resumeModalVisible}
|
||||
onClose={handleCloseResumeModal}
|
||||
data={resumeModalData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResumeInterviewPage;
|
||||
|
||||
323
src/pages/ResumeInterviewPage/staticData.js
Normal file
323
src/pages/ResumeInterviewPage/staticData.js
Normal file
@@ -0,0 +1,323 @@
|
||||
// 静态数据 - 直接定义在组件中,不依赖外部接口
|
||||
|
||||
export const staticPageData = {
|
||||
// 行业列表
|
||||
industries: [
|
||||
{
|
||||
id: "homestay",
|
||||
name: "民宿经营",
|
||||
positions: [
|
||||
{
|
||||
id: "homestay_1",
|
||||
title: "民宿管家",
|
||||
level: "初级",
|
||||
department: "民宿经营",
|
||||
type: "全职",
|
||||
experience: "1-3年",
|
||||
education: "本科",
|
||||
salary: "6-10K",
|
||||
location: "北京"
|
||||
},
|
||||
{
|
||||
id: "homestay_2",
|
||||
title: "民宿客房管家",
|
||||
level: "中级",
|
||||
department: "民宿经营",
|
||||
type: "全职",
|
||||
experience: "1-3年",
|
||||
education: "本科",
|
||||
salary: "8-12K",
|
||||
location: "北京"
|
||||
},
|
||||
{
|
||||
id: "homestay_3",
|
||||
title: "民宿运营专员",
|
||||
level: "高级",
|
||||
department: "民宿经营",
|
||||
type: "全职",
|
||||
experience: "3-5年",
|
||||
education: "本科",
|
||||
salary: "10-15K",
|
||||
location: "北京"
|
||||
}
|
||||
],
|
||||
questions: [
|
||||
{
|
||||
id: "homestay_q1",
|
||||
question: "民宿行业面试题",
|
||||
subQuestions: [
|
||||
{
|
||||
question: "如何提升民宿的入住率?",
|
||||
answer: "通过优化OTA平台展示、提升服务质量、季节性定价策略等方式提升入住率。"
|
||||
},
|
||||
{
|
||||
question: "如何处理客户投诉?",
|
||||
answer: "及时响应、诚恳道歉、解决问题、适当补偿、后续跟进。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "hotel",
|
||||
name: "酒店经营",
|
||||
positions: [
|
||||
{
|
||||
id: "hotel_1",
|
||||
title: "酒店餐饮主管",
|
||||
level: "中级",
|
||||
department: "酒店经营",
|
||||
type: "全职",
|
||||
experience: "3-5年",
|
||||
education: "本科",
|
||||
salary: "10-15K",
|
||||
location: "北京"
|
||||
},
|
||||
{
|
||||
id: "hotel_2",
|
||||
title: "酒店大堂副理",
|
||||
level: "高级",
|
||||
department: "酒店经营",
|
||||
type: "全职",
|
||||
experience: "3-5年",
|
||||
education: "本科",
|
||||
salary: "12-18K",
|
||||
location: "北京"
|
||||
},
|
||||
{
|
||||
id: "hotel_3",
|
||||
title: "餐厅运营经理",
|
||||
level: "高级",
|
||||
department: "酒店经营",
|
||||
type: "全职",
|
||||
experience: "5年以上",
|
||||
education: "本科",
|
||||
salary: "15-20K",
|
||||
location: "北京"
|
||||
}
|
||||
],
|
||||
questions: [
|
||||
{
|
||||
id: "hotel_q1",
|
||||
question: "酒店管理面试题",
|
||||
subQuestions: [
|
||||
{
|
||||
question: "如何提升酒店服务质量?",
|
||||
answer: "建立服务标准、员工培训、客户反馈机制、持续改进。"
|
||||
},
|
||||
{
|
||||
question: "如何管理酒店团队?",
|
||||
answer: "明确职责、激励机制、培训发展、团队建设。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// 简历模板
|
||||
resumeTemplates: {
|
||||
"民宿经营": [
|
||||
{
|
||||
position: "民宿管家",
|
||||
level: "初级",
|
||||
studentInfo: {
|
||||
educational_experience: ["旅游管理专业 本科", "XX大学 2020-2024"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "民宿运营管理项目",
|
||||
description: "负责民宿日常运营管理,包括客房清洁、客户服务、订单管理等工作,提升客户满意度和复购率。"
|
||||
},
|
||||
{
|
||||
name: "民宿平台运营实习",
|
||||
description: "在XX民宿平台实习期间,负责房源上架、价格调整、评价管理等工作,熟悉OTA平台运营流程。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"熟悉民宿运营管理流程",
|
||||
"掌握OTA平台操作",
|
||||
"良好的客户服务意识",
|
||||
"具备基础的财务管理能力",
|
||||
"了解民宿相关法规政策"
|
||||
],
|
||||
compound_skills: [
|
||||
"新媒体营销能力:能够运营社交媒体账号,制作推广内容",
|
||||
"数据分析能力:能够分析入住率、客单价等运营数据",
|
||||
"危机处理能力:能够妥善处理客户投诉和突发事件",
|
||||
"团队协作能力:能够与清洁、维修等团队良好配合"
|
||||
],
|
||||
personal_summary: "本人毕业于旅游管理专业,对民宿行业充满热情。通过实习和项目经历,积累了丰富的民宿运营经验,熟悉OTA平台操作和客户服务流程。希望能在民宿管家岗位上发挥专业优势,为客户提供优质的住宿体验。"
|
||||
}
|
||||
},
|
||||
{
|
||||
position: "民宿客房管家",
|
||||
level: "中级",
|
||||
studentInfo: {
|
||||
educational_experience: ["酒店管理专业 本科", "XX大学 2019-2023"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "精品民宿客房管理项目",
|
||||
description: "负责20间客房的日常管理,制定清洁标准和流程,培训清洁人员,确保客房品质达到五星级标准。"
|
||||
},
|
||||
{
|
||||
name: "民宿品质提升项目",
|
||||
description: "参与民宿软装升级项目,优化客房布局和用品配置,提升客户入住体验,好评率提升15%。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"精通客房管理标准和流程",
|
||||
"掌握客房清洁和布置技巧",
|
||||
"熟悉客房用品采购和管理",
|
||||
"具备团队管理能力",
|
||||
"了解客房安全和卫生规范"
|
||||
],
|
||||
compound_skills: [
|
||||
"审美设计能力:能够进行客房软装搭配和空间优化",
|
||||
"成本控制能力:能够控制客房运营成本,提高利润率",
|
||||
"培训能力:能够培训和指导清洁团队",
|
||||
"质量管理能力:建立和执行客房质量检查体系"
|
||||
],
|
||||
personal_summary: "具有扎实的酒店管理专业背景,专注于客房管理领域。通过系统学习和实践,掌握了客房管理的各个环节,能够独立负责民宿客房的运营管理。追求细节完美,致力于为客户创造舒适的居住环境。"
|
||||
}
|
||||
},
|
||||
{
|
||||
position: "民宿运营专员",
|
||||
level: "高级",
|
||||
studentInfo: {
|
||||
educational_experience: ["旅游管理专业 本科", "XX大学 2018-2022", "工商管理 辅修"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "连锁民宿运营管理",
|
||||
description: "负责5家连锁民宿的整体运营,包括营销推广、收益管理、服务标准制定等,年营收增长30%。"
|
||||
},
|
||||
{
|
||||
name: "民宿品牌建设项目",
|
||||
description: "参与民宿品牌定位和营销策略制定,建立会员体系,提升品牌知名度和客户忠诚度。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"精通民宿运营管理全流程",
|
||||
"掌握收益管理和定价策略",
|
||||
"熟悉数字化营销和推广",
|
||||
"具备数据分析和决策能力",
|
||||
"了解民宿行业发展趋势"
|
||||
],
|
||||
compound_skills: [
|
||||
"战略规划能力:能够制定民宿发展战略和运营计划",
|
||||
"品牌营销能力:能够进行品牌定位和营销推广",
|
||||
"财务管理能力:能够进行预算管理和成本控制",
|
||||
"项目管理能力:能够管理多个民宿项目同时运营",
|
||||
"商务谈判能力:能够与供应商、合作方进行商务谈判"
|
||||
],
|
||||
personal_summary: "拥有旅游管理和工商管理双重背景,对民宿运营有深入理解。通过多年实践,积累了丰富的民宿运营和管理经验,擅长通过数据分析优化运营策略,提升经营效益。希望在民宿运营专员岗位上,推动民宿品牌化、连锁化发展。"
|
||||
}
|
||||
}
|
||||
],
|
||||
"酒店经营": [
|
||||
{
|
||||
position: "酒店餐饮主管",
|
||||
level: "中级",
|
||||
studentInfo: {
|
||||
educational_experience: ["酒店管理专业 本科", "XX大学 2018-2022"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "五星级酒店餐饮部实习",
|
||||
description: "在XX五星级酒店餐饮部实习6个月,参与西餐厅和中餐厅的运营管理,协助制定服务标准和流程。"
|
||||
},
|
||||
{
|
||||
name: "酒店宴会服务项目",
|
||||
description: "负责大型宴会的策划和执行,包括婚宴、商务宴请等,成功服务超过50场宴会活动。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"熟悉餐饮运营管理",
|
||||
"掌握食品安全标准",
|
||||
"了解成本控制方法",
|
||||
"具备团队管理能力",
|
||||
"熟悉餐饮服务礼仪"
|
||||
],
|
||||
compound_skills: [
|
||||
"菜单设计能力:能够根据市场需求设计菜单",
|
||||
"供应链管理:了解食材采购和库存管理",
|
||||
"活动策划能力:能够策划主题餐饮活动",
|
||||
"客户关系维护:建立和维护VIP客户关系"
|
||||
],
|
||||
personal_summary: "酒店管理专业毕业,对餐饮管理充满热情。通过系统学习和实习经历,掌握了餐饮运营的各个环节。注重服务品质和客户体验,致力于打造优质的餐饮服务。"
|
||||
}
|
||||
},
|
||||
{
|
||||
position: "酒店大堂副理",
|
||||
level: "高级",
|
||||
studentInfo: {
|
||||
educational_experience: ["酒店管理专业 本科", "XX大学 2017-2021", "英语专业 辅修"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "国际品牌酒店前厅管理",
|
||||
description: "在XX国际品牌酒店担任前厅主管,管理前台、礼宾、行李等部门,确保客户服务质量。"
|
||||
},
|
||||
{
|
||||
name: "酒店服务质量提升项目",
|
||||
description: "主导实施服务质量提升项目,建立服务标准体系,客户满意度提升至95%以上。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"精通前厅运营管理",
|
||||
"掌握客户服务标准",
|
||||
"熟悉PMS系统操作",
|
||||
"具备危机处理能力",
|
||||
"流利的中英文沟通"
|
||||
],
|
||||
compound_skills: [
|
||||
"跨部门协调能力:能够协调各部门资源满足客户需求",
|
||||
"培训管理能力:制定培训计划,提升团队服务水平",
|
||||
"收益管理:了解房价策略和收益优化",
|
||||
"品牌管理:维护酒店品牌形象和声誉"
|
||||
],
|
||||
personal_summary: "具有扎实的酒店管理专业背景和国际视野,熟悉国际品牌酒店运营标准。通过多年前厅管理经验,培养了卓越的服务意识和管理能力。追求服务excellence,致力于为客户创造难忘的入住体验。"
|
||||
}
|
||||
},
|
||||
{
|
||||
position: "餐厅运营经理",
|
||||
level: "高级",
|
||||
studentInfo: {
|
||||
educational_experience: ["工商管理专业 本科", "XX大学 2016-2020", "餐饮管理 专业认证"],
|
||||
project_experience: [
|
||||
{
|
||||
name: "连锁餐厅运营管理",
|
||||
description: "负责3家连锁餐厅的整体运营,包括人员管理、成本控制、营销推广等,年营业额超过2000万。"
|
||||
},
|
||||
{
|
||||
name: "餐厅品牌升级项目",
|
||||
description: "主导餐厅品牌升级项目,包括菜单优化、装修改造、服务流程再造,营业额增长40%。"
|
||||
}
|
||||
],
|
||||
core_skills: [
|
||||
"精通餐厅运营管理",
|
||||
"掌握财务分析和预算",
|
||||
"熟悉食品成本控制",
|
||||
"具备市场营销能力",
|
||||
"了解餐饮行业法规"
|
||||
],
|
||||
compound_skills: [
|
||||
"战略规划:制定餐厅发展战略和扩张计划",
|
||||
"供应链优化:建立稳定的供应商体系",
|
||||
"数字化运营:运用数据分析优化运营决策",
|
||||
"品牌建设:打造餐厅品牌和文化",
|
||||
"投资分析:评估新店投资回报率"
|
||||
],
|
||||
personal_summary: "工商管理背景,专注餐饮行业多年。具有丰富的餐厅运营和管理经验,擅长通过精细化管理提升运营效率和盈利能力。注重创新和品质,致力于打造有竞争力的餐饮品牌。"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 我的简历
|
||||
myResume: {
|
||||
name: "张三",
|
||||
position: "应聘岗位",
|
||||
education: "本科",
|
||||
major: "旅游管理",
|
||||
skills: ["运营管理", "客户服务", "数据分析"],
|
||||
experience: "具有相关实习经验"
|
||||
}
|
||||
};
|
||||
@@ -1,47 +1,135 @@
|
||||
import request from "@/utils/request";
|
||||
import { mockData } from "@/data/mockData";
|
||||
|
||||
// 获取企业内推岗位页面聚合数据
|
||||
export async function getCompanyJobsPageData(params) {
|
||||
return request({
|
||||
url: `/api/company-jobs/page-data`,
|
||||
method: "GET",
|
||||
params,
|
||||
// 使用mock数据
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: {
|
||||
jobs: {
|
||||
list: mockData.companyJobs.companyPositions || [],
|
||||
hasMore: false,
|
||||
total: mockData.companyJobs.companyPositions?.length || 0
|
||||
},
|
||||
interviews: {
|
||||
list: mockData.companyJobs.internalPositions || [],
|
||||
hasMore: false,
|
||||
total: mockData.companyJobs.internalPositions?.length || 0
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取企业内推岗位
|
||||
export async function getJobsList(params) {
|
||||
return request({
|
||||
url: `/api/jobs`,
|
||||
method: "GET",
|
||||
params,
|
||||
// 使用mock数据
|
||||
const allJobs = mockData.companyJobs.companyPositions || [];
|
||||
const page = params.page || 1;
|
||||
const pageSize = params.pageSize || 10;
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const pageJobs = allJobs.slice(startIndex, endIndex);
|
||||
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: pageJobs,
|
||||
total: allJobs.length,
|
||||
page: page,
|
||||
pageSize: pageSize
|
||||
});
|
||||
}
|
||||
|
||||
// 获取企业内推岗位详情
|
||||
export async function getJobsDetail(id) {
|
||||
return request({
|
||||
url: `/api/jobs/${id}`,
|
||||
method: "GET",
|
||||
});
|
||||
// 使用mock数据
|
||||
const allJobs = mockData.companyJobs.companyPositions || [];
|
||||
const job = allJobs.find(job => job.id === id);
|
||||
|
||||
if (job) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: job
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
message: "岗位不存在"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取企业内推岗位面试
|
||||
export async function getInterviewsList(params) {
|
||||
return request({
|
||||
url: `/api/interviews`,
|
||||
method: "GET",
|
||||
params,
|
||||
// 使用mock数据
|
||||
const allInterviews = mockData.companyJobs.internalPositions || [];
|
||||
const page = params.page || 1;
|
||||
const pageSize = params.pageSize || 10;
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const pageInterviews = allInterviews.slice(startIndex, endIndex);
|
||||
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: pageInterviews,
|
||||
total: allInterviews.length,
|
||||
page: page,
|
||||
pageSize: pageSize
|
||||
});
|
||||
}
|
||||
|
||||
// 获取简历列表
|
||||
export async function getResumesList(params) {
|
||||
return request({
|
||||
url: `/api/resumes`,
|
||||
method: "GET",
|
||||
params: params,
|
||||
});
|
||||
try {
|
||||
// 获取岗位与面试题页面的数据
|
||||
const { getMockPageData } = await import("@/mocks/resumeInterviewMock");
|
||||
const pageData = getMockPageData();
|
||||
|
||||
// 收集所有行业的所有简历模板
|
||||
const allResumeTemplates = [];
|
||||
|
||||
if (pageData.industries && pageData.resumeTemplates) {
|
||||
pageData.industries.forEach(industry => {
|
||||
const templates = pageData.resumeTemplates[industry.name] || [];
|
||||
templates.forEach(template => {
|
||||
allResumeTemplates.push({
|
||||
id: `template_${industry.id}_${template.position}`,
|
||||
title: `${template.position}简历`, // 使用岗位名称作为简历标题
|
||||
position: template.position,
|
||||
industry: industry.name,
|
||||
level: template.level || "初级",
|
||||
skills: template.studentInfo?.core_skills || [],
|
||||
template: template
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const page = params.page || 1;
|
||||
const pageSize = params.pageSize || 10;
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const pageResumes = allResumeTemplates.slice(startIndex, endIndex);
|
||||
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: pageResumes,
|
||||
total: allResumeTemplates.length,
|
||||
page: page,
|
||||
pageSize: pageSize
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取简历列表失败:', error);
|
||||
// 如果获取失败,返回空列表
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
}
|
||||
}
|
||||
// 获取简历详情
|
||||
export async function getResumesDetail(id) {
|
||||
@@ -50,3 +138,21 @@ export async function getResumesDetail(id) {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
// 投递简历
|
||||
export async function submitResume(params) {
|
||||
// 模拟投递请求
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: "投递成功",
|
||||
data: {
|
||||
applicationId: Date.now(), // 模拟申请ID
|
||||
submittedAt: new Date().toISOString(),
|
||||
status: "submitted"
|
||||
}
|
||||
});
|
||||
}, 800); // 模拟网络延迟
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,8 +2,23 @@ import request from "@/utils/request";
|
||||
|
||||
// 获取当前登录学生信息
|
||||
export async function getLoginStudentInfo() {
|
||||
return request({
|
||||
url: `/api/students/me`,
|
||||
method: "GET",
|
||||
// 使用mock数据,避免API请求失败
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: {
|
||||
id: "1",
|
||||
name: "张三",
|
||||
studentId: "2021001",
|
||||
major: "旅游管理",
|
||||
class: "旅游管理2021级1班",
|
||||
email: "zhangsan@example.com",
|
||||
phone: "13800138000"
|
||||
}
|
||||
});
|
||||
|
||||
// 原API请求代码(暂时注释)
|
||||
// return request({
|
||||
// url: `/api/students/me`,
|
||||
// method: "GET",
|
||||
// });
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getInterviewsList,
|
||||
getResumesList,
|
||||
getResumesDetail,
|
||||
submitResume,
|
||||
} from "./companyJobs";
|
||||
import { getLoginStudentInfo } from "./global";
|
||||
import {
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
getMyRanking,
|
||||
getProfileOverview,
|
||||
} from "./personalProfile";
|
||||
import {} from "./resumeInterview";
|
||||
import { getPageData } from "./resumeInterview";
|
||||
|
||||
export {
|
||||
// 仪表盘相关
|
||||
@@ -49,4 +50,8 @@ export {
|
||||
getInterviewsList, // 获取面试列表
|
||||
getResumesList, // 获取简历列表
|
||||
getResumesDetail, // 获取简历详情
|
||||
submitResume, // 投递简历
|
||||
|
||||
// 简历面试相关
|
||||
getPageData, // 获取岗位与面试题页面数据
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import request from "@/utils/request";
|
||||
import { mockData } from "@/data/mockData";
|
||||
|
||||
// 获取当前登录学生学习进度
|
||||
export async function getLoginStudentProgress() {
|
||||
@@ -10,11 +11,18 @@ export async function getLoginStudentProgress() {
|
||||
|
||||
// 获取仪表板统计信息
|
||||
export async function getDashboardStatistics() {
|
||||
return request({
|
||||
url: `/api/dashboard/stats`,
|
||||
method: "GET",
|
||||
namespace: "globalLoading",
|
||||
// 直接返回mockData中的数据
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: mockData.dashboardStatistics,
|
||||
});
|
||||
|
||||
// 原API调用(暂时注释)
|
||||
// return request({
|
||||
// url: `/api/dashboard/stats`,
|
||||
// method: "GET",
|
||||
// namespace: "globalLoading",
|
||||
// });
|
||||
}
|
||||
|
||||
// 获取当前学生班级排名
|
||||
@@ -36,8 +44,15 @@ export async function getMyRanking() {
|
||||
|
||||
// 获取个人档案完整数据 (新接口)
|
||||
export async function getProfileOverview() {
|
||||
return request({
|
||||
url: `/api/profile/overview`,
|
||||
method: "GET",
|
||||
// 直接返回mockData中的数据
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
data: mockData.profileOverview,
|
||||
});
|
||||
|
||||
// 原API调用(暂时注释)
|
||||
// return request({
|
||||
// url: `/api/profile/overview`,
|
||||
// method: "GET",
|
||||
// });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import request from "@/utils/request";
|
||||
import { getMockProjectsList, getMockProjectDetail } from "@/mocks/projectLibraryMock";
|
||||
|
||||
// 获取项目列表
|
||||
export async function getProjectsList(params) {
|
||||
const USE_MOCK = true;
|
||||
if (USE_MOCK) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(getMockProjectsList(params));
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
return request({
|
||||
url: `/api/projects`,
|
||||
method: "GET",
|
||||
@@ -10,6 +20,14 @@ export async function getProjectsList(params) {
|
||||
|
||||
// 获取项目详情
|
||||
export async function getProjectsdetail(id) {
|
||||
const USE_MOCK = true;
|
||||
if (USE_MOCK) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(getMockProjectDetail(id));
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
return request({
|
||||
url: `/api/projects/${id}`,
|
||||
method: "GET",
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import request from "@/utils/request";
|
||||
import { getMockPageData } from "@/mocks/resumeInterviewMock";
|
||||
|
||||
// Get all page data for resume-interview page
|
||||
export const getPageData = () => {
|
||||
export const getPageData = async () => {
|
||||
// 使用mock数据
|
||||
const USE_MOCK = true;
|
||||
|
||||
if (USE_MOCK) {
|
||||
// 包装mock数据以匹配API返回格式
|
||||
const mockData = getMockPageData();
|
||||
return {
|
||||
success: true,
|
||||
data: mockData
|
||||
};
|
||||
}
|
||||
|
||||
return request({
|
||||
url: "/api/resume-interview",
|
||||
method: "GET"
|
||||
|
||||
Reference in New Issue
Block a user