仪表盘接口调整

This commit is contained in:
2025-08-25 20:56:56 +08:00
parent 2c67099e95
commit 11b529c502
15 changed files with 700 additions and 208 deletions

View File

@@ -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([]);
}
}
};

View File

@@ -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);
}
}

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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;
}

View File

@@ -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>
);
};

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>详情 &gt;</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>
);