仪表盘接口调整
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { mapJobList, mapInterviewList } from "@/utils/dataMapper";
|
import { mapJobList, mapInterviewList } from "@/utils/dataMapper";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||||
import toast from "@/components/Toast";
|
import toast from "@/components/Toast";
|
||||||
import JobList from "./components/JobList";
|
import JobList from "./components/JobList";
|
||||||
import { getJobsList, getInterviewsList } from "@/services";
|
import { getCompanyJobsPageData, getJobsList, getInterviewsList } from "@/services";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
@@ -19,10 +19,57 @@ const CompanyJobsPage = () => {
|
|||||||
const [interviews, setInterviews] = useState([]);
|
const [interviews, setInterviews] = useState([]);
|
||||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||||
|
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// 获取面试信息
|
// 初始化页面数据 - 使用聚合接口
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchInitialData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getCompanyJobsPageData({
|
||||||
|
studentId: studentInfo?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res?.success) {
|
||||||
|
// 设置岗位数据
|
||||||
|
if (res.data?.jobs) {
|
||||||
|
const mappedJobs = mapJobList(res.data.jobs.list || []);
|
||||||
|
setJobs(mappedJobs);
|
||||||
|
setJoblistHasMore(res.data.jobs.hasMore);
|
||||||
|
if (mappedJobs.length > 0) {
|
||||||
|
setJobsListPage(2); // 下次从第2页开始
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置面试数据
|
||||||
|
if (res.data?.interviews && studentInfo?.id) {
|
||||||
|
const mappedInterviews = mapInterviewList(res.data.interviews.list || []);
|
||||||
|
setInterviews(mappedInterviews);
|
||||||
|
setInterviewsHasMore(res.data.interviews.hasMore);
|
||||||
|
if (mappedInterviews.length > 0) {
|
||||||
|
setInterviewsPage(2); // 下次从第2页开始
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialDataLoaded(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch initial page data:', error);
|
||||||
|
// 如果聚合接口失败,回退到原来的方式
|
||||||
|
setInitialDataLoaded(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchInitialData();
|
||||||
|
}, [studentInfo?.id]);
|
||||||
|
|
||||||
|
// 获取面试信息 - 用于分页加载更多
|
||||||
const fetchInterviewsData = async () => {
|
const fetchInterviewsData = async () => {
|
||||||
|
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||||
|
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (studentInfo?.id) {
|
if (studentInfo?.id) {
|
||||||
const res = await getInterviewsList({
|
const res = await getInterviewsList({
|
||||||
page: interviewsPage,
|
page: interviewsPage,
|
||||||
@@ -42,14 +89,21 @@ const CompanyJobsPage = () => {
|
|||||||
return newList;
|
return newList;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (interviewsPage === 1) {
|
||||||
setInterviews([]);
|
setInterviews([]);
|
||||||
|
}
|
||||||
toast.error(res.message);
|
toast.error(res.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取企业内推岗位
|
// 获取企业内推岗位 - 用于分页加载更多
|
||||||
const fetchJobsList = async () => {
|
const fetchJobsList = async () => {
|
||||||
|
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||||
|
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getJobsList({
|
const res = await getJobsList({
|
||||||
page: jobsListPage,
|
page: jobsListPage,
|
||||||
@@ -71,8 +125,10 @@ const CompanyJobsPage = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch data:", error);
|
console.error("Failed to fetch data:", error);
|
||||||
|
if (jobsListPage === 1) {
|
||||||
setJobs([]);
|
setJobs([]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJobWrapperClick = () => {
|
const handleJobWrapperClick = () => {
|
||||||
|
|||||||
@@ -33,5 +33,48 @@
|
|||||||
}
|
}
|
||||||
.arco-calendar-cell {
|
.arco-calendar-cell {
|
||||||
height: 40px !important;
|
height: 40px !important;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-date-cell {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.is-today {
|
||||||
|
.date-number {
|
||||||
|
background-color: rgb(0, 119, 255);
|
||||||
|
color: white;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-number {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-dot {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgb(0, 119, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,68 @@
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { Calendar } from "@arco-design/web-react";
|
import { Calendar, Skeleton } from "@arco-design/web-react";
|
||||||
|
|
||||||
// 获取当前日期
|
const CalendarTaskModule = ({ tasks = [], selectedDate, onDateChange, loading }) => {
|
||||||
const today = new Date();
|
if (loading) {
|
||||||
// 提取年、月、日
|
return (
|
||||||
const year = today.getFullYear();
|
<div className="module-calendar-task-wrapper">
|
||||||
const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需+1并补0
|
<p className="module-calendar-task-title">日历</p>
|
||||||
const day = String(today.getDate()).padStart(2, "0"); // 日期补0
|
<Skeleton loading={true} />
|
||||||
// 格式化日期字符串
|
</div>
|
||||||
const formattedDate = `${year}-${month}-${day}`;
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化今天的日期
|
||||||
|
const today = new Date();
|
||||||
|
const formattedToday = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
// 获取有任务的日期集合
|
||||||
|
const datesWithTasks = new Set(tasks?.map(task => task.date) || []);
|
||||||
|
|
||||||
|
// 日历单元格渲染函数
|
||||||
|
const dateRender = (current) => {
|
||||||
|
const dateStr = current.format('YYYY-MM-DD');
|
||||||
|
const hasTasks = datesWithTasks.has(dateStr);
|
||||||
|
const isToday = dateStr === formattedToday;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`calendar-date-cell ${hasTasks ? 'has-tasks' : ''} ${isToday ? 'is-today' : ''}`}>
|
||||||
|
<div className="date-number">{current.date()}</div>
|
||||||
|
{hasTasks && <div className="task-dot"></div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDateChange = (date, dateString) => {
|
||||||
|
if (onDateChange) {
|
||||||
|
// Arco Calendar passes a dayjs object
|
||||||
|
if (date && date.format) {
|
||||||
|
// Convert dayjs to Date object
|
||||||
|
const dateStr = date.format('YYYY-MM-DD');
|
||||||
|
const dateObj = new Date(dateStr + 'T00:00:00');
|
||||||
|
if (!isNaN(dateObj.getTime())) {
|
||||||
|
onDateChange(dateObj);
|
||||||
|
}
|
||||||
|
} else if (dateString) {
|
||||||
|
// Fallback to dateString if available
|
||||||
|
const dateObj = new Date(dateString + 'T00:00:00');
|
||||||
|
if (!isNaN(dateObj.getTime())) {
|
||||||
|
onDateChange(dateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const CalendarTaskModule = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className="module-calendar-task-wrapper">
|
<div className="module-calendar-task-wrapper">
|
||||||
<p className="module-calendar-task-title">日历</p>
|
<p className="module-calendar-task-title">日历</p>
|
||||||
<Calendar
|
<Calendar
|
||||||
panelWidth="300"
|
panelWidth="300"
|
||||||
panel
|
panel
|
||||||
defaultValue={formattedDate}
|
defaultValue={formattedToday}
|
||||||
|
value={selectedDate ? selectedDate.toISOString().split('T')[0] : formattedToday}
|
||||||
style={{ fontSize: "18px" }}
|
style={{ fontSize: "18px" }}
|
||||||
onChange={(a) => console.log(a)}
|
onChange={handleDateChange}
|
||||||
|
dateRender={dateRender}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
|
import { Skeleton } from "@arco-design/web-react";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const StartClass = () => {
|
const StartClass = ({ courses, tasks, loading }) => {
|
||||||
// 获取当前时间用于筛选
|
if (loading) {
|
||||||
const now = new Date();
|
return (
|
||||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
<div className="start-class-wrapper">
|
||||||
|
<p className="start-class-title">开始上课</p>
|
||||||
|
<Skeleton loading={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取待办任务
|
||||||
|
const pendingTasks = tasks?.allTasks?.filter(task =>
|
||||||
|
task.status === 'PENDING' || task.status === 'IN_PROGRESS'
|
||||||
|
).slice(0, 5) || [];
|
||||||
|
|
||||||
|
// 格式化日期显示
|
||||||
|
const formatCourseDate = (dateStr) => {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
const today = new Date();
|
||||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
||||||
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
|
||||||
|
if (date.toDateString() === today.toDateString()) {
|
||||||
|
return '今日已上';
|
||||||
|
} else if (date.toDateString() === yesterday.toDateString()) {
|
||||||
|
return '昨日已上';
|
||||||
|
} else {
|
||||||
|
return `${date.getMonth() + 1}/${date.getDate()}已上`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="start-class-wrapper">
|
<div className="start-class-wrapper">
|
||||||
@@ -14,32 +38,53 @@ const StartClass = () => {
|
|||||||
<li className="start-class-item">
|
<li className="start-class-item">
|
||||||
<p className="start-class-item-title">最近课程</p>
|
<p className="start-class-item-title">最近课程</p>
|
||||||
<ul className="start-class-item-list">
|
<ul className="start-class-item-list">
|
||||||
<li className="start-class-item-list-item">
|
{courses?.recentCourses?.length > 0 ? (
|
||||||
昨日已上《xxxxx计算机课》
|
courses.recentCourses.slice(0, 3).map((course, index) => (
|
||||||
|
<li key={index} className="start-class-item-list-item">
|
||||||
|
{formatCourseDate(course.date)}《{course.courseName}》
|
||||||
</li>
|
</li>
|
||||||
<li className="start-class-item-list-item">
|
))
|
||||||
今日已上《xxxxxx高等数学课》
|
) : (
|
||||||
</li>
|
<li className="start-class-item-list-item">暂无最近课程记录</li>
|
||||||
<li className="start-class-item-list-item">
|
)}
|
||||||
今日计划《xxxxxx中国现代史》
|
{courses?.todaysCourses?.length > 0 && (
|
||||||
|
courses.todaysCourses.slice(0, 2).map((course, index) => (
|
||||||
|
<li key={`today-${index}`} className="start-class-item-list-item">
|
||||||
|
今日计划《{course.courseName}》
|
||||||
</li>
|
</li>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li className="start-class-item">
|
<li className="start-class-item">
|
||||||
<p className="start-class-item-title">下次上课</p>
|
<p className="start-class-item-title">下次上课</p>
|
||||||
<ul className="start-class-item-list">
|
<ul className="start-class-item-list">
|
||||||
<li className="start-class-item-list-item">《xxxxxx中国现代史》</li>
|
{courses?.nextCourse ? (
|
||||||
|
<li className="start-class-item-list-item">
|
||||||
|
《{courses.nextCourse.courseName}》
|
||||||
|
{courses.nextCourse.classroom && (
|
||||||
|
<span className="classroom"> - {courses.nextCourse.classroom}</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li className="start-class-item-list-item">暂无安排课程</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li className="start-class-item">
|
<li className="start-class-item">
|
||||||
<p className="start-class-item-title">待办事项(2)</p>
|
<p className="start-class-item-title">
|
||||||
|
待办事项({pendingTasks.length})
|
||||||
|
</p>
|
||||||
<ul className="start-class-item-list">
|
<ul className="start-class-item-list">
|
||||||
<li className="start-class-item-list-item">
|
{pendingTasks.length > 0 ? (
|
||||||
《计算机课》 Excel 数据处理
|
pendingTasks.map((task) => (
|
||||||
</li>
|
<li key={task.id} className="start-class-item-list-item">
|
||||||
<li className="start-class-item-list-item">
|
《{task.courseName}》{task.title}
|
||||||
《高等数学课》线性代数的矩阵运算题
|
|
||||||
</li>
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<li className="start-class-item-list-item">暂无待办事项</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import EchartsProgress from "../EchartsProgress";
|
import EchartsProgress from "../EchartsProgress";
|
||||||
|
import { Skeleton } from "@arco-design/web-react";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const StudyStatus = () => {
|
const StudyStatus = ({ progress = 0, loading }) => {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="module-study-status-wrapper">
|
||||||
|
<p className="module-study-status-title">学习情况</p>
|
||||||
|
<Skeleton loading={true} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-study-status-wrapper">
|
<div className="module-study-status-wrapper">
|
||||||
<p className="module-study-status-title">学习情况</p>
|
<p className="module-study-status-title">学习情况</p>
|
||||||
<div className="progress-container">
|
<div className="progress-container">
|
||||||
<EchartsProgress percent={75} strokeWidth={20} />
|
<EchartsProgress percent={progress || 0} strokeWidth={20} />
|
||||||
<span className="progress-text">整体课程完成进度</span>
|
<span className="progress-text">
|
||||||
|
整体课程完成进度 ({progress || 0}%)
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,55 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.task-count {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #86909c;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-tasks {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.task-type {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1d2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-status {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
&.status-pending {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-in_progress {
|
||||||
|
background-color: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.status-completed {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.classroom {
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 12px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
color: #262626;
|
color: #262626;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,71 @@
|
|||||||
import { mockData } from "@/data/mockData";
|
import { Avatar, Skeleton, Empty } from "@arco-design/web-react";
|
||||||
import { Avatar } from "@arco-design/web-react";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const TaskList = () => {
|
const TaskList = ({ tasks = [], selectedDate, loading }) => {
|
||||||
const { tasks } = mockData;
|
if (loading) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-tasks-wrapper">
|
<div className="module-tasks-wrapper">
|
||||||
<p className="module-tasks-title">事项</p>
|
<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">
|
<ul className="module-tasks-list">
|
||||||
{tasks.map((item, index) => (
|
{tasks.map((item, index) => (
|
||||||
<li key={index} className="module-tasks-item">
|
<li key={item.id} className="module-tasks-item">
|
||||||
<div className="module-tasks-item-info">
|
<div className="module-tasks-item-info">
|
||||||
<Avatar className="module-tasks-item-info-avatar">
|
<Avatar className="module-tasks-item-info-avatar" size="small">
|
||||||
<img alt="avatar" src={item?.avatar} />
|
{item?.teacherAvatar ? (
|
||||||
|
<img alt="avatar" src={item.teacherAvatar} />
|
||||||
|
) : (
|
||||||
|
item?.teacherName?.charAt(0) || 'T'
|
||||||
|
)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<span className="module-tasks-item-info-teacher-name">
|
<span className="module-tasks-item-info-teacher-name">
|
||||||
{item?.teacher}
|
{item?.teacherName || '未知教师'}
|
||||||
</span>
|
</span>
|
||||||
<span className="module-tasks-item-info-time">{item?.time}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`module-tasks-item-content ${
|
className={`module-tasks-item-content ${
|
||||||
@@ -28,18 +75,29 @@ const TaskList = () => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="module-tasks-item-content-info">
|
<div className="module-tasks-item-content-info">
|
||||||
<p>单元名称:{item?.type}</p>
|
<p>
|
||||||
|
<span className="task-type">
|
||||||
|
{getTaskTypeText(item.type)}:
|
||||||
|
</span>
|
||||||
|
{item?.title}
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
课程名称:{item?.course}
|
课程名称:{item?.courseName}
|
||||||
<span className="module-tasks-item-content-info-duration">
|
<span className="module-tasks-item-content-info-duration">
|
||||||
{item?.duration}
|
{item?.duration}
|
||||||
</span>
|
</span>
|
||||||
|
<span className={`task-status status-${item.status?.toLowerCase()}`}>
|
||||||
|
{item.status === 'PENDING' ? '待完成' :
|
||||||
|
item.status === 'IN_PROGRESS' ? '进行中' :
|
||||||
|
item.status === 'COMPLETED' ? '已完成' : '未知'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,44 +6,46 @@ import StudyStatus from "./components/StudyStatus";
|
|||||||
import Rank from "@/components/Rank";
|
import Rank from "@/components/Rank";
|
||||||
import StageProgress from "@/components/StageProgress";
|
import StageProgress from "@/components/StageProgress";
|
||||||
import TaskList from "./components/TaskList";
|
import TaskList from "./components/TaskList";
|
||||||
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
import { getDashboardStatistics } from "@/services";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [rankingData, setRankingData] = useState(null);
|
const [dashboardData, setDashboardData] = useState(null);
|
||||||
const [rankingLoading, setRankingLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRankingData();
|
fetchDashboardData();
|
||||||
fetchLearningProgressSummary();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 获取班级排名数据
|
// 获取仪表板完整数据
|
||||||
const fetchRankingData = async () => {
|
const fetchDashboardData = async () => {
|
||||||
try {
|
try {
|
||||||
setRankingLoading(true);
|
setLoading(true);
|
||||||
const response = await getClassRanking();
|
const response = await getDashboardStatistics();
|
||||||
if (response && response.success) {
|
if (response && response.success) {
|
||||||
setRankingData(response.data);
|
setDashboardData(response.data);
|
||||||
} else if (response) {
|
} else if (response) {
|
||||||
// 兼容直接返回数据的情况
|
// 兼容直接返回数据的情况
|
||||||
setRankingData(response);
|
setDashboardData(response);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch ranking data:', error);
|
console.error('Failed to fetch dashboard data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setRankingLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取整体学习进度
|
// 根据选中日期筛选任务
|
||||||
const fetchLearningProgressSummary = async () => {
|
const getTasksForDate = (date) => {
|
||||||
try {
|
if (!dashboardData?.tasks?.allTasks) return [];
|
||||||
const res = await getStudyRecordsProgress();
|
// Check if date is valid before calling toISOString
|
||||||
console.log("learningProgressSummary", res);
|
if (!date || isNaN(date.getTime())) {
|
||||||
} catch (error) {
|
console.warn('Invalid date provided to getTasksForDate:', date);
|
||||||
console.error('Failed to fetch learning progress:', error);
|
return [];
|
||||||
}
|
}
|
||||||
|
const dateStr = date.toISOString().split('T')[0];
|
||||||
|
return dashboardData.tasks.allTasks.filter(task => task.date === dateStr);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,12 +53,33 @@ const Dashboard = () => {
|
|||||||
<StageProgress showBlockageAlert={true} />
|
<StageProgress showBlockageAlert={true} />
|
||||||
|
|
||||||
<div className="dashboard-grid">
|
<div className="dashboard-grid">
|
||||||
<StartClass />
|
<StartClass
|
||||||
|
courses={dashboardData?.courses}
|
||||||
|
tasks={dashboardData?.tasks}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
<QuickAccess />
|
<QuickAccess />
|
||||||
<CalendarTaskModule />
|
<CalendarTaskModule
|
||||||
<StudyStatus />
|
tasks={dashboardData?.tasks?.allTasks}
|
||||||
<Rank data={rankingData} loading={rankingLoading} />
|
selectedDate={selectedDate}
|
||||||
<TaskList />
|
onDateChange={setSelectedDate}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
<StudyStatus
|
||||||
|
progress={dashboardData?.overview?.overallProgress}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
<Rank
|
||||||
|
data={dashboardData?.ranking ? {
|
||||||
|
rankings: dashboardData.ranking.topStudents
|
||||||
|
} : null}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
<TaskList
|
||||||
|
tasks={getTasksForDate(selectedDate)}
|
||||||
|
selectedDate={selectedDate}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,13 +3,39 @@ import { useSelector } from "react-redux";
|
|||||||
import StudyProgress from "../StudyProgress";
|
import StudyProgress from "../StudyProgress";
|
||||||
import ScoreRingChart from "../ScoreRingChart";
|
import ScoreRingChart from "../ScoreRingChart";
|
||||||
import AttendanceRingChart from "../AttendanceRingChart";
|
import AttendanceRingChart from "../AttendanceRingChart";
|
||||||
import { getDashboardStatistics } from "@/services";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
const ringData = [
|
|
||||||
|
const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) => {
|
||||||
|
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||||
|
|
||||||
|
console.log("StudyStudes props:", { studyStatistics, learningProgress, loading });
|
||||||
|
|
||||||
|
// 如果数据还在加载中,显示加载状态
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="study-studes-card-wrapper">
|
||||||
|
<p className="study-studes-card-title">学习情况</p>
|
||||||
|
<div>加载中...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用传入的数据,只在undefined时使用默认值(0也是有效值)
|
||||||
|
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 attendanceRate = studyStatistics?.attendance?.attendanceRate ?? 0;
|
||||||
|
const absenceRate = studyStatistics?.attendance?.absenceRate ?? 0;
|
||||||
|
|
||||||
|
// 动态生成环形图数据
|
||||||
|
const courseRingData = [
|
||||||
{
|
{
|
||||||
value: 80,
|
value: personalCourseProgress,
|
||||||
name: "我的",
|
name: "我的",
|
||||||
title: {
|
title: {
|
||||||
offsetCenter: ["-30%", "-90%"],
|
offsetCenter: ["-30%", "-90%"],
|
||||||
@@ -20,9 +46,9 @@ const ringData = [
|
|||||||
{ offset: 1, color: "#00AEFF" },
|
{ offset: 1, color: "#00AEFF" },
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
}, // 外环
|
},
|
||||||
{
|
{
|
||||||
value: 60,
|
value: classAverageCourseProgress,
|
||||||
name: "班级",
|
name: "班级",
|
||||||
title: {
|
title: {
|
||||||
offsetCenter: ["-60%", "-90%"],
|
offsetCenter: ["-60%", "-90%"],
|
||||||
@@ -33,13 +59,42 @@ const ringData = [
|
|||||||
{ offset: 1, color: "#AB2CFF" },
|
{ offset: 1, color: "#AB2CFF" },
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
}, // 中环
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const attendanceData = [
|
const homeworkRingData = [
|
||||||
|
{
|
||||||
|
value: personalHomeworkProgress,
|
||||||
|
name: "我的",
|
||||||
|
title: {
|
||||||
|
offsetCenter: ["-30%", "-90%"],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||||
|
{ offset: 0, color: "#2C7AFF" },
|
||||||
|
{ offset: 1, color: "#00AEFF" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: classAverageHomeworkProgress,
|
||||||
|
name: "班级",
|
||||||
|
title: {
|
||||||
|
offsetCenter: ["-60%", "-90%"],
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||||
|
{ offset: 0, color: "#4564FF" },
|
||||||
|
{ offset: 1, color: "#AB2CFF" },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const attendanceData = [
|
||||||
{
|
{
|
||||||
name: "出勤率",
|
name: "出勤率",
|
||||||
value: 22,
|
value: attendanceRate,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||||
{ offset: 0, color: "#2C7AFF" },
|
{ offset: 0, color: "#2C7AFF" },
|
||||||
@@ -47,22 +102,8 @@ const attendanceData = [
|
|||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ name: "缺勤率", value: 8, itemStyle: { color: "#FF9D2C" } },
|
{ name: "缺勤率", value: absenceRate, itemStyle: { color: "#FF9D2C" } },
|
||||||
// 可添加更多状态(如迟到、早退、缺勤等)
|
];
|
||||||
];
|
|
||||||
|
|
||||||
const StudyStudes = () => {
|
|
||||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
|
||||||
|
|
||||||
// 获取仪表盘数据
|
|
||||||
const queryDashboardStatistics = async () => {
|
|
||||||
const res = await getDashboardStatistics();
|
|
||||||
console.log(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
queryDashboardStatistics();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="study-studes-card-wrapper">
|
<div className="study-studes-card-wrapper">
|
||||||
@@ -76,8 +117,8 @@ const StudyStudes = () => {
|
|||||||
个人学习时长(h)
|
个人学习时长(h)
|
||||||
</p>
|
</p>
|
||||||
<p className="study-studes-card-study-time-line">
|
<p className="study-studes-card-study-time-line">
|
||||||
<i style={{ width: "70%" }}>
|
<i style={{ width: `${Math.min(personalStudyHours / classAverageStudyHours * 70, 100)}%` }}>
|
||||||
<span>145</span>
|
<span>{personalStudyHours}</span>
|
||||||
</i>
|
</i>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +128,7 @@ const StudyStudes = () => {
|
|||||||
</p>
|
</p>
|
||||||
<p className="study-studes-card-study-time-line study-studes-card-study-time-line-class">
|
<p className="study-studes-card-study-time-line study-studes-card-study-time-line-class">
|
||||||
<i style={{ width: "80%" }}>
|
<i style={{ width: "80%" }}>
|
||||||
<span>145</span>
|
<span>{classAverageStudyHours}</span>
|
||||||
</i>
|
</i>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +137,7 @@ const StudyStudes = () => {
|
|||||||
{/* 进度 */}
|
{/* 进度 */}
|
||||||
<li className="study-studes-card-study-progress">
|
<li className="study-studes-card-study-progress">
|
||||||
<StudyProgress
|
<StudyProgress
|
||||||
value={75}
|
value={overallProgress}
|
||||||
className="study-studes-card-study-progress-chart"
|
className="study-studes-card-study-progress-chart"
|
||||||
/>
|
/>
|
||||||
<p className="study-studes-card-study-progress-title">
|
<p className="study-studes-card-study-progress-title">
|
||||||
@@ -107,32 +148,32 @@ const StudyStudes = () => {
|
|||||||
<li className="study-studes-card-curriculum">
|
<li className="study-studes-card-curriculum">
|
||||||
<ScoreRingChart
|
<ScoreRingChart
|
||||||
title={`整体课程\n完成情况`}
|
title={`整体课程\n完成情况`}
|
||||||
ringData={ringData}
|
ringData={courseRingData}
|
||||||
className="study-studes-card-curriculum-chart"
|
className="study-studes-card-curriculum-chart"
|
||||||
/>
|
/>
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>班级平均进度</p>
|
<p>班级平均进度</p>
|
||||||
<span className="study-studes-card-curriculum-info-span1">60%</span>
|
<span className="study-studes-card-curriculum-info-span1">{classAverageCourseProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>我的进度</p>
|
<p>我的进度</p>
|
||||||
<span className="study-studes-card-curriculum-info-span2">60%</span>
|
<span className="study-studes-card-curriculum-info-span2">{personalCourseProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* 课后作业完成情况 */}
|
{/* 课后作业完成情况 */}
|
||||||
<li className="study-studes-card-curriculum-homework study-studes-card-curriculum">
|
<li className="study-studes-card-curriculum-homework study-studes-card-curriculum">
|
||||||
<ScoreRingChart
|
<ScoreRingChart
|
||||||
title={`课后作业\n完成情况`}
|
title={`课后作业\n完成情况`}
|
||||||
ringData={ringData}
|
ringData={homeworkRingData}
|
||||||
className="study-studes-card-curriculum-chart"
|
className="study-studes-card-curriculum-chart"
|
||||||
/>
|
/>
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>班级平均进度</p>
|
<p>班级平均进度</p>
|
||||||
<span className="study-studes-card-curriculum-info-span1">60%</span>
|
<span className="study-studes-card-curriculum-info-span1">{classAverageHomeworkProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>我的进度</p>
|
<p>我的进度</p>
|
||||||
<span className="study-studes-card-curriculum-info-span2">60%</span>
|
<span className="study-studes-card-curriculum-info-span2">{personalHomeworkProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* 考勤情况 */}
|
{/* 考勤情况 */}
|
||||||
@@ -148,11 +189,11 @@ const StudyStudes = () => {
|
|||||||
|
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>出勤率</p>
|
<p>出勤率</p>
|
||||||
<span className="study-studes-card-curriculum-info-span1">60%</span>
|
<span className="study-studes-card-curriculum-info-span1">{attendanceRate}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="study-studes-card-curriculum-info">
|
<div className="study-studes-card-curriculum-info">
|
||||||
<p>缺勤率</p>
|
<p>缺勤率</p>
|
||||||
<span className="study-studes-card-curriculum-info-span3">60%</span>
|
<span className="study-studes-card-curriculum-info-span3">{absenceRate}%</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -5,34 +5,45 @@ import Rank from "@/components/Rank";
|
|||||||
import StageProgress from "@/components/StageProgress";
|
import StageProgress from "@/components/StageProgress";
|
||||||
import StudyStudes from "./components/StudyStudes";
|
import StudyStudes from "./components/StudyStudes";
|
||||||
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
||||||
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
import { getProfileOverview } from "@/services";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const PersonalProfile = () => {
|
const PersonalProfile = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [rankData, setRankData] = useState([]); // 班级排名数据
|
const [profileData, setProfileData] = useState(null); // 个人档案完整数据
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const queryLearningProgressSummary = async () => {
|
// 获取个人档案完整数据
|
||||||
const res = await getStudyRecordsProgress();
|
const queryProfileOverview = async () => {
|
||||||
console.log("learningProgressSummary", res);
|
try {
|
||||||
};
|
setLoading(true);
|
||||||
|
console.log("Fetching profile overview...");
|
||||||
|
const res = await getProfileOverview();
|
||||||
|
console.log("Profile overview response:", res);
|
||||||
|
|
||||||
// 获取班级排名
|
|
||||||
const queryRankData = async () => {
|
|
||||||
const res = await getClassRanking();
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
const data = res.data;
|
||||||
|
|
||||||
|
// 更新Redux中的学生信息
|
||||||
const studentInfo = {
|
const studentInfo = {
|
||||||
myRank: res.data.myRank,
|
...data.studentInfo,
|
||||||
classInfo: res.data.classInfo,
|
myRank: data.ranking.myRank,
|
||||||
|
classInfo: data.ranking.classInfo,
|
||||||
};
|
};
|
||||||
setRankData(res.data);
|
|
||||||
|
setProfileData(data);
|
||||||
dispatch(updateStudentInfo(studentInfo));
|
dispatch(updateStudentInfo(studentInfo));
|
||||||
|
console.log("Profile data set:", data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch profile overview:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryRankData();
|
queryProfileOverview();
|
||||||
queryLearningProgressSummary();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,10 +54,22 @@ const PersonalProfile = () => {
|
|||||||
<div className="unified-profile-layout">
|
<div className="unified-profile-layout">
|
||||||
<div className="unified-profile-left">
|
<div className="unified-profile-left">
|
||||||
<ProfileCard />
|
<ProfileCard />
|
||||||
<Rank className="unified-profile-rank" data={rankData} />
|
<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>
|
||||||
<div className="unified-profile-right">
|
<div className="unified-profile-right">
|
||||||
<StudyStudes />
|
<StudyStudes
|
||||||
|
studyStatistics={profileData?.studyStatistics}
|
||||||
|
learningProgress={profileData?.learningProgress}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||||
import { mockData } from "@/data/mockData";
|
import { getPageData } from "@/services/resumeInterview";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const { resumeInterview } = mockData;
|
|
||||||
|
|
||||||
const ResumeInterviewPage = () => {
|
const ResumeInterviewPage = () => {
|
||||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||||
const [modalData, setModalData] = useState(undefined);
|
const [modalData, setModalData] = useState(undefined);
|
||||||
|
const [pageData, setPageData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const sectionsRef = useRef({});
|
const sectionsRef = useRef({});
|
||||||
|
|
||||||
// 导航到指定行业段落
|
// 导航到指定行业段落
|
||||||
@@ -32,12 +32,37 @@ const ResumeInterviewPage = () => {
|
|||||||
setModalData(undefined);
|
setModalData(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取页面数据
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
|
if (!pageData?.industries) return;
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const scrollPosition = window.scrollY + 200;
|
const scrollPosition = window.scrollY + 200;
|
||||||
|
|
||||||
resumeInterview.industries.forEach((industry) => {
|
pageData.industries.forEach((industry) => {
|
||||||
const section = sectionsRef.current[industry.id];
|
const section = sectionsRef.current[industry.id];
|
||||||
if (section) {
|
if (section) {
|
||||||
const sectionTop = section.offsetTop;
|
const sectionTop = section.offsetTop;
|
||||||
@@ -52,12 +77,28 @@ const ResumeInterviewPage = () => {
|
|||||||
|
|
||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
}, [resumeInterview.industries]);
|
}, [pageData?.industries]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="resume-interview-page">
|
||||||
|
<div className="loading">加载中...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pageData) {
|
||||||
|
return (
|
||||||
|
<div className="resume-interview-page">
|
||||||
|
<div className="error">加载失败,请刷新重试</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="resume-interview-page">
|
<div className="resume-interview-page">
|
||||||
<ul className="resume-interview-navigation">
|
<ul className="resume-interview-navigation">
|
||||||
{resumeInterview.industries.map((industry) => (
|
{pageData.industries.map((industry) => (
|
||||||
<li
|
<li
|
||||||
key={industry.id}
|
key={industry.id}
|
||||||
className={`resume-interview-navigation-item ${
|
className={`resume-interview-navigation-item ${
|
||||||
@@ -70,7 +111,7 @@ const ResumeInterviewPage = () => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="resume-interview-content-wrapper">
|
<ul className="resume-interview-content-wrapper">
|
||||||
{resumeInterview?.industries?.map((item) => (
|
{pageData.industries.map((item) => (
|
||||||
<li
|
<li
|
||||||
className="resume-interview-content-item-wrapper"
|
className="resume-interview-content-item-wrapper"
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -80,23 +121,26 @@ const ResumeInterviewPage = () => {
|
|||||||
<p className="item-subtitle">简历与面试题</p>
|
<p className="item-subtitle">简历与面试题</p>
|
||||||
<div className="item-content-wrapper">
|
<div className="item-content-wrapper">
|
||||||
<ul className="jobs-list">
|
<ul className="jobs-list">
|
||||||
{item.positions.map((i) => (
|
{item.positions.map((position) => (
|
||||||
<li className="job-item job-item-change" key={i.id}>
|
<li className="job-item job-item-change" key={position.id}>
|
||||||
<span>{i.level}</span>
|
<span>{position.level}</span>
|
||||||
<div className="job-name">
|
<div className="job-name">
|
||||||
<p>{i.name}</p>
|
<p>{position.name}</p>
|
||||||
<span>详情 ></span>
|
<span>详情 ></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="resumes-list">
|
<ul className="resumes-list">
|
||||||
|
{item.questions.map((question) => (
|
||||||
<li
|
<li
|
||||||
|
key={question.id}
|
||||||
className="resume-item"
|
className="resume-item"
|
||||||
onClick={() => handleHookQuestionClick(item)}
|
onClick={() => handleHookQuestionClick({ ...item, question })}
|
||||||
>
|
>
|
||||||
<p>面试题1122345</p>
|
<p>{question.question}</p>
|
||||||
</li>
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -105,6 +149,7 @@ const ResumeInterviewPage = () => {
|
|||||||
<InterviewQuestionsModal
|
<InterviewQuestionsModal
|
||||||
visible={resumeModalVisible}
|
visible={resumeModalVisible}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
|
data={modalData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 获取企业内推岗位页面聚合数据
|
||||||
|
export async function getCompanyJobsPageData(params) {
|
||||||
|
return request({
|
||||||
|
url: `/api/company-jobs/page-data`,
|
||||||
|
method: "GET",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 获取企业内推岗位
|
// 获取企业内推岗位
|
||||||
export async function getJobsList(params) {
|
export async function getJobsList(params) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from "./dashboard";
|
} from "./dashboard";
|
||||||
import { getProjectsList } from "./projectLibrary";
|
import { getProjectsList } from "./projectLibrary";
|
||||||
import {
|
import {
|
||||||
|
getCompanyJobsPageData,
|
||||||
getJobsList,
|
getJobsList,
|
||||||
getJobsDetail,
|
getJobsDetail,
|
||||||
getInterviewsList,
|
getInterviewsList,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
getLoginStudentProgress,
|
getLoginStudentProgress,
|
||||||
getClassRank,
|
getClassRank,
|
||||||
getMyRanking,
|
getMyRanking,
|
||||||
|
getProfileOverview,
|
||||||
} from "./personalProfile";
|
} from "./personalProfile";
|
||||||
import {} from "./resumeInterview";
|
import {} from "./resumeInterview";
|
||||||
|
|
||||||
@@ -35,11 +37,13 @@ export {
|
|||||||
getLoginStudentInfo, // 获取当前登录学生基本信息
|
getLoginStudentInfo, // 获取当前登录学生基本信息
|
||||||
getLoginStudentProgress, // 获取当前学生学习进度
|
getLoginStudentProgress, // 获取当前学生学习进度
|
||||||
getClassRank, // 获取班级排名(别名)
|
getClassRank, // 获取班级排名(别名)
|
||||||
|
getProfileOverview, // 获取个人档案完整数据(新接口)
|
||||||
|
|
||||||
// 项目和作品相关
|
// 项目和作品相关
|
||||||
getProjectsList, // 获取项目列表
|
getProjectsList, // 获取项目列表
|
||||||
|
|
||||||
// 求职相关
|
// 求职相关
|
||||||
|
getCompanyJobsPageData, // 获取企业内推岗位页面聚合数据
|
||||||
getJobsList, // 获取岗位列表
|
getJobsList, // 获取岗位列表
|
||||||
getJobsDetail, // 岗位详情
|
getJobsDetail, // 岗位详情
|
||||||
getInterviewsList, // 获取面试列表
|
getInterviewsList, // 获取面试列表
|
||||||
|
|||||||
@@ -36,3 +36,12 @@ export async function getMyRanking() {
|
|||||||
namespace: "profileLoading",
|
namespace: "profileLoading",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取个人档案完整数据 (新接口)
|
||||||
|
export async function getProfileOverview() {
|
||||||
|
return request({
|
||||||
|
url: `/api/profile/overview`,
|
||||||
|
method: "GET",
|
||||||
|
namespace: "profileLoading",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,33 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// Get all page data for resume-interview page
|
||||||
|
export const getPageData = () => {
|
||||||
|
return request({
|
||||||
|
url: "/api/resume-interview",
|
||||||
|
method: "GET"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export individual functions for future use if needed
|
||||||
|
export const getResumes = (params) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/resumes",
|
||||||
|
method: "GET",
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getInterviews = (params) => {
|
||||||
|
return request({
|
||||||
|
url: "/api/interviews",
|
||||||
|
method: "GET",
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMyInterviews = () => {
|
||||||
|
return request({
|
||||||
|
url: "/api/interviews/my-interviews",
|
||||||
|
method: "GET"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user