初始化12个产业教务系统项目
主要内容: - 包含12个产业的完整教务系统前端代码 - 智能启动脚本 (start-industry.sh) - 可视化产业导航页面 (index.html) - 项目文档 (README.md) 优化内容: - 删除所有node_modules和.yoyo文件夹,从7.5GB减少到2.7GB - 添加.gitignore文件避免上传不必要的文件 - 自动依赖管理和智能启动系统 产业列表: 1. 文旅产业 (5150) 2. 智能制造 (5151) 3. 智能开发 (5152) 4. 财经商贸 (5153) 5. 视觉设计 (5154) 6. 交通物流 (5155) 7. 大健康 (5156) 8. 土木水利 (5157) 9. 食品产业 (5158) 10. 化工产业 (5159) 11. 能源产业 (5160) 12. 环保产业 (5161) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as echarts from "echarts";
|
||||
import "echarts/lib/chart/pie";
|
||||
import "echarts/lib/component/tooltip";
|
||||
|
||||
const AttendanceRingChart = ({
|
||||
data = [],
|
||||
centerContent = null, // 新增参数,用于传入中间的自定义内容
|
||||
centerContentStyle = {}, // 新增参数,用于自定义中间内容的样式
|
||||
}) => {
|
||||
const chartRef = useRef(null);
|
||||
const [chartInstance, setChartInstance] = useState(null);
|
||||
const containerRef = useRef(null); // 新增容器ref
|
||||
// 初始化/更新图表
|
||||
useEffect(() => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// 销毁旧实例
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
|
||||
// 创建新实例
|
||||
const newChart = echarts.init(chartRef.current);
|
||||
setChartInstance(newChart);
|
||||
|
||||
// 配置图表选项
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: "pie",
|
||||
radius: ["90%", "80%"],
|
||||
avoidLabelOverlap: false,
|
||||
padAngle: 5,
|
||||
itemStyle: {
|
||||
borderRadius: 30,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
emphasis: {
|
||||
label: { show: false },
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
newChart.setOption(option);
|
||||
// 监听窗口变化自动调整大小
|
||||
window.addEventListener("resize", () => newChart.resize());
|
||||
return () => {
|
||||
window.removeEventListener("resize", () => newChart.resize());
|
||||
newChart.dispose();
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={chartRef}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
{centerContent && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
textAlign: "center",
|
||||
...centerContentStyle,
|
||||
}}
|
||||
>
|
||||
{centerContent}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttendanceRingChart;
|
||||
@@ -0,0 +1,158 @@
|
||||
.profile-card-wrapper {
|
||||
width: 100%;
|
||||
height: 364px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
flex-shrink: 0;
|
||||
background-image: linear-gradient(180deg, #e1ebff, #f3f7ff);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-image: url("@/assets/images/PersonalProfile/personal_profile_bg.png");
|
||||
background-size: 100% 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.profile-card-user-info {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.profile-card-user-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.profile-card-user-name {
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.profile-card-user-name-text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-image: url("@/assets/images/PersonalProfile/male_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-card-user-name-student-id {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #4e5969;
|
||||
}
|
||||
}
|
||||
}
|
||||
.profile-card-achievement-info {
|
||||
width: 328px;
|
||||
height: 47px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.profile-card-achievement-info-item {
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.profile-card-achievement-info-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.profile-card-achievement-info-item-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
}
|
||||
}
|
||||
.profile-card-class-info {
|
||||
width: 328px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
/* 投影(box-shadow) */
|
||||
box-shadow: 2px 2px 16.4px 0 rgba(103, 162, 247, 0.25);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.profile-card-class-info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.profile-card-class-info-item {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.profile-card-class-info-item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.profile-card-class-info-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #4e5969;
|
||||
position: absolute;
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
.profile-card-class-info-item-text {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Avatar } from "@arco-design/web-react";
|
||||
import { useSelector } from "react-redux";
|
||||
import IconFont from "@/components/IconFont";
|
||||
import "./index.css";
|
||||
|
||||
const ProfileCard = () => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
return (
|
||||
<div className="profile-card-wrapper">
|
||||
<div className="profile-card-user-info">
|
||||
<Avatar className="profile-card-user-avatar">
|
||||
<img
|
||||
alt="avatar"
|
||||
src={studentInfo?.avatar || "//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp"}
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="profile-card-user-name">
|
||||
<span className="profile-card-user-name-text">
|
||||
{studentInfo?.realName || "-"}
|
||||
</span>
|
||||
<p className="profile-card-user-name-student-id">
|
||||
学号: {studentInfo?.studentNo || "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="profile-card-achievement-info">
|
||||
<li className="profile-card-achievement-info-item">
|
||||
<span className="profile-card-achievement-info-item-title">学分</span>
|
||||
<span className="profile-card-achievement-info-item-text">
|
||||
{studentInfo?.myRank?.score || "-"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="profile-card-achievement-info-item">
|
||||
<span className="profile-card-achievement-info-item-title">
|
||||
班级排名
|
||||
</span>
|
||||
<span className="profile-card-achievement-info-item-text">
|
||||
{studentInfo?.myRank?.rank || "-"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="profile-card-achievement-info-item">
|
||||
<span className="profile-card-achievement-info-item-title">MBTI</span>
|
||||
<span className="profile-card-achievement-info-item-text">
|
||||
{studentInfo?.mbtiType || "-"}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="profile-card-class-info">
|
||||
<li className="profile-card-class-info-item">
|
||||
<IconFont
|
||||
className="profile-card-class-info-item-icon"
|
||||
src="recuUY5p8rDCck"
|
||||
/>
|
||||
<span className="profile-card-class-info-item-title">学校</span>
|
||||
<span className="profile-card-class-info-item-text">
|
||||
{studentInfo?.school || "-"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="profile-card-class-info-item">
|
||||
<IconFont
|
||||
className="profile-card-class-info-item-icon"
|
||||
src="recuUY5pyDDMub"
|
||||
/>
|
||||
<span className="profile-card-class-info-item-title">专业</span>
|
||||
<span className="profile-card-class-info-item-text">
|
||||
{studentInfo?.major || "-"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="profile-card-class-info-item">
|
||||
<IconFont
|
||||
className="profile-card-class-info-item-icon"
|
||||
src="recuUY5pWmMoaQ"
|
||||
/>
|
||||
<span className="profile-card-class-info-item-title">
|
||||
复合能力课
|
||||
</span>
|
||||
<span className="profile-card-class-info-item-text">
|
||||
{studentInfo?.className || "-"}
|
||||
</span>
|
||||
</li>
|
||||
<li className="profile-card-class-info-item">
|
||||
<IconFont
|
||||
className="profile-card-class-info-item-icon"
|
||||
src="recuUY5tMX7M6A"
|
||||
/>
|
||||
<span className="profile-card-class-info-item-title">垂直能力课</span>
|
||||
<span className="profile-card-class-info-item-text">
|
||||
{studentInfo?.stageName || "-"}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileCard;
|
||||
@@ -0,0 +1,122 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
const ScoreRingChart = ({
|
||||
className = "",
|
||||
ringData = [], // 数据
|
||||
bgColor = "#EDF4FF", // 未填充部分背景色
|
||||
}) => {
|
||||
// 创建图表容器引用
|
||||
const chartRef = useRef(null);
|
||||
// 图表实例引用
|
||||
const chartInstance = useRef(null);
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (!chartRef.current) return;
|
||||
|
||||
// 销毁已存在的图表实例
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.dispose();
|
||||
}
|
||||
|
||||
// 创建新图表实例
|
||||
chartInstance.current = echarts.init(chartRef.current);
|
||||
|
||||
// 配置图表
|
||||
const option = {
|
||||
series: [
|
||||
{
|
||||
type: "gauge",
|
||||
startAngle: 90,
|
||||
endAngle: -270,
|
||||
radius: "100%",
|
||||
pointer: {
|
||||
show: false,
|
||||
},
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 20,
|
||||
color: [[1, bgColor]],
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
data: [ringData[0]],
|
||||
detail: false,
|
||||
},
|
||||
{
|
||||
type: "gauge",
|
||||
startAngle: 90,
|
||||
endAngle: -270,
|
||||
radius: "70%",
|
||||
pointer: {
|
||||
show: false,
|
||||
},
|
||||
progress: {
|
||||
show: true,
|
||||
overlap: false,
|
||||
roundCap: true,
|
||||
clip: false,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 20,
|
||||
color: [[1, bgColor]],
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
data: [ringData[1]],
|
||||
title: {
|
||||
fontSize: 14,
|
||||
},
|
||||
detail: { show: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 设置图表配置
|
||||
chartInstance.current.setOption(option);
|
||||
const resize = () => chartInstance.current?.resize();
|
||||
window.addEventListener("resize", resize);
|
||||
chartInstance.current.__resize = resize; // 方便卸载
|
||||
};
|
||||
|
||||
// 组件挂载和更新时初始化图表
|
||||
useEffect(() => {
|
||||
initChart();
|
||||
|
||||
return () => {
|
||||
if (chartInstance.current) {
|
||||
window.removeEventListener("resize", chartInstance.current.__resize);
|
||||
chartInstance.current.dispose();
|
||||
chartInstance.current = null;
|
||||
}
|
||||
};
|
||||
}, [ringData, bgColor]);
|
||||
|
||||
return <div ref={chartRef} className={className} />;
|
||||
};
|
||||
|
||||
export default ScoreRingChart;
|
||||
@@ -0,0 +1,235 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import * as echarts from "echarts";
|
||||
const screenWidth = window.screen.width; // 物理屏幕宽度(单位:像素)
|
||||
|
||||
const StudyProgress = ({
|
||||
value = 0, // 默认值78%
|
||||
width = (180 / 1440) * screenWidth,
|
||||
height = (180 / 1440) * screenWidth,
|
||||
className = "",
|
||||
}) => {
|
||||
// 创建图表容器引用
|
||||
const chartRef = useRef(null);
|
||||
// 图表实例引用
|
||||
const chartInstance = useRef(null);
|
||||
|
||||
// 图表配置常量
|
||||
const ROOT_PATH = "https://echarts.apache.org/examples";
|
||||
const _panelImageURL = ROOT_PATH + "/data/asset/img/custom-gauge-panel.png";
|
||||
const _animationDuration = 1000;
|
||||
const _animationDurationUpdate = 1000;
|
||||
const _animationEasingUpdate = "quarticInOut";
|
||||
const _valOnRadianMax = 200;
|
||||
const _outerRadius = Math.min(width, height) / 2;
|
||||
const _innerRadius = _outerRadius * 0.85;
|
||||
const _pointerInnerRadius = _outerRadius * 0.2;
|
||||
const _insidePanelRadius = _outerRadius * 0.7;
|
||||
|
||||
// 转换为极坐标点
|
||||
const convertToPolarPoint = (renderItemParams, radius, radian) => {
|
||||
return [
|
||||
Math.cos(radian) * radius + renderItemParams.coordSys.cx,
|
||||
-Math.sin(radian) * radius + renderItemParams.coordSys.cy,
|
||||
];
|
||||
};
|
||||
|
||||
// 创建指针点
|
||||
const makePionterPoints = (renderItemParams, polarEndRadian) => {
|
||||
return [
|
||||
convertToPolarPoint(renderItemParams, _outerRadius, polarEndRadian),
|
||||
convertToPolarPoint(
|
||||
renderItemParams,
|
||||
_outerRadius,
|
||||
polarEndRadian + Math.PI * 0.03
|
||||
),
|
||||
convertToPolarPoint(
|
||||
renderItemParams,
|
||||
_pointerInnerRadius,
|
||||
polarEndRadian
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
// 生成文本
|
||||
const makeText = (valOnRadian) => {
|
||||
if (valOnRadian < -10) {
|
||||
console.error("非法值:", valOnRadian);
|
||||
}
|
||||
return ((valOnRadian / _valOnRadianMax) * 100).toFixed(0) + "%";
|
||||
};
|
||||
|
||||
// 渲染项函数
|
||||
const renderItem = (params, api) => {
|
||||
// 将百分比转换为弧度值
|
||||
const valOnRadian = (value / 100) * _valOnRadianMax;
|
||||
const coords = api.coord([api.value(0), valOnRadian]);
|
||||
const polarEndRadian = coords[3];
|
||||
const imageStyle = {
|
||||
image: _panelImageURL,
|
||||
x: params.coordSys.cx - _outerRadius,
|
||||
y: params.coordSys.cy - _outerRadius,
|
||||
width: _outerRadius * 2,
|
||||
height: _outerRadius * 2,
|
||||
};
|
||||
|
||||
return {
|
||||
type: "group",
|
||||
children: [
|
||||
{
|
||||
type: "image",
|
||||
style: imageStyle,
|
||||
clipPath: {
|
||||
type: "sector",
|
||||
shape: {
|
||||
cx: params.coordSys.cx,
|
||||
cy: params.coordSys.cy,
|
||||
r: _outerRadius,
|
||||
r0: _innerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: -polarEndRadian,
|
||||
transition: "endAngle",
|
||||
enterFrom: { endAngle: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "image",
|
||||
style: imageStyle,
|
||||
clipPath: {
|
||||
type: "polygon",
|
||||
shape: {
|
||||
points: makePionterPoints(params, polarEndRadian),
|
||||
},
|
||||
extra: {
|
||||
polarEndRadian: polarEndRadian,
|
||||
transition: "polarEndRadian",
|
||||
enterFrom: { polarEndRadian: 0 },
|
||||
},
|
||||
during: function (apiDuring) {
|
||||
apiDuring.setShape(
|
||||
"points",
|
||||
makePionterPoints(params, apiDuring.getExtra("polarEndRadian"))
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "circle",
|
||||
shape: {
|
||||
cx: params.coordSys.cx,
|
||||
cy: params.coordSys.cy,
|
||||
r: _insidePanelRadius,
|
||||
},
|
||||
style: {
|
||||
fill: "#fff",
|
||||
shadowBlur: 25,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0,
|
||||
shadowColor: "rgba(76,107,167,0.4)",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
extra: {
|
||||
valOnRadian: valOnRadian,
|
||||
transition: "valOnRadian",
|
||||
enterFrom: { valOnRadian: 0 },
|
||||
},
|
||||
style: {
|
||||
text: makeText(valOnRadian),
|
||||
fontSize: Math.min(width, height) / 8,
|
||||
fontWeight: 700,
|
||||
x: params.coordSys.cx,
|
||||
y: params.coordSys.cy,
|
||||
fill: "rgb(0,50,190)",
|
||||
align: "center",
|
||||
verticalAlign: "middle",
|
||||
enterFrom: { opacity: 0 },
|
||||
},
|
||||
during: function (apiDuring) {
|
||||
apiDuring.setStyle(
|
||||
"text",
|
||||
makeText(apiDuring.getExtra("valOnRadian"))
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
// 图表选项配置
|
||||
const getChartOption = () => ({
|
||||
animationEasing: _animationEasingUpdate,
|
||||
animationDuration: _animationDuration,
|
||||
animationDurationUpdate: _animationDurationUpdate,
|
||||
animationEasingUpdate: _animationEasingUpdate,
|
||||
dataset: {
|
||||
source: [[1, (value / 100) * _valOnRadianMax]],
|
||||
},
|
||||
tooltip: {},
|
||||
angleAxis: {
|
||||
type: "value",
|
||||
startAngle: 0,
|
||||
show: false,
|
||||
min: 0,
|
||||
max: _valOnRadianMax,
|
||||
},
|
||||
radiusAxis: {
|
||||
type: "value",
|
||||
show: false,
|
||||
},
|
||||
polar: {},
|
||||
series: [
|
||||
{
|
||||
type: "custom",
|
||||
coordinateSystem: "polar",
|
||||
renderItem: renderItem,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 初始化和更新图表
|
||||
useEffect(() => {
|
||||
// 确保DOM已经渲染
|
||||
if (chartRef.current && !chartInstance.current) {
|
||||
chartInstance.current = echarts.init(chartRef.current);
|
||||
chartInstance.current.setOption(getChartOption());
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.setOption(getChartOption());
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.resize();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// 组件卸载时清理
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.dispose();
|
||||
chartInstance.current = null;
|
||||
}
|
||||
};
|
||||
}, [value, width, height]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={chartRef}
|
||||
className={className}
|
||||
style={{
|
||||
width: width,
|
||||
height: height,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudyProgress;
|
||||
@@ -0,0 +1,259 @@
|
||||
.study-studes-card-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.study-studes-card-title {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 30px;
|
||||
color: #262626;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.study-studes-card-spin {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.study-studes-card-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.study-studes-card-study-info {
|
||||
width: 356px;
|
||||
height: 310px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
box-sizing: border-box;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.study-studes-card-time-wrapper {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #fff;
|
||||
width: 324px;
|
||||
height: 133px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 20px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.study-studes-card-study-info-title {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-left: 30px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-image: url("@/assets/images/PersonalProfile/title_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.study-studes-card-study-time-line {
|
||||
width: 290px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
background-color: #f4f7f9;
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
overflow: visible;
|
||||
> i {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
display: block;
|
||||
right: 0;
|
||||
top: -25px;
|
||||
transform: translateX(50%);
|
||||
font-style: normal;
|
||||
width: 46px;
|
||||
height: 24px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1d2129;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.icon1 {
|
||||
background-image: url("@/assets/images/PersonalProfile/line_icon1.png");
|
||||
}
|
||||
.icon2 {
|
||||
background-image: url("@/assets/images/PersonalProfile/line_icon2.png");
|
||||
}
|
||||
}
|
||||
.line1 {
|
||||
background: linear-gradient(to right, #2c7dff, #2da7ff);
|
||||
}
|
||||
.line2 {
|
||||
background: linear-gradient(to right, #37c850, #86e263);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.study-studes-card-study-progress {
|
||||
height: 310px;
|
||||
width: 356px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.study-studes-card-curriculum {
|
||||
width: 232px;
|
||||
height: 390px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||
padding: 10px;
|
||||
|
||||
.study-studes-card-curriculum-title {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-left: 30px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-image: url("@/assets/images/PersonalProfile/title_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.study-studes-card-curriculum-chart {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.study-studes-card-curriculum-info:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.study-studes-card-curriculum-info {
|
||||
width: 200px;
|
||||
height: 73px;
|
||||
border-radius: 12px;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
> i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.my-i {
|
||||
background-image: linear-gradient(0deg, #2c7aff, #2cb1ff);
|
||||
}
|
||||
.class-i {
|
||||
background-image: linear-gradient(0deg, #25c343, #99e869);
|
||||
}
|
||||
.loss-i {
|
||||
background-image: linear-gradient(0deg, #ff9a2d, #ffc02d);
|
||||
}
|
||||
> span {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #1d2129;
|
||||
}
|
||||
}
|
||||
> span {
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
font-family: "HarmonyOS Sans TC";
|
||||
color: #1d2129;
|
||||
}
|
||||
}
|
||||
}
|
||||
.study-studes-card-curriculum-chart-center-content {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border-radius: 50%;
|
||||
line-height: 110px;
|
||||
color: #1d2129;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
background-color: #e8f3ff;
|
||||
box-shadow: inset 0 0 15.6px 0 rgba(0, 172, 255, 0.2); /* #0AC3FF 转 rgba,20% 透明度 */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
import * as echarts from "echarts";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Spin } from "@arco-design/web-react";
|
||||
import IconFont from "@/components/IconFont";
|
||||
import EchartsProgress from "@/components/EchartsProgress";
|
||||
import ScoreRingChart from "../ScoreRingChart";
|
||||
import AttendanceRingChart from "../AttendanceRingChart";
|
||||
import "./index.css";
|
||||
|
||||
const StudyStudes = ({ studyStatistics, learningProgress, loading }) => {
|
||||
const studentInfo = useSelector((state) => state.student.studentInfo);
|
||||
|
||||
// 使用传入的数据,只在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: "#2CB9FF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
value: classAverageCourseProgress,
|
||||
// name: "班级",
|
||||
// title: {
|
||||
// offsetCenter: ["-60%", "-90%"],
|
||||
// },
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#25C343" },
|
||||
{ offset: 1, color: "#A3EB6C" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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: "#2CB9FF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
value: classAverageHomeworkProgress,
|
||||
// name: "班级",
|
||||
// title: {
|
||||
// offsetCenter: ["-60%", "-90%"],
|
||||
// },
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#25C343" },
|
||||
{ offset: 1, color: "#A3EB6C" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const attendanceData = [
|
||||
{
|
||||
name: "出勤率",
|
||||
value: attendanceRate,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#2C7AFF" },
|
||||
{ offset: 1, color: "#2CB9FF" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "缺勤率",
|
||||
value: absenceRate,
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
|
||||
{ offset: 0, color: "#FF9A2D" },
|
||||
{ offset: 1, color: "#FFC02D" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="study-studes-card-wrapper">
|
||||
<p className="study-studes-card-title">
|
||||
<IconFont className="title-icon" src="recuUY5mDawm4e" />
|
||||
<span>学习情况</span>
|
||||
</p>
|
||||
{loading ? (
|
||||
<Spin size={40} className="study-studes-card-spin" />
|
||||
) : (
|
||||
<ul className="study-studes-card-list">
|
||||
{/* 时长 */}
|
||||
<li className="study-studes-card-study-info">
|
||||
<div className="study-studes-card-time-wrapper">
|
||||
<p className="study-studes-card-study-info-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', paddingLeft: '0' }}>
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '8px', flexShrink: 0 }} />
|
||||
<span style={{ fontWeight: 'bold' }}>个人学习时长(h)</span>
|
||||
</p>
|
||||
<p className="study-studes-card-study-time-line">
|
||||
<i
|
||||
className="line1"
|
||||
style={{
|
||||
width: "100%",
|
||||
position: 'relative',
|
||||
display: 'block'
|
||||
}}
|
||||
>
|
||||
<span className="icon1" style={{
|
||||
width: '60px',
|
||||
height: '32px',
|
||||
backgroundImage: 'url(https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW5McrJTuS8.png)',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
position: 'absolute',
|
||||
right: '-30px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%) translateY(-30px)',
|
||||
paddingLeft: '0'
|
||||
}}>{personalStudyHours}</span>
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
<div className="study-studes-card-time-wrapper">
|
||||
<p className="study-studes-card-study-info-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', paddingLeft: '0' }}>
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '8px', flexShrink: 0 }} />
|
||||
<span style={{ fontWeight: 'bold' }}>班级平均学习时长(h)</span>
|
||||
</p>
|
||||
<p className="study-studes-card-study-time-line study-studes-card-study-time-line-class">
|
||||
<i className="line2" style={{
|
||||
width: "89%",
|
||||
position: 'relative',
|
||||
display: 'block'
|
||||
}}>
|
||||
<span className="icon2" style={{
|
||||
width: '60px',
|
||||
height: '32px',
|
||||
backgroundImage: 'url(https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW5McGy6zLW.png)',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
position: 'absolute',
|
||||
right: '-30px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%) translateY(-30px)',
|
||||
paddingLeft: '0'
|
||||
}}>{classAverageStudyHours}</span>
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
{/* 进度 */}
|
||||
<li className="study-studes-card-study-progress">
|
||||
<EchartsProgress percent={overallProgress || 0} strokeWidth={20} />
|
||||
</li>
|
||||
{/* 课程整体完成情况 */}
|
||||
<li className="study-studes-card-curriculum">
|
||||
<p className="study-studes-card-curriculum-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', paddingLeft: '0' }}>
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '8px', flexShrink: 0 }} />
|
||||
<span style={{ fontWeight: 'bold' }}>整体课程完成情况</span>
|
||||
</p>
|
||||
<ScoreRingChart
|
||||
title={`整体课程\n完成情况`}
|
||||
ringData={courseRingData}
|
||||
className="study-studes-card-curriculum-chart"
|
||||
/>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="my-i" />
|
||||
<span>我的进度</span>
|
||||
</p>
|
||||
<span>{personalCourseProgress}%</span>
|
||||
</div>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="class-i" />
|
||||
<span>班级平均进度</span>
|
||||
</p>
|
||||
<span>{classAverageCourseProgress}%</span>
|
||||
</div>
|
||||
</li>
|
||||
{/* 课后作业完成情况 */}
|
||||
<li className="study-studes-card-curriculum-homework study-studes-card-curriculum">
|
||||
<p className="study-studes-card-curriculum-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', paddingLeft: '0' }}>
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '8px', flexShrink: 0 }} />
|
||||
<span style={{ fontWeight: 'bold' }}>课后作业完成情况</span>
|
||||
</p>
|
||||
<ScoreRingChart
|
||||
ringData={homeworkRingData}
|
||||
className="study-studes-card-curriculum-chart"
|
||||
/>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="my-i" />
|
||||
<span>我的进度</span>
|
||||
</p>
|
||||
<span>{personalHomeworkProgress}%</span>
|
||||
</div>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="class-i" />
|
||||
<span>班级平均进度</span>
|
||||
</p>
|
||||
<span>{classAverageHomeworkProgress}%</span>
|
||||
</div>
|
||||
</li>
|
||||
{/* 考勤情况 */}
|
||||
<li className="study-studes-card-curriculum">
|
||||
<p className="study-studes-card-curriculum-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', paddingLeft: '0' }}>
|
||||
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '8px', flexShrink: 0 }} />
|
||||
<span style={{ fontWeight: 'bold' }}>考勤情况</span>
|
||||
</p>
|
||||
<AttendanceRingChart
|
||||
data={attendanceData}
|
||||
centerContent={
|
||||
<div className="study-studes-card-curriculum-chart-center-content">
|
||||
考勤情况
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="my-i" />
|
||||
<span>出勤率</span>
|
||||
</p>
|
||||
<span>{attendanceRate}%</span>
|
||||
</div>
|
||||
<div className="study-studes-card-curriculum-info">
|
||||
<p>
|
||||
<i className="loss-i" />
|
||||
<span>缺勤率</span>
|
||||
</p>
|
||||
<span>{absenceRate}%</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudyStudes;
|
||||
54
frontend/src/pages/PersonalProfile/index.css
Normal file
54
frontend/src/pages/PersonalProfile/index.css
Normal file
@@ -0,0 +1,54 @@
|
||||
/* 个人档案页面样式 */
|
||||
.personal-profile {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 统一布局系统 */
|
||||
.unified-profile-layout {
|
||||
width: 100%;
|
||||
height: 805px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.unified-profile-left {
|
||||
width: 373px;
|
||||
height: 100%;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.unified-profile-rank {
|
||||
height: 420px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.unified-profile-right {
|
||||
flex: 1;
|
||||
height: 785px;
|
||||
border-radius: 16px;
|
||||
background-color: #e8f6ff;
|
||||
position: relative;
|
||||
border: 2px solid #fff;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-image: url("@/assets/images/PersonalProfile/unified_profile_right_bg.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
frontend/src/pages/PersonalProfile/index.jsx
Normal file
81
frontend/src/pages/PersonalProfile/index.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import ProfileCard from "./components/ProfileCard";
|
||||
import ClassRank from "@/components/ClassRank";
|
||||
import StageProgress from "@/components/StageProgress";
|
||||
import StudyStudes from "./components/StudyStudes";
|
||||
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
||||
import { getProfileOverview } from "@/services";
|
||||
import "./index.css";
|
||||
|
||||
const PersonalProfile = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [profileData, setProfileData] = useState(null); // 个人档案完整数据
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取个人档案完整数据
|
||||
const queryProfileOverview = async () => {
|
||||
try {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
const res = await getProfileOverview();
|
||||
setLoading(false);
|
||||
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));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch profile overview:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
queryProfileOverview();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="personal-profile">
|
||||
<StageProgress />
|
||||
|
||||
{/* 统一布局内容区域 */}
|
||||
<div className="unified-profile-layout">
|
||||
<div className="unified-profile-left">
|
||||
<ProfileCard />
|
||||
<ClassRank
|
||||
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
|
||||
studyStatistics={profileData?.studyStatistics}
|
||||
learningProgress={profileData?.learningProgress}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonalProfile;
|
||||
Reference in New Issue
Block a user