仪表盘接口调整
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { mapJobList, mapInterviewList } from "@/utils/dataMapper";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import toast from "@/components/Toast";
|
||||
import JobList from "./components/JobList";
|
||||
import { getJobsList, getInterviewsList } from "@/services";
|
||||
import { getCompanyJobsPageData, getJobsList, getInterviewsList } from "@/services";
|
||||
|
||||
import "./index.css";
|
||||
const PAGE_SIZE = 10;
|
||||
@@ -19,10 +19,57 @@ const CompanyJobsPage = () => {
|
||||
const [interviews, setInterviews] = useState([]);
|
||||
const [interviewsPage, setInterviewsPage] = useState(1);
|
||||
const [interviewsHasMore, setInterviewsHasMore] = useState(true);
|
||||
const [initialDataLoaded, setInitialDataLoaded] = useState(false);
|
||||
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 () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (interviewsPage === 1 && interviews.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (studentInfo?.id) {
|
||||
const res = await getInterviewsList({
|
||||
page: interviewsPage,
|
||||
@@ -42,14 +89,21 @@ const CompanyJobsPage = () => {
|
||||
return newList;
|
||||
});
|
||||
} else {
|
||||
setInterviews([]);
|
||||
if (interviewsPage === 1) {
|
||||
setInterviews([]);
|
||||
}
|
||||
toast.error(res.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 获取企业内推岗位
|
||||
// 获取企业内推岗位 - 用于分页加载更多
|
||||
const fetchJobsList = async () => {
|
||||
// 如果初始数据还没加载完成,或者是第一页且已有初始数据,则跳过
|
||||
if (!initialDataLoaded || (jobsListPage === 1 && jobs.length > 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getJobsList({
|
||||
page: jobsListPage,
|
||||
@@ -71,7 +125,9 @@ const CompanyJobsPage = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
setJobs([]);
|
||||
if (jobsListPage === 1) {
|
||||
setJobs([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,5 +33,48 @@
|
||||
}
|
||||
.arco-calendar-cell {
|
||||
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 { Calendar } from "@arco-design/web-react";
|
||||
import { Calendar, Skeleton } from "@arco-design/web-react";
|
||||
|
||||
// 获取当前日期
|
||||
const today = new Date();
|
||||
// 提取年、月、日
|
||||
const year = today.getFullYear();
|
||||
const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需+1并补0
|
||||
const day = String(today.getDate()).padStart(2, "0"); // 日期补0
|
||||
// 格式化日期字符串
|
||||
const formattedDate = `${year}-${month}-${day}`;
|
||||
const CalendarTaskModule = ({ tasks = [], selectedDate, onDateChange, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="module-calendar-task-wrapper">
|
||||
<p className="module-calendar-task-title">日历</p>
|
||||
<Skeleton loading={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 格式化今天的日期
|
||||
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 (
|
||||
<div className="module-calendar-task-wrapper">
|
||||
<p className="module-calendar-task-title">日历</p>
|
||||
<Calendar
|
||||
panelWidth="300"
|
||||
panel
|
||||
defaultValue={formattedDate}
|
||||
defaultValue={formattedToday}
|
||||
value={selectedDate ? selectedDate.toISOString().split('T')[0] : formattedToday}
|
||||
style={{ fontSize: "18px" }}
|
||||
onChange={(a) => console.log(a)}
|
||||
onChange={handleDateChange}
|
||||
dateRender={dateRender}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
import { Skeleton } from "@arco-design/web-react";
|
||||
import "./index.css";
|
||||
|
||||
const StartClass = () => {
|
||||
// 获取当前时间用于筛选
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
||||
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
||||
const StartClass = ({ courses, tasks, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<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);
|
||||
|
||||
if (date.toDateString() === today.toDateString()) {
|
||||
return '今日已上';
|
||||
} else if (date.toDateString() === yesterday.toDateString()) {
|
||||
return '昨日已上';
|
||||
} else {
|
||||
return `${date.getMonth() + 1}/${date.getDate()}已上`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="start-class-wrapper">
|
||||
@@ -14,32 +38,53 @@ const StartClass = () => {
|
||||
<li className="start-class-item">
|
||||
<p className="start-class-item-title">最近课程</p>
|
||||
<ul className="start-class-item-list">
|
||||
<li className="start-class-item-list-item">
|
||||
昨日已上《xxxxx计算机课》
|
||||
</li>
|
||||
<li className="start-class-item-list-item">
|
||||
今日已上《xxxxxx高等数学课》
|
||||
</li>
|
||||
<li className="start-class-item-list-item">
|
||||
今日计划《xxxxxx中国现代史》
|
||||
</li>
|
||||
{courses?.recentCourses?.length > 0 ? (
|
||||
courses.recentCourses.slice(0, 3).map((course, index) => (
|
||||
<li key={index} className="start-class-item-list-item">
|
||||
{formatCourseDate(course.date)}《{course.courseName}》
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li className="start-class-item-list-item">暂无最近课程记录</li>
|
||||
)}
|
||||
{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>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
<li className="start-class-item">
|
||||
<p className="start-class-item-title">下次上课</p>
|
||||
<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>
|
||||
</li>
|
||||
<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">
|
||||
<li className="start-class-item-list-item">
|
||||
《计算机课》 Excel 数据处理
|
||||
</li>
|
||||
<li className="start-class-item-list-item">
|
||||
《高等数学课》线性代数的矩阵运算题
|
||||
</li>
|
||||
{pendingTasks.length > 0 ? (
|
||||
pendingTasks.map((task) => (
|
||||
<li key={task.id} className="start-class-item-list-item">
|
||||
《{task.courseName}》{task.title}
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<li className="start-class-item-list-item">暂无待办事项</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import EchartsProgress from "../EchartsProgress";
|
||||
import { Skeleton } from "@arco-design/web-react";
|
||||
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 (
|
||||
<div className="module-study-status-wrapper">
|
||||
<p className="module-study-status-title">学习情况</p>
|
||||
<div className="progress-container">
|
||||
<EchartsProgress percent={75} strokeWidth={20} />
|
||||
<span className="progress-text">整体课程完成进度</span>
|
||||
<EchartsProgress percent={progress || 0} strokeWidth={20} />
|
||||
<span className="progress-text">
|
||||
整体课程完成进度 ({progress || 0}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,55 @@
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
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;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,103 @@
|
||||
import { mockData } from "@/data/mockData";
|
||||
import { Avatar } from "@arco-design/web-react";
|
||||
import { Avatar, Skeleton, Empty } from "@arco-design/web-react";
|
||||
import "./index.css";
|
||||
|
||||
const TaskList = () => {
|
||||
const { tasks } = mockData;
|
||||
const TaskList = ({ tasks = [], selectedDate, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">事项</p>
|
||||
<Skeleton loading={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const getTaskTypeText = (type) => {
|
||||
const typeMap = {
|
||||
'HOMEWORK': '作业',
|
||||
'PROJECT': '项目',
|
||||
'REPORT': '报告',
|
||||
'INTERVIEW': '面试',
|
||||
'OTHER': '其他'
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
const getPriorityClass = (priority) => {
|
||||
const classMap = {
|
||||
'URGENT': 'urgent',
|
||||
'HIGH': 'high',
|
||||
'MEDIUM': 'medium',
|
||||
'LOW': 'low'
|
||||
};
|
||||
return classMap[priority] || 'medium';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="module-tasks-wrapper">
|
||||
<p className="module-tasks-title">事项</p>
|
||||
<ul className="module-tasks-list">
|
||||
{tasks.map((item, index) => (
|
||||
<li key={index} className="module-tasks-item">
|
||||
<div className="module-tasks-item-info">
|
||||
<Avatar className="module-tasks-item-info-avatar">
|
||||
<img alt="avatar" src={item?.avatar} />
|
||||
</Avatar>
|
||||
<span className="module-tasks-item-info-teacher-name">
|
||||
{item?.teacher}
|
||||
</span>
|
||||
<span className="module-tasks-item-info-time">{item?.time}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`module-tasks-item-content ${
|
||||
index === tasks.length - 1
|
||||
? "module-tasks-item-content-last"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="module-tasks-item-content-info">
|
||||
<p>单元名称:{item?.type}</p>
|
||||
<div>
|
||||
课程名称:{item?.course}
|
||||
<span className="module-tasks-item-content-info-duration">
|
||||
{item?.duration}
|
||||
</span>
|
||||
<p className="module-tasks-title">
|
||||
事项 - {formatDate(selectedDate)}
|
||||
{tasks.length > 0 && <span className="task-count">({tasks.length})</span>}
|
||||
</p>
|
||||
|
||||
{tasks.length === 0 ? (
|
||||
<div className="no-tasks">
|
||||
<Empty description="该日无事项" />
|
||||
</div>
|
||||
) : (
|
||||
<ul className="module-tasks-list">
|
||||
{tasks.map((item, index) => (
|
||||
<li key={item.id} className="module-tasks-item">
|
||||
<div className="module-tasks-item-info">
|
||||
<Avatar className="module-tasks-item-info-avatar" size="small">
|
||||
{item?.teacherAvatar ? (
|
||||
<img alt="avatar" src={item.teacherAvatar} />
|
||||
) : (
|
||||
item?.teacherName?.charAt(0) || 'T'
|
||||
)}
|
||||
</Avatar>
|
||||
<span className="module-tasks-item-info-teacher-name">
|
||||
{item?.teacherName || '未知教师'}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`module-tasks-item-content ${
|
||||
index === tasks.length - 1
|
||||
? "module-tasks-item-content-last"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="module-tasks-item-content-info">
|
||||
<p>
|
||||
<span className="task-type">
|
||||
{getTaskTypeText(item.type)}:
|
||||
</span>
|
||||
{item?.title}
|
||||
</p>
|
||||
<div>
|
||||
课程名称:{item?.courseName}
|
||||
<span className="module-tasks-item-content-info-duration">
|
||||
{item?.duration}
|
||||
</span>
|
||||
<span className={`task-status status-${item.status?.toLowerCase()}`}>
|
||||
{item.status === 'PENDING' ? '待完成' :
|
||||
item.status === 'IN_PROGRESS' ? '进行中' :
|
||||
item.status === 'COMPLETED' ? '已完成' : '未知'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,44 +6,46 @@ import StudyStatus from "./components/StudyStatus";
|
||||
import Rank from "@/components/Rank";
|
||||
import StageProgress from "@/components/StageProgress";
|
||||
import TaskList from "./components/TaskList";
|
||||
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
||||
import { getDashboardStatistics } from "@/services";
|
||||
import "./index.css";
|
||||
|
||||
const Dashboard = () => {
|
||||
const [rankingData, setRankingData] = useState(null);
|
||||
const [rankingLoading, setRankingLoading] = useState(true);
|
||||
const [dashboardData, setDashboardData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
fetchRankingData();
|
||||
fetchLearningProgressSummary();
|
||||
fetchDashboardData();
|
||||
}, []);
|
||||
|
||||
// 获取班级排名数据
|
||||
const fetchRankingData = async () => {
|
||||
// 获取仪表板完整数据
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
setRankingLoading(true);
|
||||
const response = await getClassRanking();
|
||||
setLoading(true);
|
||||
const response = await getDashboardStatistics();
|
||||
if (response && response.success) {
|
||||
setRankingData(response.data);
|
||||
setDashboardData(response.data);
|
||||
} else if (response) {
|
||||
// 兼容直接返回数据的情况
|
||||
setRankingData(response);
|
||||
setDashboardData(response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch ranking data:', error);
|
||||
console.error('Failed to fetch dashboard data:', error);
|
||||
} finally {
|
||||
setRankingLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取整体学习进度
|
||||
const fetchLearningProgressSummary = async () => {
|
||||
try {
|
||||
const res = await getStudyRecordsProgress();
|
||||
console.log("learningProgressSummary", res);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch learning progress:', error);
|
||||
// 根据选中日期筛选任务
|
||||
const getTasksForDate = (date) => {
|
||||
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);
|
||||
return [];
|
||||
}
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
return dashboardData.tasks.allTasks.filter(task => task.date === dateStr);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -51,12 +53,33 @@ const Dashboard = () => {
|
||||
<StageProgress showBlockageAlert={true} />
|
||||
|
||||
<div className="dashboard-grid">
|
||||
<StartClass />
|
||||
<StartClass
|
||||
courses={dashboardData?.courses}
|
||||
tasks={dashboardData?.tasks}
|
||||
loading={loading}
|
||||
/>
|
||||
<QuickAccess />
|
||||
<CalendarTaskModule />
|
||||
<StudyStatus />
|
||||
<Rank data={rankingData} loading={rankingLoading} />
|
||||
<TaskList />
|
||||
<CalendarTaskModule
|
||||
tasks={dashboardData?.tasks?.allTasks}
|
||||
selectedDate={selectedDate}
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -3,66 +3,107 @@ import { useSelector } from "react-redux";
|
||||
import StudyProgress from "../StudyProgress";
|
||||
import ScoreRingChart from "../ScoreRingChart";
|
||||
import AttendanceRingChart from "../AttendanceRingChart";
|
||||
import { getDashboardStatistics } from "@/services";
|
||||
import "./index.css";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const ringData = [
|
||||
{
|
||||
value: 80,
|
||||
name: "我的",
|
||||
title: {
|
||||
offsetCenter: ["-30%", "-90%"],
|
||||
},
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#2C7AFF" },
|
||||
{ offset: 1, color: "#00AEFF" },
|
||||
]),
|
||||
},
|
||||
}, // 外环
|
||||
{
|
||||
value: 60,
|
||||
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: "出勤率",
|
||||
value: 22,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#2C7AFF" },
|
||||
{ offset: 1, color: "#00D5FF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{ name: "缺勤率", value: 8, itemStyle: { color: "#FF9D2C" } },
|
||||
// 可添加更多状态(如迟到、早退、缺勤等)
|
||||
];
|
||||
|
||||
const StudyStudes = () => {
|
||||
const StudyStudes = ({ studyStatistics, learningProgress, loading = false }) => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
|
||||
console.log("StudyStudes props:", { studyStatistics, learningProgress, loading });
|
||||
|
||||
// 获取仪表盘数据
|
||||
const queryDashboardStatistics = async () => {
|
||||
const res = await getDashboardStatistics();
|
||||
console.log(res);
|
||||
};
|
||||
// 如果数据还在加载中,显示加载状态
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="study-studes-card-wrapper">
|
||||
<p className="study-studes-card-title">学习情况</p>
|
||||
<div>加载中...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
queryDashboardStatistics();
|
||||
}, []);
|
||||
// 使用传入的数据,只在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: personalCourseProgress,
|
||||
name: "我的",
|
||||
title: {
|
||||
offsetCenter: ["-30%", "-90%"],
|
||||
},
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#2C7AFF" },
|
||||
{ offset: 1, color: "#00AEFF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
value: classAverageCourseProgress,
|
||||
name: "班级",
|
||||
title: {
|
||||
offsetCenter: ["-60%", "-90%"],
|
||||
},
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#4564FF" },
|
||||
{ offset: 1, color: "#AB2CFF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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: "出勤率",
|
||||
value: attendanceRate,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#2C7AFF" },
|
||||
{ offset: 1, color: "#00D5FF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{ name: "缺勤率", value: absenceRate, itemStyle: { color: "#FF9D2C" } },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="study-studes-card-wrapper">
|
||||
@@ -76,8 +117,8 @@ const StudyStudes = () => {
|
||||
个人学习时长(h)
|
||||
</p>
|
||||
<p className="study-studes-card-study-time-line">
|
||||
<i style={{ width: "70%" }}>
|
||||
<span>145</span>
|
||||
<i style={{ width: `${Math.min(personalStudyHours / classAverageStudyHours * 70, 100)}%` }}>
|
||||
<span>{personalStudyHours}</span>
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
@@ -87,7 +128,7 @@ const StudyStudes = () => {
|
||||
</p>
|
||||
<p className="study-studes-card-study-time-line study-studes-card-study-time-line-class">
|
||||
<i style={{ width: "80%" }}>
|
||||
<span>145</span>
|
||||
<span>{classAverageStudyHours}</span>
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
@@ -96,7 +137,7 @@ const StudyStudes = () => {
|
||||
{/* 进度 */}
|
||||
<li className="study-studes-card-study-progress">
|
||||
<StudyProgress
|
||||
value={75}
|
||||
value={overallProgress}
|
||||
className="study-studes-card-study-progress-chart"
|
||||
/>
|
||||
<p className="study-studes-card-study-progress-title">
|
||||
@@ -107,32 +148,32 @@ const StudyStudes = () => {
|
||||
<li className="study-studes-card-curriculum">
|
||||
<ScoreRingChart
|
||||
title={`整体课程\n完成情况`}
|
||||
ringData={ringData}
|
||||
ringData={courseRingData}
|
||||
className="study-studes-card-curriculum-chart"
|
||||
/>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>班级平均进度</p>
|
||||
<span className="study-studes-card-curriculum-info-span1">60%</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">60%</span>
|
||||
<span className="study-studes-card-curriculum-info-span2">{personalCourseProgress}%</span>
|
||||
</div>
|
||||
</li>
|
||||
{/* 课后作业完成情况 */}
|
||||
<li className="study-studes-card-curriculum-homework study-studes-card-curriculum">
|
||||
<ScoreRingChart
|
||||
title={`课后作业\n完成情况`}
|
||||
ringData={ringData}
|
||||
ringData={homeworkRingData}
|
||||
className="study-studes-card-curriculum-chart"
|
||||
/>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>班级平均进度</p>
|
||||
<span className="study-studes-card-curriculum-info-span1">60%</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">60%</span>
|
||||
<span className="study-studes-card-curriculum-info-span2">{personalHomeworkProgress}%</span>
|
||||
</div>
|
||||
</li>
|
||||
{/* 考勤情况 */}
|
||||
@@ -148,11 +189,11 @@ const StudyStudes = () => {
|
||||
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>出勤率</p>
|
||||
<span className="study-studes-card-curriculum-info-span1">60%</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">60%</span>
|
||||
<span className="study-studes-card-curriculum-info-span3">{absenceRate}%</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -5,34 +5,45 @@ import Rank from "@/components/Rank";
|
||||
import StageProgress from "@/components/StageProgress";
|
||||
import StudyStudes from "./components/StudyStudes";
|
||||
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
||||
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
||||
import { getProfileOverview } from "@/services";
|
||||
import "./index.css";
|
||||
|
||||
const PersonalProfile = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [rankData, setRankData] = useState([]); // 班级排名数据
|
||||
const [profileData, setProfileData] = useState(null); // 个人档案完整数据
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const queryLearningProgressSummary = async () => {
|
||||
const res = await getStudyRecordsProgress();
|
||||
console.log("learningProgressSummary", res);
|
||||
};
|
||||
|
||||
// 获取班级排名
|
||||
const queryRankData = async () => {
|
||||
const res = await getClassRanking();
|
||||
if (res.success) {
|
||||
const studentInfo = {
|
||||
myRank: res.data.myRank,
|
||||
classInfo: res.data.classInfo,
|
||||
};
|
||||
setRankData(res.data);
|
||||
dispatch(updateStudentInfo(studentInfo));
|
||||
// 获取个人档案完整数据
|
||||
const queryProfileOverview = 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);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryRankData();
|
||||
queryLearningProgressSummary();
|
||||
queryProfileOverview();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -43,10 +54,22 @@ const PersonalProfile = () => {
|
||||
<div className="unified-profile-layout">
|
||||
<div className="unified-profile-left">
|
||||
<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 className="unified-profile-right">
|
||||
<StudyStudes />
|
||||
<StudyStudes
|
||||
studyStatistics={profileData?.studyStatistics}
|
||||
learningProgress={profileData?.learningProgress}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||
import { mockData } from "@/data/mockData";
|
||||
import { getPageData } from "@/services/resumeInterview";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const { resumeInterview } = mockData;
|
||||
|
||||
const ResumeInterviewPage = () => {
|
||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||
const [modalData, setModalData] = useState(undefined);
|
||||
const [pageData, setPageData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const sectionsRef = useRef({});
|
||||
|
||||
// 导航到指定行业段落
|
||||
@@ -32,12 +32,37 @@ const ResumeInterviewPage = () => {
|
||||
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(() => {
|
||||
if (!pageData?.industries) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
const scrollPosition = window.scrollY + 200;
|
||||
|
||||
resumeInterview.industries.forEach((industry) => {
|
||||
pageData.industries.forEach((industry) => {
|
||||
const section = sectionsRef.current[industry.id];
|
||||
if (section) {
|
||||
const sectionTop = section.offsetTop;
|
||||
@@ -52,12 +77,28 @@ const ResumeInterviewPage = () => {
|
||||
|
||||
window.addEventListener("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 (
|
||||
<div className="resume-interview-page">
|
||||
<ul className="resume-interview-navigation">
|
||||
{resumeInterview.industries.map((industry) => (
|
||||
{pageData.industries.map((industry) => (
|
||||
<li
|
||||
key={industry.id}
|
||||
className={`resume-interview-navigation-item ${
|
||||
@@ -70,7 +111,7 @@ const ResumeInterviewPage = () => {
|
||||
))}
|
||||
</ul>
|
||||
<ul className="resume-interview-content-wrapper">
|
||||
{resumeInterview?.industries?.map((item) => (
|
||||
{pageData.industries.map((item) => (
|
||||
<li
|
||||
className="resume-interview-content-item-wrapper"
|
||||
key={item.id}
|
||||
@@ -80,23 +121,26 @@ const ResumeInterviewPage = () => {
|
||||
<p className="item-subtitle">简历与面试题</p>
|
||||
<div className="item-content-wrapper">
|
||||
<ul className="jobs-list">
|
||||
{item.positions.map((i) => (
|
||||
<li className="job-item job-item-change" key={i.id}>
|
||||
<span>{i.level}</span>
|
||||
{item.positions.map((position) => (
|
||||
<li className="job-item job-item-change" key={position.id}>
|
||||
<span>{position.level}</span>
|
||||
<div className="job-name">
|
||||
<p>{i.name}</p>
|
||||
<p>{position.name}</p>
|
||||
<span>详情 ></span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="resumes-list">
|
||||
<li
|
||||
className="resume-item"
|
||||
onClick={() => handleHookQuestionClick(item)}
|
||||
>
|
||||
<p>面试题1122345</p>
|
||||
</li>
|
||||
{item.questions.map((question) => (
|
||||
<li
|
||||
key={question.id}
|
||||
className="resume-item"
|
||||
onClick={() => handleHookQuestionClick({ ...item, question })}
|
||||
>
|
||||
<p>{question.question}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -105,6 +149,7 @@ const ResumeInterviewPage = () => {
|
||||
<InterviewQuestionsModal
|
||||
visible={resumeModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
data={modalData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user