feat: 🎸 接口对接调整
This commit is contained in:
@@ -31,7 +31,7 @@ export default defineConfig([
|
||||
},
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"no-console": "warn",
|
||||
"no-console": 1,
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"no-nested-ternary": 0, // 允许嵌套三元表达式
|
||||
"no-script-url": 0, // 允许javascript:;
|
||||
@@ -47,6 +47,7 @@ export default defineConfig([
|
||||
"react/no-deprecated": 0, // 关闭react弃用检测
|
||||
"react/no-string-refs": 0,
|
||||
"no-useless-escape": 0,
|
||||
"react-refresh/only-export-components": 0, // 允许匿名导出
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>多多畅职教育系统</title>
|
||||
</head>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -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;
|
||||
|
||||
// 节流处理: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
|
||||
|
||||
@@ -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">
|
||||
|
||||
0
src/components/Layout/index_new.jsx
Normal file
0
src/components/Layout/index_new.jsx
Normal 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,13 +23,13 @@ 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[2] || null, // 第3名
|
||||
];
|
||||
|
||||
const listStudents = rankings.slice(3);
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
const fetchJobs = useCallback(async () => {
|
||||
const res = await getJobsList({
|
||||
page: listPage,
|
||||
pageSize: PAGE_SIZE,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
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 (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;
|
||||
});
|
||||
}
|
||||
}, [listPage]);
|
||||
|
||||
if (loading && jobs.length === 0) {
|
||||
return (
|
||||
<div
|
||||
<InfiniteScroll
|
||||
loadMore={fetchJobs}
|
||||
hasMore={listHasMore}
|
||||
empty={jobs.length === 0}
|
||||
className="company-jobs-list-page-wrapper"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "400px",
|
||||
}}
|
||||
>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="company-jobs-list-page-wrapper">
|
||||
<JobList data={jobs} />
|
||||
{jobs.length === 0 && !loading && (
|
||||
<div
|
||||
style={{
|
||||
textAlign: "center",
|
||||
padding: "40px",
|
||||
color: "#999",
|
||||
}}
|
||||
>
|
||||
暂无岗位信息
|
||||
</div>
|
||||
)}
|
||||
</ul>
|
||||
</InfiniteScroll>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
list-style: none;
|
||||
|
||||
.company-jobs-page-interview-item-info {
|
||||
width: 100%;
|
||||
|
||||
@@ -92,7 +92,7 @@ const EchartsProgress = ({
|
||||
return () => {
|
||||
window.removeEventListener("resize", resizeHandler);
|
||||
};
|
||||
}, [percent, strokeWidth]);
|
||||
}, [backgroundColor, percent, progressColor, strokeWidth]);
|
||||
|
||||
return <div ref={chartRef} className="progress-chart" />;
|
||||
};
|
||||
|
||||
@@ -12,39 +12,41 @@ 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 ? (
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
@@ -70,9 +70,13 @@ const Dashboard = () => {
|
||||
loading={loading}
|
||||
/>
|
||||
<Rank
|
||||
data={dashboardData?.ranking ? {
|
||||
rankings: dashboardData.ranking.topStudents
|
||||
} : null}
|
||||
data={
|
||||
dashboardData?.ranking
|
||||
? {
|
||||
rankings: dashboardData.ranking.topStudents,
|
||||
}
|
||||
: null
|
||||
}
|
||||
loading={loading}
|
||||
/>
|
||||
<TaskList
|
||||
|
||||
@@ -5,12 +5,13 @@ 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) {
|
||||
return (
|
||||
@@ -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>
|
||||
|
||||
@@ -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,13 +14,10 @@ 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;
|
||||
|
||||
@@ -33,18 +30,17 @@ const PersonalProfile = () => {
|
||||
|
||||
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">
|
||||
@@ -56,11 +52,15 @@ const PersonalProfile = () => {
|
||||
<ProfileCard />
|
||||
<Rank
|
||||
className="unified-profile-rank"
|
||||
data={profileData?.ranking ? {
|
||||
data={
|
||||
profileData?.ranking
|
||||
? {
|
||||
rankings: profileData.ranking.rankings,
|
||||
myRank: profileData.ranking.myRank,
|
||||
classInfo: profileData.ranking.classInfo,
|
||||
} : null}
|
||||
}
|
||||
: null
|
||||
}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useState } from "react";
|
||||
import Modal from "@/components/Modal";
|
||||
import "./index.css";
|
||||
|
||||
@@ -7,31 +6,27 @@ 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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
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) => (
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user