feat: 🎸 接口对接调整

This commit is contained in:
2025-08-26 10:54:12 +08:00
parent 44a94b861a
commit 428b880970
21 changed files with 223 additions and 292 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState, useCallback } from "react";
import { Empty, Spin } from "@arco-design/web-react";
import "./index.css";
@@ -13,18 +13,27 @@ const InfiniteScroll = ({
const containerRef = useRef(null);
const sentinelRef = useRef(null);
const observerRef = useRef(null);
const throttleRef = useRef(null); // 节流控制
const [loading, setLoading] = useState(false);
const [hasInitialized, setHasInitialized] = useState(false); // 首次挂载
// 加载更多数据的处理函数
const handleLoadMore = () => {
// 加载更多数据的处理函数(带节流)
const handleLoadMore = useCallback(() => {
if (loading || !hasMore) return;
setLoading(true);
loadMore().finally(() => {
setLoading(false);
});
};
// 节流处理500ms内只能触发一次
if (throttleRef.current) {
clearTimeout(throttleRef.current);
}
throttleRef.current = setTimeout(() => {
setLoading(true);
loadMore().finally(() => {
setLoading(false);
throttleRef.current = null;
});
}, 10);
}, [hasMore, loadMore, loading]);
// 设置IntersectionObserver
useEffect(() => {
@@ -59,8 +68,13 @@ const InfiniteScroll = ({
if (observerRef.current) {
observerRef.current.disconnect();
}
// 清理节流定时器
if (throttleRef.current) {
clearTimeout(throttleRef.current);
throttleRef.current = null;
}
};
}, [loadMore, hasMore, threshold, loading]);
}, [loadMore, hasMore, threshold, loading, hasInitialized, handleLoadMore]);
return (
<div

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { Spin } from "@arco-design/web-react";
import { useSelector, useDispatch } from "react-redux";
import { getLoginStudentInfo } from "@/services";
@@ -14,19 +14,19 @@ const Layout = ({ children }) => {
);
const studentInfo = useSelector((state) => state.student.studentInfo);
const queryLoginStudentInfo = async () => {
const queryLoginStudentInfo = useCallback(async () => {
const res = await getLoginStudentInfo();
if (res.success) {
dispatch(setStudentInfo(res.data));
}
};
}, [dispatch]);
// 初始化项目统一获取登录用户信息
useEffect(() => {
if (!studentInfo) {
queryLoginStudentInfo();
}
}, [studentInfo]);
}, [queryLoginStudentInfo, studentInfo]);
return (
<div className="app-layout">

View File

View File

@@ -1,6 +1,9 @@
import { Avatar, Skeleton } from "@arco-design/web-react";
import "./index.css";
const positions = ["item2", "item1", "item3"];
const icons = ["icon2", "icon1", "icon3"];
const Rank = ({ className, data = null, loading = false }) => {
if (loading) {
return (
@@ -11,7 +14,7 @@ const Rank = ({ className, data = null, loading = false }) => {
);
}
if (!data || !data.rankings || data.rankings.length === 0) {
if (!data || !data?.rankings || data?.rankings?.length === 0) {
return (
<div className={`module-class-rank ${className}`}>
<p className="module-class-rank-title">班级排名</p>
@@ -20,15 +23,15 @@ const Rank = ({ className, data = null, loading = false }) => {
);
}
const rankings = data.rankings.slice(0, 6);
const rankings = data?.rankings?.slice(0, 6);
// 安全处理领奖台学生确保至少有3个位置
const podiumStudents = [
rankings[1] || null, // 第2名
rankings[0] || null, // 第1名
rankings[2] || null // 第3名
rankings[0] || null, // 第1名
rankings[2] || null, // 第3名
];
const listStudents = rankings.slice(3);
return (
@@ -37,21 +40,11 @@ const Rank = ({ className, data = null, loading = false }) => {
<ul className="module-class-rank-podium">
{podiumStudents.map((student, index) => {
const positions = ["item2", "item1", "item3"];
const icons = ["icon2", "icon1", "icon3"];
if (!student) {
return (
<li key={`empty-${index}`} className={`module-class-rank-podium-${positions[index]} empty`}>
<div className="module-class-rank-podium-placeholder">
<span>-</span>
</div>
</li>
);
}
return (
<li key={student.studentId} className={`module-class-rank-podium-${positions[index]}`}>
return student ? (
<li
key={student.studentId}
className={`module-class-rank-podium-${positions[index]}`}
>
<Avatar className="module-class-rank-podium-avatar">
{student.avatar ? (
<img alt="avatar" src={student.avatar} />
@@ -64,6 +57,15 @@ const Rank = ({ className, data = null, loading = false }) => {
</span>
<i className={`module-class-rank-podium-${icons[index]}`}></i>
</li>
) : (
<li
key={`empty-${index}`}
className={`module-class-rank-podium-${positions[index]} empty`}
>
<div className="module-class-rank-podium-placeholder">
<span>-</span>
</div>
</li>
);
})}
</ul>

View File

@@ -1,5 +1,6 @@
import { useNavigate, useLocation } from "react-router-dom";
import { Statistic } from "@arco-design/web-react";
import { useSelector } from "react-redux";
import IconFont from "@/components/IconFont";
import Logo from "@/assets/images/Sidebar/logo.png";
import BTNICON from "@/assets/images/Sidebar/btn_icon.png";
@@ -9,6 +10,7 @@ import "./index.css";
const Sidebar = ({ isCollapsed, setIsCollapsed }) => {
const navigate = useNavigate();
const location = useLocation();
const studentInfo = useSelector((state) => state.student.studentInfo);
const handleNavClick = (path) => {
navigate(path);

View File

@@ -1,73 +1,46 @@
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
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 JobList from "@/pages/CompanyJobsPage/components/JobList";
import "./index.css";
const PAGE_SIZE = 20;
const CompanyJobsListPage = () => {
const [jobs, setJobs] = useState([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const navigate = useNavigate();
const [listPage, setListPage] = useState(1);
const [listHasMore, setListHasMore] = useState(true);
useEffect(() => {
fetchJobs();
}, [page]);
const fetchJobs = async () => {
try {
setLoading(true);
const response = await getJobsList({
page,
pageSize: 20,
isActive: true,
const fetchJobs = useCallback(async () => {
const res = await getJobsList({
page: listPage,
pageSize: PAGE_SIZE,
isActive: true,
});
if (res.success) {
const mappedJobs = mapJobList(res.data);
setJobs((prevList) => {
const newList = [...prevList, ...mappedJobs];
if (res.total === newList?.length) {
setListHasMore(false);
} else {
setListPage((prevPage) => prevPage + 1);
}
return newList;
});
const mappedJobs = mapJobList(response.data || response);
setJobs(mappedJobs);
setTotal(response.total || mappedJobs.length);
} catch (error) {
console.error("Failed to fetch jobs:", error);
setJobs([]);
} finally {
setLoading(false);
}
};
if (loading && jobs.length === 0) {
return (
<div
className="company-jobs-list-page-wrapper"
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: "400px",
}}
>
<p>加载中...</p>
</div>
);
}
}, [listPage]);
return (
<ul className="company-jobs-list-page-wrapper">
<InfiniteScroll
loadMore={fetchJobs}
hasMore={listHasMore}
empty={jobs.length === 0}
className="company-jobs-list-page-wrapper"
>
<JobList data={jobs} />
{jobs.length === 0 && !loading && (
<div
style={{
textAlign: "center",
padding: "40px",
color: "#999",
}}
>
暂无岗位信息
</div>
)}
</ul>
</InfiniteScroll>
);
};

View File

@@ -1,10 +1,10 @@
import { useState } from "react";
import { useState, useCallback } 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 ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
import FILEICON from "@/assets/images/CompanyJobsPage/file_icon.png";
import ResumeInfoModal from "../ResumeInfoModal";
import { getResumesList } from "@/services";
import "./index.css";
@@ -24,7 +24,7 @@ export default ({ visible, onClose, data }) => {
onClose();
};
const queryResumeList = async () => {
const queryResumeList = useCallback(async () => {
const res = await getResumesList({
page: listPage,
pageSize: PAGE_SIZE,
@@ -41,7 +41,7 @@ export default ({ visible, onClose, data }) => {
return newList;
});
}
};
}, [listPage, studentInfo?.id]);
// 点击立即投递
const handleClickDeliverBtn = (e) => {
@@ -50,18 +50,19 @@ export default ({ visible, onClose, data }) => {
};
const onSearch = (value) => {
// todo
console.log(value);
};
// 选择简历投递
const userResumesClick = (item) => {
// todo
console.log(item);
};
// 点击简历详情
const userResumesBtnClick = (e, item) => {
e.stopPropagation();
console.log(item);
setResumeInfoModalShow(true);
};
@@ -185,6 +186,7 @@ export default ({ visible, onClose, data }) => {
</Modal>
<ResumeInfoModal
visible={resumeInfoModalShow}
data={null}
onClose={() => setResumeInfoModalShow(false)}
/>
</>

View File

@@ -98,6 +98,7 @@
margin-bottom: 20px;
box-sizing: border-box;
padding: 20px;
list-style: none;
.company-jobs-page-interview-item-info {
width: 100%;

View File

@@ -92,7 +92,7 @@ const EchartsProgress = ({
return () => {
window.removeEventListener("resize", resizeHandler);
};
}, [percent, strokeWidth]);
}, [backgroundColor, percent, progressColor, strokeWidth]);
return <div ref={chartRef} className="progress-chart" />;
};

View File

@@ -12,41 +12,43 @@ const TaskList = ({ tasks = [], selectedDate, loading }) => {
}
const formatDate = (date) => {
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "long",
day: "numeric",
});
};
const getTaskTypeText = (type) => {
const typeMap = {
'HOMEWORK': '作业',
'PROJECT': '项目',
'REPORT': '报告',
'INTERVIEW': '面试',
'OTHER': '其他'
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';
};
// 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>}
{tasks.length > 0 && (
<span className="task-count">({tasks.length})</span>
)}
</p>
{tasks.length === 0 ? (
<div className="no-tasks">
<Empty description="该日无事项" />
@@ -60,11 +62,11 @@ const TaskList = ({ tasks = [], selectedDate, loading }) => {
{item?.teacherAvatar ? (
<img alt="avatar" src={item.teacherAvatar} />
) : (
item?.teacherName?.charAt(0) || 'T'
item?.teacherName?.charAt(0) || "T"
)}
</Avatar>
<span className="module-tasks-item-info-teacher-name">
{item?.teacherName || '未知教师'}
{item?.teacherName || "未知教师"}
</span>
</div>
<div
@@ -86,11 +88,11 @@ const TaskList = ({ tasks = [], selectedDate, loading }) => {
<span className="module-tasks-item-content-info-duration">
{item?.duration}
</span>
<span className={`task-status status-${item.status?.toLowerCase()}`}>
{/* <span className={`task-status status-${item.status?.toLowerCase()}`}>
{item.status === 'PENDING' ? '待完成' :
item.status === 'IN_PROGRESS' ? '进行中' :
item.status === 'COMPLETED' ? '已完成' : '未知'}
</span>
</span> */}
</div>
</div>
</div>

View File

@@ -30,7 +30,7 @@ const Dashboard = () => {
setDashboardData(response);
}
} catch (error) {
console.error('Failed to fetch dashboard data:', error);
console.error("Failed to fetch dashboard data:", error);
} finally {
setLoading(false);
}
@@ -41,11 +41,11 @@ const Dashboard = () => {
if (!dashboardData?.tasks?.allTasks) return [];
// Check if date is valid before calling toISOString
if (!date || isNaN(date.getTime())) {
console.warn('Invalid date provided to getTasksForDate:', date);
console.warn("Invalid date provided to getTasksForDate:", date);
return [];
}
const dateStr = date.toISOString().split('T')[0];
return dashboardData.tasks.allTasks.filter(task => task.date === dateStr);
const dateStr = date.toISOString().split("T")[0];
return dashboardData.tasks.allTasks.filter((task) => task.date === dateStr);
};
return (
@@ -53,29 +53,33 @@ const Dashboard = () => {
<StageProgress showBlockageAlert={true} />
<div className="dashboard-grid">
<StartClass
<StartClass
courses={dashboardData?.courses}
tasks={dashboardData?.tasks}
loading={loading}
/>
<QuickAccess />
<CalendarTaskModule
<CalendarTaskModule
tasks={dashboardData?.tasks?.allTasks}
selectedDate={selectedDate}
onDateChange={setSelectedDate}
loading={loading}
/>
<StudyStatus
<StudyStatus
progress={dashboardData?.overview?.overallProgress}
loading={loading}
/>
<Rank
data={dashboardData?.ranking ? {
rankings: dashboardData.ranking.topStudents
} : null}
<Rank
data={
dashboardData?.ranking
? {
rankings: dashboardData.ranking.topStudents,
}
: null
}
loading={loading}
/>
<TaskList
<TaskList
tasks={getTasksForDate(selectedDate)}
selectedDate={selectedDate}
loading={loading}

View File

@@ -5,11 +5,12 @@ import ScoreRingChart from "../ScoreRingChart";
import AttendanceRingChart from "../AttendanceRingChart";
import "./index.css";
const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) => {
const StudyStudes = ({
studyStatistics,
learningProgress,
loading = false,
}) => {
const studentInfo = useSelector((state) => state.student.studentInfo);
console.log("StudyStudes props:", { studyStatistics, learningProgress, loading });
// 如果数据还在加载中,显示加载状态
if (loading) {
@@ -25,10 +26,14 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) =>
const personalStudyHours = studyStatistics?.studyTime?.personal ?? 0;
const classAverageStudyHours = studyStatistics?.studyTime?.classAverage ?? 0;
const overallProgress = learningProgress?.overallProgress ?? 0;
const personalCourseProgress = studyStatistics?.courseCompletion?.personalProgress ?? 0;
const classAverageCourseProgress = studyStatistics?.courseCompletion?.classAverageProgress ?? 0;
const personalHomeworkProgress = studyStatistics?.homeworkCompletion?.personalProgress ?? 0;
const classAverageHomeworkProgress = studyStatistics?.homeworkCompletion?.classAverageProgress ?? 0;
const personalCourseProgress =
studyStatistics?.courseCompletion?.personalProgress ?? 0;
const classAverageCourseProgress =
studyStatistics?.courseCompletion?.classAverageProgress ?? 0;
const personalHomeworkProgress =
studyStatistics?.homeworkCompletion?.personalProgress ?? 0;
const classAverageHomeworkProgress =
studyStatistics?.homeworkCompletion?.classAverageProgress ?? 0;
const attendanceRate = studyStatistics?.attendance?.attendanceRate ?? 0;
const absenceRate = studyStatistics?.attendance?.absenceRate ?? 0;
@@ -117,7 +122,14 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) =>
个人学习时长h
</p>
<p className="study-studes-card-study-time-line">
<i style={{ width: `${Math.min(personalStudyHours / classAverageStudyHours * 70, 100)}%` }}>
<i
style={{
width: `${Math.min(
(personalStudyHours / classAverageStudyHours) * 70,
100
)}%`,
}}
>
<span>{personalStudyHours}</span>
</i>
</p>
@@ -153,11 +165,15 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) =>
/>
<div className="study-studes-card-curriculum-info">
<p>班级平均进度</p>
<span className="study-studes-card-curriculum-info-span1">{classAverageCourseProgress}%</span>
<span className="study-studes-card-curriculum-info-span1">
{classAverageCourseProgress}%
</span>
</div>
<div className="study-studes-card-curriculum-info">
<p>我的进度</p>
<span className="study-studes-card-curriculum-info-span2">{personalCourseProgress}%</span>
<span className="study-studes-card-curriculum-info-span2">
{personalCourseProgress}%
</span>
</div>
</li>
{/* 课后作业完成情况 */}
@@ -169,11 +185,15 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) =>
/>
<div className="study-studes-card-curriculum-info">
<p>班级平均进度</p>
<span className="study-studes-card-curriculum-info-span1">{classAverageHomeworkProgress}%</span>
<span className="study-studes-card-curriculum-info-span1">
{classAverageHomeworkProgress}%
</span>
</div>
<div className="study-studes-card-curriculum-info">
<p>我的进度</p>
<span className="study-studes-card-curriculum-info-span2">{personalHomeworkProgress}%</span>
<span className="study-studes-card-curriculum-info-span2">
{personalHomeworkProgress}%
</span>
</div>
</li>
{/* 考勤情况 */}
@@ -189,11 +209,15 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) =>
<div className="study-studes-card-curriculum-info">
<p>出勤率</p>
<span className="study-studes-card-curriculum-info-span1">{attendanceRate}%</span>
<span className="study-studes-card-curriculum-info-span1">
{attendanceRate}%
</span>
</div>
<div className="study-studes-card-curriculum-info">
<p>缺勤率</p>
<span className="study-studes-card-curriculum-info-span3">{absenceRate}%</span>
<span className="study-studes-card-curriculum-info-span3">
{absenceRate}%
</span>
</div>
</li>
</ul>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { useDispatch } from "react-redux";
import ProfileCard from "./components/ProfileCard";
import Rank from "@/components/Rank";
@@ -14,37 +14,33 @@ const PersonalProfile = () => {
const [loading, setLoading] = useState(true);
// 获取个人档案完整数据
const queryProfileOverview = async () => {
const queryProfileOverview = useCallback(async () => {
try {
setLoading(true);
console.log("Fetching profile overview...");
const res = await getProfileOverview();
console.log("Profile overview response:", res);
if (res.success) {
const data = res.data;
// 更新Redux中的学生信息
const studentInfo = {
...data.studentInfo,
myRank: data.ranking.myRank,
classInfo: data.ranking.classInfo,
};
setProfileData(data);
dispatch(updateStudentInfo(studentInfo));
console.log("Profile data set:", data);
}
} catch (error) {
console.error("Failed to fetch profile overview:", error);
} finally {
setLoading(false);
}
};
}, [dispatch]);
useEffect(() => {
queryProfileOverview();
}, []);
}, [queryProfileOverview]);
return (
<div className="personal-profile">
@@ -54,18 +50,22 @@ const PersonalProfile = () => {
<div className="unified-profile-layout">
<div className="unified-profile-left">
<ProfileCard />
<Rank
className="unified-profile-rank"
data={profileData?.ranking ? {
rankings: profileData.ranking.rankings,
myRank: profileData.ranking.myRank,
classInfo: profileData.ranking.classInfo,
} : null}
<Rank
className="unified-profile-rank"
data={
profileData?.ranking
? {
rankings: profileData.ranking.rankings,
myRank: profileData.ranking.myRank,
classInfo: profileData.ranking.classInfo,
}
: null
}
loading={loading}
/>
</div>
<div className="unified-profile-right">
<StudyStudes
<StudyStudes
studyStatistics={profileData?.studyStatistics}
learningProgress={profileData?.learningProgress}
loading={loading}

View File

@@ -1,4 +1,3 @@
import { useState } from "react";
import Modal from "@/components/Modal";
import PDFICON from "@/assets/images/Common/pdf_icon.png";
import "./index.css";

View File

@@ -3,7 +3,6 @@ import { Input } from "@arco-design/web-react";
import InfiniteScroll from "@/components/InfiniteScroll";
import ProjectCasesModal from "./components/ProjectCasesModal";
import { getProjectsList } from "@/services/projectLibrary";
import "./index.css";
const InputSearch = Input.Search;
@@ -25,6 +24,7 @@ const ProjectLibrary = () => {
};
const handleProjectClick = (item) => {
console.log(item);
setModalData(item);
setProjectCasesModalVisible(true);
};
@@ -36,13 +36,11 @@ const ProjectLibrary = () => {
const fetchProjects = async (searchValue = "", pageNum) => {
try {
// 这里使用真实API替换模拟数据
const res = await getProjectsList({
search: searchValue,
page: pageNum ?? page,
pageSize: PAGE_SIZE,
});
console.log(res);
if (res.success) {
setProjectList((prevList) => {
const newList = [...prevList, ...res.data];

View File

@@ -1,4 +1,3 @@
import { useState } from "react";
import Modal from "@/components/Modal";
import "./index.css";
@@ -7,35 +6,31 @@ export default ({ visible, onClose, data }) => {
onClose();
};
if (!data) return null;
const { question } = data;
return (
<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>{question.question}
<span>问题:</span>
{data?.question?.question}
</p>
<p className="interview-questions-modal-answer">
<span>解答</span>
这是一个{question.difficulty.toLowerCase()}难度的{question.category}相关问题
针对这类问题建议从以下几个方面来回答
1. 理解问题的核心概念和背景
2. 结合实际项目经验进行说明
3. 展示对相关技术的深入理解
4. 提及可能的优化或改进方案
这是一个{data?.question?.difficulty?.toLowerCase()}难度的
{data?.question?.category}相关问题
针对这类问题建议从以下几个方面来回答 1.
理解问题的核心概念和背景 2. 结合实际项目经验进行说明 3.
展示对相关技术的深入理解 4. 提及可能的优化或改进方案
</p>
</li>
</ul>
</div>
</Modal>
);
};
};

View File

@@ -41,63 +41,6 @@
font-weight: 600;
text-align: center;
}
.search-container {
position: relative;
display: flex;
align-items: center;
}
.search-input {
width: 200px;
height: 32px;
padding: 0 35px 0 12px;
border: 1px solid #e5e8ed;
border-radius: 16px;
font-size: 14px;
outline: none;
transition: border-color 0.3s;
&::placeholder {
color: #86909c;
}
&:focus {
border-color: #2c7aff;
box-shadow: 0 0 0 2px rgba(44, 122, 255, 0.1);
}
}
.search-icon {
position: absolute;
right: 10px;
width: 16px;
height: 16px;
background-color: #86909c;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E") center/contain no-repeat;
pointer-events: none;
}
.clear-button {
position: absolute;
right: 8px;
width: 20px;
height: 20px;
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #86909c;
font-size: 14px;
opacity: 0.7;
transition: opacity 0.3s;
&:hover {
opacity: 1;
}
}
}
.resume-interview-content-wrapper {
@@ -312,4 +255,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
import { useRef, useState, useEffect } from "react";
import toast from "@/components/Toast";
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
import ResumeModal from "./components/ResumeModal";
import ResumeInfoModal from "@/pages/CompanyJobsPage/components/ResumeInfoModal";
import { getPageData } from "@/services/resumeInterview";
import "./index.css";
@@ -13,7 +14,6 @@ const ResumeInterviewPage = () => {
const [resumeModalData, setResumeModalData] = useState(undefined);
const [pageData, setPageData] = useState(null);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const sectionsRef = useRef({});
// 导航到指定行业段落
@@ -27,19 +27,24 @@ const ResumeInterviewPage = () => {
// 面试题点击处理
const handleQuestionClick = (item) => {
setInterviewModalData(item);
setInterviewModalVisible(true);
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];
const selectedTemplate =
templates.find((t) => t.level === position.level) || templates[0];
setResumeModalData({
selectedTemplate,
studentResume: pageData.myResume
studentResume: pageData.myResume,
});
setResumeModalVisible(true);
};
@@ -54,21 +59,8 @@ const ResumeInterviewPage = () => {
setResumeModalData(undefined);
};
// Search functionality
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
};
const handleClearSearch = () => {
setSearchTerm("");
};
// Filter positions based on search term
const filterPositions = (positions) => {
if (!searchTerm.trim()) return positions;
return positions.filter(position =>
position.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return positions.filter((position) => position.name.toLowerCase());
};
// 获取页面数据
@@ -150,21 +142,6 @@ const ResumeInterviewPage = () => {
</li>
))}
</div>
<div className="search-container">
<input
type="text"
className="search-input"
placeholder="搜索职位名称..."
value={searchTerm}
onChange={handleSearchChange}
/>
{searchTerm && (
<button className="clear-button" onClick={handleClearSearch}>
×
</button>
)}
{!searchTerm && <div className="search-icon" />}
</div>
</ul>
<ul className="resume-interview-content-wrapper">
{pageData.industries.map((item) => (
@@ -178,8 +155,8 @@ const ResumeInterviewPage = () => {
<div className="item-content-wrapper">
<ul className="jobs-list">
{filterPositions(item.positions).map((position) => (
<li
className="job-item job-item-change"
<li
className="job-item job-item-change"
key={position.id}
onClick={() => handlePositionClick(position, item)}
>
@@ -190,11 +167,6 @@ const ResumeInterviewPage = () => {
</div>
</li>
))}
{searchTerm && filterPositions(item.positions).length === 0 && (
<li className="no-results">
<p>未找到匹配的职位</p>
</li>
)}
</ul>
<ul className="resumes-list">
{item.questions.map((question) => (
@@ -216,7 +188,7 @@ const ResumeInterviewPage = () => {
onClose={handleCloseInterviewModal}
data={interviewModalData}
/>
<ResumeModal
<ResumeInfoModal
visible={resumeModalVisible}
onClose={handleCloseResumeModal}
data={resumeModalData}
@@ -225,4 +197,4 @@ const ResumeInterviewPage = () => {
);
};
export default ResumeInterviewPage;
export default ResumeInterviewPage;