初始化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:
KQL
2025-09-24 14:14:14 +08:00
commit cd2e307402
8380 changed files with 6105118 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 转 rgba20% 透明度 */
}
}
}

View File

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

View 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%;
}
}
}

View 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;