feat: 添加AI课程集成和修复初始化错误

- 将终生学习系统课添加到公共课直播间
- 修复allCalendarEvents初始化顺序问题
- 更正AI课程导师为李奇
- 添加AI课程与日历页面同步功能
This commit is contained in:
KQL
2025-09-07 23:09:48 +08:00
parent 6337cc4812
commit 27f2339c9e
81 changed files with 41799 additions and 1654 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -19,9 +19,10 @@ const Rank = ({ className, data, loading }) => {
return (
<div className={`module-class-rank ${className}`}>
<p className="module-class-rank-title">
<p className="module-class-rank-title" style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-start' }}>
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="icon" style={{ width: '24px', height: '24px', marginRight: '8px', flexShrink: 0 }} />
<IconFont className="title-icon" src="recuUY5nNf7DWT" />
<span>班级排名</span>
<span style={{ fontWeight: 'bold' }}>班级排名</span>
</p>
{loading ? (
<Spin size={40} className="module-class-rank-spin" />

View File

@@ -40,6 +40,36 @@
.course-list {
width: 100%;
/* 分割线样式 */
.course-divider {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
position: relative;
.divider-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, #e5e6eb 20%, #e5e6eb 80%, transparent);
border-style: dashed;
border-width: 1px 0 0 0;
border-color: #e5e6eb;
}
.divider-text {
padding: 0 16px;
font-size: 14px;
font-weight: 500;
color: #86909c;
background-color: #fff;
position: relative;
z-index: 1;
}
}
/* 自定义折叠面板元素 */
.course-list-item {
width: 272px;
@@ -51,6 +81,14 @@
font-weight: 400;
line-height: 21px;
border: none;
&.has-preview-unit {
.arco-collapse-item-header {
background: linear-gradient(135deg, #f0f7ff 0%, #e8f3ff 100%);
border: 1px solid #b3d4ff;
box-shadow: 0 2px 8px rgba(64, 128, 255, 0.1);
}
}
.arco-collapse-item-header {
border-radius: 8px;
@@ -275,6 +313,40 @@
border: 1px solid #ff7d00;
}
}
.preview-badge {
position: absolute;
left: 50%;
top: 60%;
transform: translate(-50%, -50%);
padding: 4px 12px;
background: #4080ff;
color: #fff;
font-size: 12px;
font-weight: 600;
border-radius: 12px;
z-index: 10;
box-shadow: 0 3px 8px rgba(64, 128, 255, 0.3);
}
@keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 3px 8px rgba(255, 107, 107, 0.3);
}
50% {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.5);
}
100% {
transform: scale(1);
box-shadow: 0 3px 8px rgba(255, 107, 107, 0.3);
}
}
.has-preview {
/* 可试看标签已调整到中心位置,不需要额外样式 */
}
}
}
}

View File

@@ -7,7 +7,8 @@ const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
const CourseList = ({ className = "", onCourseClick }) => {
const [courseLiveList, setCourseLiveList] = useState([]);
const [compoundCourseList, setCompoundCourseList] = useState([]);
const [verticalCourseList, setVerticalCourseList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
@@ -20,14 +21,19 @@ const CourseList = ({ className = "", onCourseClick }) => {
try {
const res = await getCourseLiveList();
if (res.success) {
const courseList = res.data || [];
setCourseLiveList(courseList);
const compoundList = res.data.compound || [];
const verticalList = res.data.vertical || [];
setCompoundCourseList(compoundList);
setVerticalCourseList(verticalList);
// 合并两个列表用于查找默认选中的课程
const allCourses = [...compoundList, ...verticalList];
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of courseList) {
for (const unit of allCourses) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
@@ -45,7 +51,7 @@ const CourseList = ({ className = "", onCourseClick }) => {
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of courseList) {
for (const unit of allCourses) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
@@ -123,9 +129,10 @@ const CourseList = ({ className = "", onCourseClick }) => {
className="course-list"
bordered={false}
expandIconPosition="right"
defaultActiveKey={["1"]}
defaultActiveKey={[]}
>
{courseLiveList.map((unit, index) => (
{/* 复合能力课部分 */}
{compoundCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
@@ -167,6 +174,97 @@ const CourseList = ({ className = "", onCourseClick }) => {
</Timeline>
</CollapseItem>
))}
{/* 分割线 */}
{compoundCourseList.length > 0 && verticalCourseList.length > 0 && (
<div className="course-divider">
<span className="divider-line"></span>
<span className="divider-text">垂直能力课</span>
<span className="divider-line"></span>
</div>
)}
{/* 垂直能力课部分 */}
{verticalCourseList.map((unit, index) => {
// 检查单元是否包含可试看课程
const hasPreviewCourse = unit.courses.some(course => course.canPreview);
return (
<CollapseItem
key={unit.unitId}
header={
<div style={{
display: 'flex',
alignItems: 'center',
width: '100%',
position: 'relative'
}}>
{unit.unitName}
{hasPreviewCourse && (
<span style={{
marginLeft: '8px',
padding: '2px 8px',
background: '#4080ff',
color: '#fff',
fontSize: '11px',
fontWeight: '600',
borderRadius: '10px'
}}>
可试看
</span>
)}
</div>
}
name={`vertical-${index + 1}`}
className={`course-list-item vertical-course ${hasPreviewCourse ? 'has-preview-unit' : ''}`}
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''} ${course.canPreview ? 'has-preview' : ''}`}
onClick={() => {
// 先设置选中状态和触发课程点击事件
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName, courseType: 'vertical' });
// 如果是可试看课程,延迟打开新窗口
if (course.canPreview && course.previewUrl) {
setTimeout(() => {
window.open(course.previewUrl, '_blank');
}, 100);
}
}}
style={{ cursor: 'pointer' }}
>
{course.canPreview && (
<span className="preview-badge">可试看</span>
)}
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
);
})}
</Collapse>
</div>
</div>

View File

@@ -0,0 +1,236 @@
import { useState, useEffect } from "react";
import { Collapse, Timeline, Spin } from "@arco-design/web-react";
import { getCourseLiveList } from "@/services/courseLive";
import "./index.css";
const TimelineItem = Timeline.Item;
const CollapseItem = Collapse.Item;
const CourseList = ({ className = "", onCourseClick }) => {
const [compoundCourseList, setCompoundCourseList] = useState([]);
const [verticalCourseList, setVerticalCourseList] = useState([]);
const [loading, setLoading] = useState(false);
const [selectedCourseId, setSelectedCourseId] = useState(null);
useEffect(() => {
fetchCourseList();
}, []);
const fetchCourseList = async () => {
setLoading(true);
try {
const res = await getCourseLiveList();
if (res.success) {
const compoundList = res.data.compound || [];
const verticalList = res.data.vertical || [];
setCompoundCourseList(compoundList);
setVerticalCourseList(verticalList);
// 合并两个列表用于查找默认选中的课程
const allCourses = [...compoundList, ...verticalList];
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of allCourses) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
// 触发课程选择事件
if (onCourseClick) {
onCourseClick({
...todayCourse,
unitName: unit.unitName
});
}
foundTodayCourse = true;
break;
}
}
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of allCourses) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
if (onCourseClick) {
onCourseClick({
...activeCourse,
unitName: unit.unitName
});
}
break;
}
}
}
}
} catch (error) {
console.error("获取课程列表失败:", error);
} finally {
setLoading(false);
}
};
// 判断课程状态
const getCourseStatus = (course) => {
if (course.completed) return "finish";
if (course.current) return "active";
// 判断未来课程的具体状态
if (course.upcoming) {
const courseDate = new Date(course.date);
const today = new Date();
// 重置时间部分只比较日期
courseDate.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
const timeDiff = courseDate - today;
const daysDiff = Math.floor(timeDiff / (24 * 60 * 60 * 1000));
// 未来7天内的课程显示为"即将开始"
if (daysDiff > 0 && daysDiff <= 7) {
return "coming";
}
// 7天后的课程显示为"未开始"
return "not-started";
}
// 默认状态
return "pending";
};
// 获取图标类型
const getDotIcon = (course) => {
if (course.completed) return <div className="time-line-dot-icon" />;
if (course.current) return <div className="time-line-clock-icon" />;
return <div className="time-line-lock-icon" />;
};
if (loading) {
return (
<div className={`${className} course-list-wrapper`}>
<p className="course-list-title">课程列表</p>
<div className="course-list-content">
<Spin />
</div>
</div>
);
}
return (
<div className={`${className} course-list-wrapper`}>
<p className="course-list-title">课程列表</p>
<div className="course-list-content">
<Collapse
lazyload
className="course-list"
bordered={false}
expandIconPosition="right"
defaultActiveKey={[]}
>
{/* 复合能力课部分 */}
{compoundCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
name={String(index + 1)}
className="course-list-item"
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''}`}
onClick={() => {
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName });
}}
style={{ cursor: 'pointer' }}
>
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
))}
{/* 分割线 */}
{compoundCourseList.length > 0 && verticalCourseList.length > 0 && (
<div className="course-divider">
<span className="divider-line"></span>
<span className="divider-text">垂直能力课</span>
<span className="divider-line"></span>
</div>
)}
{/* 垂直能力课部分 */}
{verticalCourseList.map((unit, index) => (
<CollapseItem
key={unit.unitId}
header={unit.unitName}
name={`vertical-${index + 1}`}
className="course-list-item vertical-course"
>
<Timeline>
{unit.courses.map((course) => (
<TimelineItem
key={course.courseId}
dot={getDotIcon(course)}
lineType="dashed"
>
<div
className={`time-line-item ${getCourseStatus(course)} ${selectedCourseId === course.courseId ? 'selected' : ''}`}
onClick={() => {
setSelectedCourseId(course.courseId);
onCourseClick && onCourseClick({ ...course, unitName: unit.unitName, courseType: 'vertical' });
}}
style={{ cursor: 'pointer' }}
>
<p style={{
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal',
wordBreak: 'break-word',
WebkitLineClamp: 'unset',
WebkitBoxOrient: 'unset',
display: 'block',
maxWidth: 'calc(100% - 70px)'
}}>{course.courseName}</p>
<div className="time-line-item-info">
<span>{course.teacherName}</span>
<span>{course.date}</span>
</div>
</div>
</TimelineItem>
))}
</Timeline>
</CollapseItem>
))}
</Collapse>
</div>
</div>
);
};
export default CourseList;

View File

@@ -90,17 +90,30 @@
overflow: hidden;
img {
width: 150%;
height: 150%;
width: 200%;
height: 200%;
object-fit: cover;
object-position: center 30%;
position: absolute;
left: 50%;
top: 2%;
top: 0%;
transform: translateX(-50%);
}
}
/* 孙应战导师头像特殊调整 - 居中显示 */
.teacher-avatar.teacher-sunyingzhan {
img {
width: 175% !important;
height: 175% !important;
object-fit: cover !important;
object-position: center center !important;
top: -50% !important;
left: 5% !important;
transform: none !important;
}
}
/* 刘杰导师头像特殊调整 */
.teacher-avatar.teacher-liujie {
img {
@@ -111,6 +124,17 @@
}
}
/* 求职策略页面的头像特殊调整 */
.teacher-avatar.teacher-strategy {
img {
width: 150% !important;
height: 150% !important;
object-fit: cover !important;
object-position: center 40% !important;
top: 0% !important;
}
}
.avatar-wrapper {
position: relative;
width: 64px;
@@ -236,18 +260,19 @@
}
.courses-video-player-teacher-tags {
width: 100%;
height: 50px;
height: auto;
min-height: 50px;
.teacher-tags {
width: 100%;
height: 24px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
overflow-x: auto;
margin-top: 5px;
gap: 8px;
overflow: hidden;
> li {
box-sizing: border-box;
@@ -256,10 +281,9 @@
color: #0077ff;
font-size: 14px;
font-weight: 400;
margin-right: 10px;
border-radius: 4px;
flex-shrink: 0;
flex-wrap: nowrap;
white-space: nowrap;
}
}
}

View File

@@ -1,4 +1,4 @@
import { Avatar } from "@arco-design/web-react";
import { Avatar, Tooltip } from "@arco-design/web-react";
import Locked from "@/components/Locked";
import "./index.css";
@@ -7,19 +7,37 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
console.log(item);
};
// 默认导师信息 - 魏立慧老师(用于求职策略定制页面)
const defaultTeacher = {
name: "魏立慧",
introduction: "企业资深一线HR专注于为求职者提供一对一的个性化指导。通过真实招聘视角深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径帮助学生快速提升求职竞争力。求职策略以实用落地为核心注重互动交流与角色定位让学员在轻松氛围中获得直击痛点的求职策略。",
specialties: ["深谙用人逻辑", "擅长挖掘优势", "沟通真诚自然", "点评直击要害"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png",
type: "企业资深HR"
};
// 1V1求职规划课程信息
const oneOnOneCourse = {
courseName: "1V1 求职规划",
date: "2024-08-25",
unitName: "1V1 规划阶段",
teacherName: "魏立慧",
current: false
};
// 获取当前课程的导师信息
const currentTeacher = selectedCourse && teacherData
? teacherData[selectedCourse.teacherName]
: teacherData?.["魏立慧"] || {
name: "魏立慧",
introduction: "企业资深一线HR专注于为求职者提供一对一的个性化指导。通过真实招聘视角深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径帮助学生快速提升求职竞争力。求职策略以实用落地为核心注重互动交流与角色定位让学员在轻松氛围中获得直击痛点的求职策略。",
specialties: ["深谙用人逻辑", "擅长挖掘优势", "沟通真诚自然", "点评直击要害"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png",
type: "企业资深HR"
};
// 如果是锁定状态(求职策略页面),始终显示魏立慧老师
const currentTeacher = isLock
? defaultTeacher
: (selectedCourse && teacherData && teacherData[selectedCourse.teacherName]
? teacherData[selectedCourse.teacherName]
: null);
// 如果是锁定状态使用1V1求职规划课程信息
const displayCourse = isLock ? oneOnOneCourse : selectedCourse;
// 需要调整头像位置的导师
const needsAdjustment = ["赵志强", "魏立慧", "郭建辉"].includes(currentTeacher.name);
const needsAdjustment = currentTeacher && ["赵志强", "魏立慧", "郭建辉", "孙应战"].includes(currentTeacher.name);
// 根据导师设置不同的背景色 - 这些颜色提取自实际的PNG图片背景
const getAvatarBackground = (name) => {
@@ -27,16 +45,16 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
"刘杰": "#E3E2E0", // 浅灰色
"郭建辉": "#E0D9D3", // 米灰色
"赵志强": "#E3E2E0", // 浅灰色
"孙应战": "#E3E2E0", // 浅灰
"孙应战": "#FFFFFF", //
"魏立慧": "#DCD8D4" // 灰褐色
};
return backgrounds[name] || "#E3E2E0";
};
// 获取当前课程信息
const courseName = selectedCourse?.courseName || "钢铁是怎样炼成的";
const courseDate = selectedCourse?.date || "09.01";
const unitName = selectedCourse?.unitName || "教育体系认知";
const courseName = selectedCourse?.courseName || "";
const courseDate = selectedCourse?.date || "";
const unitName = selectedCourse?.unitName || "";
// 格式化日期时间
const formatDateTime = (date) => {
@@ -79,99 +97,171 @@ export default ({ className = "", isLock = false, selectedCourse, teacherData, u
<span onClick={() => handleClickBtn(2)}>下一集 &gt;</span>
</div>
<div className="courses-video-player-video">
{/* 所有课程都显示模糊的海报图和锁定状态 */}
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<img
src={unitPosters?.[unitName] || unitPosters?.["岗位体系认知"]}
alt={unitName}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
filter: 'blur(20px)'
}}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
{selectedCourse ? (
/* 选中课程时显示模糊的海报图和锁定状态 */
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<img
src={unitPosters?.[unitName] || unitPosters?.["岗位体系认知"]}
alt={unitName}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
filter: 'blur(10px)'
}}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '16px'
}}>
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
alt="lock"
style={{ width: '280px', height: '280px' }}
/>
<span style={{
color: '#fff',
fontSize: '16px',
fontWeight: '500',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: '8px 16px',
borderRadius: '4px'
}}>
DEMO演示非学员无查看权限
</span>
</div>
</div>
) : (
/* 未选中课程时显示黑屏 */
<div style={{
width: '100%',
height: '100%',
backgroundColor: '#000',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '16px'
justifyContent: 'center'
}}>
<img
src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVOrz2GnJdK.png"
alt="lock"
style={{ width: '280px', height: '280px' }}
/>
<span style={{
color: '#fff',
fontSize: '16px',
fontWeight: '500',
textAlign: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
padding: '8px 16px',
borderRadius: '4px'
color: '#666',
fontSize: '16px'
}}>
DEMO演示非学员无查看权限
请选择课程开始观看
</span>
</div>
</div>
)}
</div>
</>
)}
</div>
<div className="courses-video-player-info">
{/* 直播观众信息 */}
<div className="courses-video-player-audience-info">
<div className="avatar-wrapper">
<Avatar
className={`teacher-avatar ${needsAdjustment ? 'avatar-adjust' : ''} ${currentTeacher.name === '刘杰' ? 'teacher-liujie' : ''}`}
style={{ backgroundColor: getAvatarBackground(currentTeacher.name) }}
>
<img
alt="avatar"
src={currentTeacher.avatar}
/>
</Avatar>
{selectedCourse?.current && <div className="living-icon" />}
</div>
<span className="teacher-name">{currentTeacher.name}老师</span>
<span className="teacher-tag">{unitName}</span>
<div className="living-data">
<div className="living-data-item">
<span>开始</span>
<span>{formatDateTime(courseDate)} - 14:00</span>
{(isLock || (selectedCourse && currentTeacher)) ? (
<>
{/* 直播观众信息 */}
<div className="courses-video-player-audience-info">
<div className="avatar-wrapper">
<Avatar
className={`teacher-avatar ${needsAdjustment ? 'avatar-adjust' : ''} ${currentTeacher?.name === '刘杰' ? 'teacher-liujie' : ''} ${currentTeacher?.name === '孙应战' ? 'teacher-sunyingzhan' : ''} ${isLock ? 'teacher-strategy' : ''}`}
style={{ backgroundColor: getAvatarBackground(currentTeacher?.name) }}
>
<img
alt="avatar"
src={currentTeacher?.avatar || ''}
/>
</Avatar>
{displayCourse?.current && <div className="living-icon" />}
</div>
<span className="teacher-name">{currentTeacher?.name || ''}老师</span>
<span className="teacher-tag">{displayCourse?.unitName || unitName}</span>
<div className="living-data">
<div className="living-data-item">
<span>开始</span>
<span>{formatDateTime(displayCourse?.date || courseDate)} - 14:00</span>
</div>
<div className="living-data-item">
<span>时长</span>
<span>{isLock ? '120分钟' : '60分钟'}</span>
</div>
<div className="living-data-item">
<span>观看</span>
<span>{isLock ? '1人' : `${
selectedCourse?.courseType === 'vertical'
? 50 + Math.floor(Math.random() * 11) // 垂直课50-60人
: 180 + Math.floor(Math.random() * 21) // 复合课180-200人
}`}</span>
</div>
</div>
</div>
<div className="living-data-item">
<span>时长</span>
<span>60分钟</span>
</div>
<div className="living-data-item">
<span>观看</span>
<span>{selectedCourse ? '197人' : '1928人'}</span>
{/* 直播教师信息 */}
<div className="courses-video-player-teacher-info">
<div className="courses-video-player-teacher-introduce">
<p className="title icon1">导师介绍</p>
<p className="teacher-introduce">
{currentTeacher?.introduction || ''}
</p>
</div>
<div className="courses-video-player-teacher-tags">
<p className="title icon2">教师专长</p>
<Tooltip
content={
<div style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
maxWidth: '320px',
padding: '8px'
}}>
{(currentTeacher?.specialties || []).map((specialty, index) => (
<span key={index} style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: '500',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
{specialty}
</span>
))}
</div>
}
position="top"
color="#fff"
style={{
'--arco-color-bg-tooltip': 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
'--arco-color-text-tooltip': '#1d2129'
}}
>
<ul className="teacher-tags">
{(currentTeacher?.specialties || []).map((specialty, index) => (
<li key={index}>{specialty}</li>
))}
</ul>
</Tooltip>
</div>
</div>
</>
) : (
/* 未选中课程时显示空白 */
<div style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#86909c',
fontSize: '14px'
}}>
请选择课程查看导师信息
</div>
</div>
{/* 直播教师信息 */}
<div className="courses-video-player-teacher-info">
<div className="courses-video-player-teacher-introduce">
<p className="title icon1">导师介绍</p>
<p className="teacher-introduce">
{currentTeacher.introduction}
</p>
</div>
<div className="courses-video-player-teacher-tags">
<p className="title icon2">教师专长</p>
<ul className="teacher-tags">
{currentTeacher.specialties.map((specialty, index) => (
<li key={index}>{specialty}</li>
))}
</ul>
</div>
</div>
)}
</div>
</div>
);

View File

@@ -192,6 +192,12 @@
gap: 5px;
}
.course-list-item .time-line-item .course-date {
font-size: 12px;
color: #86909c;
margin-left: auto;
}
.course-list-item .finish::before {
content: "已结束";
position: absolute;

View File

@@ -22,43 +22,7 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
if (res.success) {
const courseList = res.data || [];
setCourseLiveList(courseList);
// 设置默认选中今天的课程(如果有)
const todayStr = new Date().toISOString().split('T')[0];
let foundTodayCourse = false;
for (const unit of courseList) {
const todayCourse = unit.courses.find(c => c.date === todayStr);
if (todayCourse) {
setSelectedCourseId(todayCourse.courseId);
// 触发课程选择事件
if (onCourseClick) {
onCourseClick({
...todayCourse,
unitName: unit.unitName
});
}
foundTodayCourse = true;
break;
}
}
// 如果没有今天的课程选中第一个current或upcoming的课程
if (!foundTodayCourse) {
for (const unit of courseList) {
const activeCourse = unit.courses.find(c => c.current || c.upcoming);
if (activeCourse) {
setSelectedCourseId(activeCourse.courseId);
if (onCourseClick) {
onCourseClick({
...activeCourse,
unitName: unit.unitName
});
}
break;
}
}
}
// 不设置默认选中,保持黑屏状态
}
} catch (error) {
console.error("Failed to fetch course list:", error);
@@ -122,7 +86,7 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
className="course-list"
bordered={false}
expandIconPosition="right"
defaultActiveKey={["1", "2", "3"]}
defaultActiveKey={[]}
>
{courseLiveList.map((unit, index) => (
<CollapseItem
@@ -155,9 +119,8 @@ const PublicCourseList = ({ className = "", onCourseClick }) => {
{course.courseName}
</p>
<div className="time-line-item-info">
<span>讲师{course.teacherName}</span>
{course.date && <span>{course.date}</span>}
{course.time && <span>{course.time}</span>}
<span>{course.teacherName}</span>
<span className="course-date">{course.date}</span>
</div>
</div>
</TimelineItem>

View File

@@ -17,11 +17,12 @@
height: 64px;
background-size: 100% 100%;
margin-right: -10px;
background-image: url("@/assets/images/StageProgress/step1_active.png");
background-image: url("@/assets/images/StageProgress/step1.png");
background-size: 100%;
color: #ffffff;
color: #0077ff;
font-size: 16px;
font-weight: 400;
position: relative;
}
.stage-progress-item4 {
@@ -39,11 +40,15 @@
}
.stage-progress-item4-active {
color: #ffffff;
color: #ffffff !important;
font-size: 16px;
font-weight: 400;
background-image: url("@/assets/images/StageProgress/step4_active.png");
}
.stage-progress-item4-active .stage-progress-item-text-active {
color: #ffffff !important;
}
.stage-progress-item {
flex-shrink: 0;
@@ -57,11 +62,12 @@
color: #86909c;
font-size: 16px;
font-weight: 400;
position: relative;
}
.stage-progress-item-active {
color: #ffffff;
background-image: url("@/assets/images/StageProgress/step_active.png");
color: #0077ff;
background-image: url("@/assets/images/StageProgress/step2-3.png");
}
.stage-progress-item-step {
@@ -93,6 +99,18 @@
width: 20px;
height: 20px;
}
.stage-progress-item-icon-blue {
filter: brightness(0) saturate(100%) invert(31%) sepia(98%) saturate(2000%) hue-rotate(201deg) brightness(95%) contrast(101%);
}
.stage-progress-item-icon-white {
filter: brightness(0) invert(1);
}
.stage-progress-item4-text {
color: #ffffff !important;
}
.stage-progress-item-icon2 {
width: 5px;
height: 10px;
@@ -100,7 +118,7 @@
}
.stage-progress-item-text-active {
color: #fff !important;
color: #0077ff !important;
}
.stage-progress-text {

View File

@@ -1,5 +1,6 @@
import { useNavigate } from "react-router-dom";
import IconFont from "@/components/IconFont";
import CareerStartIcon from "@/assets/images/StageProgress/career_start_icon_new.png";
import "./index.css";
const StageProgress = () => {
@@ -16,31 +17,35 @@ const StageProgress = () => {
step1
</span>
<span className="stage-progress-item-text stage-progress-item-text-active">
生涯
生涯
</span>
<IconFont src="recuUY5n0dIJKu" className="stage-progress-item-icon" />
<img
src={CareerStartIcon}
className="stage-progress-item-icon stage-progress-item-icon-blue"
alt="生涯启航"
/>
</li>
<li className="stage-progress-item">
<span className="stage-progress-item-step">step2</span>
<span className="stage-progress-item-text">能力跃升</span>
<IconFont src="recuUY5kKp4Qc5" className="stage-progress-item-icon" />
<li className="stage-progress-item stage-progress-item-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step2</span>
<span className="stage-progress-item-text stage-progress-item-text-active">能力跃升</span>
<IconFont src="recuUY5kKp4Qc5" className="stage-progress-item-icon stage-progress-item-icon-blue" />
</li>
<li className="stage-progress-text" onClick={handleClickStar}>
<span>垂直方向选择</span>
</li>
<li className="stage-progress-item">
<span className="stage-progress-item-step">step3</span>
<span className="stage-progress-item-text">垂直精进</span>
<IconFont src="recuUY5qlmzVhH" className="stage-progress-item-icon" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2" />
<IconFont src="recuUY5joxSk5C" className="stage-progress-item-icon" />
<li className="stage-progress-item stage-progress-item-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step3</span>
<span className="stage-progress-item-text stage-progress-item-text-active">垂直精进</span>
<IconFont src="recuUY5qlmzVhH" className="stage-progress-item-icon stage-progress-item-icon-blue" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2 stage-progress-item-icon-blue" />
<IconFont src="recuUY5joxSk5C" className="stage-progress-item-icon stage-progress-item-icon-blue" />
</li>
<li className="stage-progress-item4 ">
<span className="stage-progress-item-step">step4</span>
<span className="stage-progress-item-text">决胜求职</span>
<IconFont src="recuUY5lTOco3Q" className="stage-progress-item-icon" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2" />
<IconFont src="recuUY5luVMCPc" className="stage-progress-item-icon" />
<li className="stage-progress-item4 stage-progress-item4-active">
<span className="stage-progress-item-step stage-progress-item-step-active">step4</span>
<span className="stage-progress-item-text stage-progress-item-text-active stage-progress-item4-text">决胜求职</span>
<IconFont src="recuUY5lTOco3Q" className="stage-progress-item-icon stage-progress-item-icon-white" />
<IconFont src="recuUY5xdpLNXn" className="stage-progress-item-icon2 stage-progress-item-icon-white" />
<IconFont src="recuUY5luVMCPc" className="stage-progress-item-icon stage-progress-item-icon-white" />
</li>
</ul>
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
// 导入JSON数据
import companyJobsData from './companyJobs.json';
import calendarCoursesData from './calendarCourses.json';
import aiCoursesData from '../../网页未导入数据/ai课程表.json';
// 转换函数将JSON数据转换为页面所需格式
const transformCompanyJobs = (jobsData) => {
@@ -54,25 +55,46 @@ const transformCalendarCourses = (coursesData) => {
// 解析上课时间
const timeStr = course["上课时间"] || "20:00-21:00";
const [startTime, endTime] = timeStr.split('-').map(t => t.trim());
let startTime = "20:00";
let endTime = "21:00";
// 处理时间格式,确保有效
if (timeStr && timeStr.includes('-')) {
const timeParts = timeStr.split('-').map(t => t ? t.trim() : '');
if (timeParts[0]) startTime = timeParts[0];
if (timeParts[1]) endTime = timeParts[1];
} else if (timeStr) {
// 如果没有'-',假设是开始时间
startTime = timeStr.trim();
// 自动计算结束时间加1小时
const [hour, minute] = startTime.split(':');
const endHour = parseInt(hour) + 1;
endTime = `${endHour.toString().padStart(2, '0')}:${minute || '00'}`;
}
// 构建完整的时间戳
const startDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${startTime}`;
const endDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${endTime}`;
// 确定课程类型和颜色
let type = 'course';
let type = 'class';
let color = '#3b82f6'; // 默认蓝色
if (course["公开课"]) {
type = 'public';
color = '#10b981'; // 绿色
} else if (course["1V1 规划阶段"]) {
type = 'planning';
type = 'public-course';
color = '#f59e0b'; // 橙色
} else if (course["1V1 规划阶段"]) {
type = 'one-on-one';
color = '#ec4899'; // 粉色
} else if (course["模拟面试实战练习阶段"]) {
type = 'interview';
color = '#ef4444'; //
color = '#3b82f6'; //
} else if (course["复合技能阶段"]) {
type = 'compound-skill';
color = '#667eea'; // 紫色
} else if (course["垂直方向阶段(方向二:商业活动策划)"]) {
type = 'vertical-skill';
color = '#22c55e'; // 绿色
}
// 确定课程状态
@@ -104,39 +126,140 @@ const transformCalendarCourses = (coursesData) => {
});
};
// 转换AI课程数据为日历事件格式
const transformAICourses = (aiData) => {
return aiData
.filter(course => course["AI课阶段"] && course["日期"])
.map((course, index) => {
// 解析日期
const dateStr = course["日期"];
const [year, month, day] = dateStr.split('/').map(Number);
const courseDate = new Date(year, month - 1, day);
// 获取课程名称
const courseName = course["AI课阶段"];
// 解析上课时间
const timeStr = course["上课时间"] || "20:00~21:00";
let startTime = "20:00";
let endTime = "21:00";
if (timeStr && timeStr.includes('~')) {
const [start, end] = timeStr.split('~');
startTime = start.trim();
endTime = end.trim();
}
// 构建完整的时间戳
const startDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${startTime}`;
const endDateTime = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${endTime}`;
return {
id: `ai-event-${index + 1}`,
title: courseName,
startTime: startDateTime,
endTime: endDateTime,
date: courseDate,
type: 'ai-course',
color: '#10b981', // 绿色代表AI课程
teacher: '李奇', // AI课程的默认讲师
location: course["上课地点"] || "线上",
status: course["课程状态"] || "",
unit: "终生学习系统课"
};
});
};
// 生成课程直播列表:从日历课程数据生成单元和课程结构
const generateCourseLiveListFromCalendar = (calendarEvents) => {
// 按单元名称分组
let globalCourseCounter = 0; // 全局课程计数器
// 定义复合能力课的课程名称列表完整版包含所有72门课程
const 复合能力课名称 = [
// 岗位体系认知
"教育体系认知", "现代文旅类企业的管理体系", "专科生的职业规划",
// 产业认知课
"文旅产业认知课", "行业详解:旅游行业", "行业详解:酒店与民宿行业",
"行业详解:活动与会展行业", "行业详解:文化服务行业",
// 旅游产业全景与文旅基础知识
"现代文旅产业生态图谱", "文旅政策法规与风险管理", "旅游产品与旅游资源",
"游客行为心理学基础", "可持续旅游发展",
// 文旅服务:形象、沟通与体验的融合艺术
"文旅场景职业形象IP塑造", "情境化服务体验设计", "政务商务接待专项礼仪",
"文旅服务中的非语言表达", "服务沟通技巧与表达训练", "多元文化下的服务表达差异",
// 文旅与供应链基础
"供应链管理的内容", "文旅资源调度", "文旅产品全生命周期管理",
"文旅商品供应链", "住宿业资源协同", "小型文旅项目的供应链角色模拟",
"文旅项目供应链特征", "文旅供应链中的B2B与B2C模式",
// 商业设计基础
"现代设计行业的发展现状", "设计基础", "字体设计与中文字体情绪表达",
"商业平面色彩搭配", "平面设计构图", "图像编辑工具Photoshop",
"矢量与标志设计Illustrator", "快速设计工具使用Canva",
"移动端视觉原型设计Figma", "视频剪辑入门:剪映",
// AIGC人工智能生成内容
"AIGC发展简史与基本逻辑", "AIGC的基本概念与各领域的应用",
"AIGC语言模型chatgpt的灵活应用", "AIGC绘画模型midjourney应用",
"AIGC图像生成模型Stable Diffusion的应用操作",
"AIGC图像生成模型Stable Diffusion Al摄影和平面设计",
"AIGC视频应用音视频生成与AI自动剪辑", "AI词曲创作suno",
"AIGC生成内容的版权问题与合规使用",
// 全栈新媒体运营赋能文旅营销
"新媒体应用传播学", "新媒体故事结构入门", "新媒体产品策划",
"平台账号经营与内容赛道", "各平台变现方式与具体方法",
"内容运营:短视频制作内容对标", "内容运营:短视频的制作工具",
"直播运营:直播间的搭建", "私域运营:私域流量池的运营",
"品牌运营当地文化IP数字化传播", "品牌运营:跨界营销创新",
// 活动策划基础
"活动类型与功能认知", "受众定位与主题创意方法",
"活动宣传渠道与推广方式", "活动文案写作与表达技巧",
"活动流程设计与时间节点把控", "活动场地选择与布置基础",
"活动预算与资源统筹", "活动复盘报告撰写与数据分析方法",
"应急预案与活动风险管理",
// 智慧文旅应用
"智慧文旅概论", "OTA平台运营", "票务分销平台",
"景区智能导览系统", "智能导览设备运用",
"智慧酒店/智慧景区体验场景模拟",
// 单元小结(多个单元都有)
"单元小结"
];
// 转换为Set以提高查找效率
const 复合能力课Set = new Set(复合能力课名称);
// 按单元名称分组,只包含复合能力课
const unitMap = {};
calendarEvents.forEach(event => {
const unitName = event.unit || "未分类课程";
if (!unitMap[unitName]) {
unitMap[unitName] = {
unitId: `unit-${Object.keys(unitMap).length + 1}`,
unitName: unitName,
courses: []
};
// 只处理复合能力课标题在复合能力课列表中或type包含skill/class
const isSkillCourse = ['compound-skill', 'vertical-skill', 'class'].includes(event.type);
if ((isSkillCourse || event.type === 'course') && event.title && 复合能力课Set.has(event.title)) {
const unitName = event.unit || "未分类课程";
if (!unitMap[unitName]) {
unitMap[unitName] = {
unitId: `unit-${Object.keys(unitMap).length + 1}`,
unitName: unitName,
courses: []
};
}
const courseDate = new Date(event.startTime.split(' ')[0]);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const courseDay = new Date(courseDate.getFullYear(), courseDate.getMonth(), courseDate.getDate());
unitMap[unitName].courses.push({
courseId: `c${++globalCourseCounter}`,
courseName: event.title,
teacherName: event.teacher,
date: event.startTime.split(' ')[0],
time: event.startTime.split(' ')[1] + ' - ' + event.endTime.split(' ')[1],
completed: courseDay < today,
current: courseDay.getTime() === today.getTime(),
upcoming: courseDay > today,
status: event.status,
location: event.location || "线上"
});
}
const courseDate = new Date(event.startTime.split(' ')[0]);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const courseDay = new Date(courseDate.getFullYear(), courseDate.getMonth(), courseDate.getDate());
unitMap[unitName].courses.push({
courseId: `c${unitMap[unitName].courses.length + 1}`,
courseName: event.title,
teacherName: event.teacher,
date: event.startTime.split(' ')[0],
time: event.startTime.split(' ')[1] + ' - ' + event.endTime.split(' ')[1],
completed: courseDay < today,
current: courseDay.getTime() === today.getTime(),
upcoming: courseDay > today,
status: event.status,
location: event.location || "线上"
});
});
// 转换为数组并排序
@@ -146,80 +269,160 @@ const generateCourseLiveListFromCalendar = (calendarEvents) => {
const dateB = b.courses[0]?.date || '9999-12-31';
return dateA.localeCompare(dateB);
});
};
};;;
// 生成公共课程列表:从课程直播列表中提取公共课程
const generatePublicCourseLiveList = (courseLiveList) => {
// 生成公共课程列表:从日历事件中提取公共课程
const generatePublicCourseLiveList = (calendarEvents) => {
// 定义公共课单元
const publicUnits = {
"AI课": {
unitId: "public-ai",
unitName: "AI课",
courses: []
},
"企业高管公开课": {
unitId: "public-executive",
unitName: "企业高管公开课",
"终生学习系统课": {
unitId: "public-ai-learning",
unitName: "终生学习系统课",
courses: []
},
"营销能力课": {
unitId: "public-marketing",
unitName: "营销能力课",
courses: []
},
"企业高管公开课": {
unitId: "public-executive",
unitName: "企业高管公开课",
courses: []
}
};
// 定义关键词映射到单元
const keywordToUnit = {
"AI": "AI课",
"AIGC": "AI课",
"人工智能": "AI课",
"ChatGPT": "AI课",
"Midjourney": "AI课",
"企业": "企业高管公开课",
"高管": "企业高管公开课",
"管理": "企业高管公开课",
"领导": "企业高管公开课",
"战略": "企业高管公开课",
"营销": "营销能力课",
"销售": "营销能力课",
"品牌": "营销能力课",
"推广": "营销能力课",
"市场": "营销能力课",
"运营": "营销能力课",
"新媒体": "营销能力课"
// 导师头像映射
const teacherAvatars = {
"孙应战": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpJCc6qecx.jpg",
"李毅峰": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVPz0WRmxCK.jpeg",
"周伏波": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7Gi9YxSN.jpeg",
"范雪娇": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7JsHHDNZ.jpeg",
"李奇": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7LDWK6iG.jpg"
};
// 遍历所有课程,分类到相应单元
courseLiveList.forEach(unit => {
unit.courses.forEach(course => {
let assigned = false;
// 遍历日历事件找出公开课和AI课程
calendarEvents.forEach(event => {
// 处理type为'public-course'的课程或AI课程
if ((event.type === 'public-course' || event.type === 'ai-course') && event.title) {
const courseDate = new Date(event.startTime.split(' ')[0]);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const courseDay = new Date(courseDate.getFullYear(), courseDate.getMonth(), courseDate.getDate());
// 检查课程名称是否包含关键词
for (const [keyword, unitName] of Object.entries(keywordToUnit)) {
if (course.courseName.includes(keyword) || unit.unitName.includes(keyword)) {
publicUnits[unitName].courses.push({
...course,
originalUnit: unit.unitName
});
assigned = true;
break;
}
}
const courseData = {
courseId: `public-${publicUnits["终生学习系统课"].courses.length + publicUnits["营销能力课"].courses.length + publicUnits["企业高管公开课"].courses.length + 1}`,
courseName: event.title,
teacherName: event.teacher,
teacherAvatar: teacherAvatars[event.teacher] || "",
date: event.startTime.split(' ')[0],
time: event.startTime.split(' ')[1] + ' - ' + event.endTime.split(' ')[1],
completed: courseDay < today,
current: courseDay.getTime() === today.getTime(),
upcoming: courseDay > today,
status: event.status,
location: "线上",
duration: "60分钟",
participants: 150
};
// 如果没有匹配到任何公共课单元,检查是否是公开课类型
if (!assigned && (unit.unitName.includes("公开课") || unit.unitName.includes("公共"))) {
// 默认归类到企业高管公开课
publicUnits["企业高管公开课"].courses.push({
...course,
originalUnit: unit.unitName
});
// 根据课程类型和讲师分类
if (event.type === 'ai-course') {
publicUnits["终生学习系统课"].courses.push(courseData);
} else if (event.teacher === "孙应战") {
publicUnits["营销能力课"].courses.push(courseData);
} else {
publicUnits["企业高管公开课"].courses.push(courseData);
}
});
}
});
// 转换为数组格式并过滤空单元
return Object.values(publicUnits).filter(unit => unit.courses.length > 0);
// 转换为数组格式并过滤空单元,按顺序返回:终生学习系统课、企业高管公开课、营销能力课
const result = [];
if (publicUnits["终生学习系统课"].courses.length > 0) {
result.push(publicUnits["终生学习系统课"]);
}
if (publicUnits["企业高管公开课"].courses.length > 0) {
result.push(publicUnits["企业高管公开课"]);
}
if (publicUnits["营销能力课"].courses.length > 0) {
result.push(publicUnits["营销能力课"]);
}
return result;
};
// 生成垂直能力课列表:从日历事件中提取垂直能力课程
const generateVerticalCourseLiveList = (calendarEvents, unitPosters = {}) => {
let globalCourseCounter = 0; // 全局课程计数器
// 定义垂直能力课单元名称映射
const unitNameMap = {
"职业规划课": "职业规划课",
"商业活动策略设计与创意策划": "商业活动策略设计与创意策划",
"商业活动全程策划执行与运营优化": "商业活动全程策划执行与运营优化",
"商业空间与文创产品设计": "商业空间与文创产品设计",
"短视频与自媒体运营": "短视频与自媒体运营",
"漫展与二次元活动策划与执行": "漫展与二次元活动策划与执行",
"户外音乐节主题策划与流程统筹": "户外音乐节主题策划与流程统筹",
"城市 IP 赛事活动整合与策划": "城市 IP 赛事活动整合与策划",
"消费电子展品牌策划与执行": "消费电子展品牌策划与执行",
"品牌招商展全案策划与招商运营": "品牌招商展全案策划与招商运营",
"商业街区打卡空间视觉呈现": "商业街区打卡空间视觉呈现",
"文旅衍生文创产品设计": "文旅衍生文创产品设计"
};
// 按单元名称分组
const unitMap = {};
calendarEvents.forEach(event => {
// 处理垂直能力课type为'vertical-skill'
if (event.type === 'vertical-skill' && event.title) {
const unitName = event.unit || "未分类课程";
if (!unitMap[unitName]) {
unitMap[unitName] = {
unitId: `vertical-unit-${Object.keys(unitMap).length + 1}`,
unitName: unitNameMap[unitName] || unitName,
unitPoster: unitPosters[unitName] || "", // 添加单元海报
courses: []
};
}
const courseDate = new Date(event.startTime.split(' ')[0]);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const courseDay = new Date(courseDate.getFullYear(), courseDate.getMonth(), courseDate.getDate());
// 创建课程对象
const courseObj = {
courseId: `v${++globalCourseCounter}`,
courseName: event.title,
teacherName: event.teacher,
date: event.startTime.split(' ')[0],
time: event.startTime.split(' ')[1] + ' - ' + event.endTime.split(' ')[1],
completed: courseDay < today,
current: courseDay.getTime() === today.getTime(),
upcoming: courseDay > today,
status: event.status,
location: event.location || "线上"
};
// 为"展会主题与品牌定位"课程添加试看标签和链接
if (event.title === "展会主题与品牌定位" && unitName === "消费电子展品牌策划与执行") {
courseObj.canPreview = true;
courseObj.previewUrl = "https://du9uay.github.io/zhanhui/";
}
unitMap[unitName].courses.push(courseObj);
}
});
// 转换为数组并排序
return Object.values(unitMap).sort((a, b) => {
// 按第一个课程的日期排序
const dateA = a.courses[0]?.date || '9999-12-31';
const dateB = b.courses[0]?.date || '9999-12-31';
return dateA.localeCompare(dateB);
});
};
// 更新导师课程信息:根据日历事件更新导师的课程列表
@@ -267,6 +470,12 @@ const generateTasksFromCalendarEvents = (calendarEvents) => {
};
// 模拟数据
// 合并普通课程和AI课程需要在mockData之前定义避免引用错误
const allCalendarEvents = [
...transformCalendarCourses(calendarCoursesData),
...transformAICourses(aiCoursesData)
];
export const mockData = {
// 用户信息
user: {
@@ -313,7 +522,20 @@ export const mockData = {
"AIGC人工智能生成内容": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJyThB.png",
" 全栈新媒体运营赋能文旅营销": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJpmik.png",
"活动策划基础": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJMV4g.png",
"智慧文旅应用": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJWckP.png"
"智慧文旅应用": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJWckP.png",
// 垂直能力课单元海报
"职业规划课": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJm4HJ.png",
"商业活动策略设计与创意策划": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJRS3b.png",
"商业活动全程策划执行与运营优化": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJnGUK.png",
"商业空间与文创产品设计": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJp9xP.png",
"短视频与自媒体运营": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJr7sV.png",
"漫展与二次元活动策划与执行": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJy73s.png",
"户外音乐节主题策划与流程统筹": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJA8Az.png",
"城市 IP 赛事活动整合与策划": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJzIDN.png",
"消费电子展品牌策划与执行": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJ99vz.png",
"品牌招商展全案策划与招商运营": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJeHTm.png",
"商业街区打卡空间视觉呈现": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJYZlr.png",
"文旅衍生文创产品设计": "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/butler_course_cover/compression/recuSHuSaJsA0q.png"
},
// 导师信息数据
@@ -321,9 +543,10 @@ export const mockData = {
"刘杰": {
name: "刘杰",
introduction: "15年民宿产业化研发与标准设计经验专注民宿产品创新、职业化培训体系搭建及乡村文旅业态升级主导多项国家级行业标准制定与落地实践。木亚文旅创始人兼董事长全面负责民宿产业研发、标准制定及全国业务拓展同时也是莫干山民宿行业协会执行会长兼秘书长以及浙江大学城市学院旅游管理专业校外导师。曾作为核心起草人主持《乡村民宿服务质量规范》《民宿管家职业技能等级评定规范》等国标编制推动民宿行业标准化发展申报并推动'民宿管家'入选国家新职业目录,建立首个国家级职业技能评定体系。",
specialties: ["一听就懂", "思路很清晰", "课堂不枯燥", "学习氛围爆棚"],
specialties: ["民宿产业化专家","一听就懂", "思路很清晰", "课堂不枯燥", "学习氛围爆棚"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpJBE4VCCx.png",
type: "复合课导师",
verticalDirection: "项目经营管理",
courses: []
},
"郭建辉": {
@@ -332,33 +555,67 @@ export const mockData = {
specialties: ["经验传授权威", "实用可落地", "表达沟通提升", "跨界经验丰富"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpJBMNLZL5.png",
type: "复合课导师",
verticalDirection: "商业活动策划",
courses: []
},
"赵志强": {
name: "赵志强",
introduction: "深耕乡村旅游与民宿产业12年从一线管家服务到国家级标准制定全程参与中国民宿职业化与产业化进程。曾作为首席讲师为苏浙沪皖四地文旅部门提供'乡村振兴十大模式'培训,设计桑蚕文化园、谢家路村等沉浸式研学路线,促成区域人才协作机制,获江苏省人社厅列为省级高级研修示范项目。曾担任标准起草专家组核心成员,牵头《德清县地方民宿管理办法》试点,联合政府申报'民宿管理师'新职业,制定长三角区域民宿评级体系,推动了紫蓬山民宿学校(安徽)、吴江农文旅融合示范区落地,形成可复制的'民宿+'乡村振兴模式。",
specialties: ["讲解有节奏", "讲解通俗", "幽默又有料", "善于启发"],
specialties: ["乡村振兴实践者", "政企协作桥梁","讲解有节奏", "讲解通俗", "幽默又有料", "善于启发"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpJC2oLPpC.png",
type: "复合课导师",
verticalDirection: "文化服务",
courses: []
},
"孙应战": {
name: "孙应战",
introduction: "拥有18年制造业与产品营销经验曾在上市公司与世界500强外企任职参与大众MEB平台、奥迪EA888发动机及新能源汽车项目开发。作为德企内训讲师、国际演讲学会资深会员和中国心理卫生协会会员善于将实践经验转化为通俗讲解课堂氛围轻松易懂。",
specialties: ["经验传授权威", "实用可落地", "表达沟通提升", "跨界经验丰富"],
specialties: ["世界500强外企背景","学习零压力", "实用可落地", "善于启发"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpJCc6qecx.jpg",
type: "公共课导师",
courses: []
},
"李毅峰": {
name: "李毅峰",
introduction: "拥有超过二十年的柔性电子产业从业经验长期专注于柔性印制电路板FPC的材料研发、精密设计、自动化制造与产业化应用具备从技术开发到市场交付的全流程掌控能力。职业生涯中先后主导完成数十项高密度FPC研发项目涵盖5G通信终端、可穿戴设备、汽车电子、医疗影像、工业控制等多个应用场景。曾带领团队突破多层柔性板对位精度控制与微线宽蚀刻技术瓶颈使线路最小宽距精度控制在±20μm以内成功实现系列产品向超薄、超柔、高可靠性方向升级批量供应多家国际知名电子企业。",
specialties: ["善于启发", "易懂好理解", "讲解有节奏", "超有耐心"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVPz0WRmxCK.jpeg",
type: "公共课导师",
courses: []
},
"周伏波": {
name: "周伏波",
introduction: "具备二十年以上光电子技术领域深耕经验长期致力于光电子芯片材料、光通信器件、半导体照明、激光器件及光电显示技术的研发与产业化工作是推动中国光电子产业链升级的重要技术推动者与行业实践者。其主导完成的多项核心材料与芯片工艺突破成功应用于光模块、光引擎、LED器件、激光显示模组等多个高精度、高性能领域累计获得授权专利60余项多项技术成果实现量产落地。",
specialties: ["光电显示技术探索者", "60+专利技术成果持有者", "国家级重大专项牵头人", "一听就懂", "思路很清晰", "讲解易懂"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7Gi9YxSN.jpeg",
type: "公共课导师",
courses: []
},
"范雪娇": {
name: "范雪娇",
introduction: "拥有十八年的工业自动化与机电设备行业从业经验长期专注于自动化装备研发、配件系统优化、机电一体化集成与工程项目落地具备从核心部件开发到整线系统集成的全链条实践能力。职业生涯中累计参与和主导项目超过百项涵盖非标自动化装置、精密机电配件、控制系统架构设计与生产线智能改造等多个方向广泛服务于汽车制造、3C电子、包装、医疗设备、家电装配等高标准行业。",
specialties: ["工业自动化资深专家", "自动化装备研发实践者", "智能装备融合发展典型代表","讲解有节奏", "学习零压力", "善于总结", "超会举例"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7JsHHDNZ.jpeg",
type: "公共课导师",
courses: []
},
"李奇": {
name: "李奇",
introduction: "毕业于南洋理工学院拥有硕士学位具备扎实的人工智能理论基础与产业实践经验。近年来专注于大模型工程化与AI应用课程体系建设主持完成《AIGC实战从模型调用到产品落地》《AI在视觉设计与内容生成中的应用》等多个应用型教学项目。曾主导开发'AI技能地图导航系统'实现课程内容与岗位技能的精准对齐在2024年全国高校AI课程创新大赛中荣获一等奖。擅长将复杂的算法模型转化为通俗易懂的教学内容致力于打造'人人可用AI'的实训课程体系是众多高校与职业培训机构特聘的AI技术与课程体系顾问。",
specialties: ["人工智能技术顾问", "大模型工程化研究者", "AI应用课程体系建设者", "教学内容转化能力强", "技巧随手教", "幽默又有料", "讲解有节奏"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuVU7LDWK6iG.jpg",
type: "AI课导师",
courses: []
},
"魏立慧": {
name: "魏立慧",
introduction: "企业资深一线HR主讲专注于为求职者提供一对一的个性化指导。通过真实招聘视角深入剖析个人优势与短板、传授面试技巧、规划职业定位与发展路径帮助学生快速提升求职竞争力。求职策略以实用落地为核心注重互动交流与角色定位让学员在轻松氛围中获得直击痛点的求职策略。",
specialties: ["点评直击要害", "擅长挖掘优势", "职业规划达人", "深谙用人逻辑"],
specialties: ["点评直击要害", "擅长挖掘优势", "职业规划达人", "深谙用人逻辑", "一线HR资深讲师"],
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png",
type: "企业资深HR",
courses: []
}
}, transformCalendarCourses(calendarCoursesData)),
}, allCalendarEvents),
// 个人档案详细信息
profile: {
@@ -444,7 +701,7 @@ export const mockData = {
},
// 日历事件数据 - 使用当前月份的日期进行演示
calendarEvents: transformCalendarCourses(calendarCoursesData),
calendarEvents: allCalendarEvents,
// 日历配置
calendarConfig: {
@@ -1442,7 +1699,7 @@ export const mockData = {
date: "2023-09-15",
time: "14:30",
status: "evaluated",
videoUrl: "/live.mp4",
videoUrl: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_ago.mov",
interviewer: "职业导师",
duration: "05:32",
type: "first_experience",
@@ -3675,8 +3932,8 @@ mockData.dashboardStatistics = {
{
rank: 4,
studentId: "2325030602",
studentName: "李",
name: "李",
studentName: "李",
name: "李",
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/avatar/douyin/13c5709a3993fdf353d147209f8145cb.jpg",
score: 95,
credits: 95,
@@ -3776,16 +4033,16 @@ mockData.profileOverview = {
// 学习统计
studyStatistics: {
studyTime: {
personal: 168,
classAverage: 107,
personal: 273,
classAverage: 242,
},
courseCompletion: {
personalProgress: 100,
classAverageProgress: 63,
classAverageProgress: 89,
},
homeworkCompletion: {
personalProgress: 88,
classAverageProgress: 56,
personalProgress: 100,
classAverageProgress: 71,
},
attendance: {
attendanceRate: 98,
@@ -3909,8 +4166,8 @@ mockData.profileOverview = {
{
rank: 4,
studentId: "2325030602",
studentName: "李",
name: "李",
studentName: "李",
name: "李",
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/avatar/douyin/13c5709a3993fdf353d147209f8145cb.jpg",
score: 95,
credits: 95,
@@ -4096,16 +4353,16 @@ mockData.profileOverview = {
// 学习统计
studyStatistics: {
studyTime: {
personal: 168,
classAverage: 107,
personal: 273,
classAverage: 242,
},
courseCompletion: {
personalProgress: 100,
classAverageProgress: 63,
classAverageProgress: 89,
},
homeworkCompletion: {
personalProgress: 88,
classAverageProgress: 56,
personalProgress: 100,
classAverageProgress: 71,
},
attendance: {
attendanceRate: 98,
@@ -4229,8 +4486,8 @@ mockData.profileOverview = {
{
rank: 4,
studentId: "2325030602",
studentName: "李",
name: "李",
studentName: "李",
name: "李",
avatar: "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/avatar/douyin/13c5709a3993fdf353d147209f8145cb.jpg",
score: 95,
credits: 95,
@@ -4300,10 +4557,14 @@ mockData.profileOverview = {
};
// 课程直播间的课程列表数据从CSV生成
mockData.courseLiveList = generateCourseLiveListFromCalendar(transformCalendarCourses(calendarCoursesData));
mockData.courseLiveList = generateCourseLiveListFromCalendar(allCalendarEvents);
// 生成公共课程列表
mockData.publicCourseLiveList = generatePublicCourseLiveList(mockData.courseLiveList);
mockData.publicCourseLiveList = generatePublicCourseLiveList(allCalendarEvents);
// 生成垂直能力课列表传入unitPosters以包含海报数据
mockData.verticalCourseLiveList = generateVerticalCourseLiveList(allCalendarEvents, mockData.unitPosters);
// 在courseLiveList定义后更新dashboardStatistics的课程和任务数据
const dashboardCourseData = generateDashboardCourses(mockData.courseLiveList);
@@ -4433,7 +4694,7 @@ mockData.dashboardStatistics.courses = {
"type": "course"
}
],
allTasks: generateTasksFromCalendarEvents(transformCalendarCourses(calendarCoursesData))
allTasks: generateTasksFromCalendarEvents(allCalendarEvents)
};
})();

File diff suppressed because it is too large Load Diff

4638
src/data/mockData.js.backup2 Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -90,7 +90,8 @@ const CalendarHeader = ({
</button>
</div>
<div className="view-switcher">
{/* 视图切换器 - 暂时隐藏 */}
{/* <div className="view-switcher">
<button
className={`view-button ${currentView === 'month' ? 'active' : ''}`}
onClick={() => handleViewChange('month')}
@@ -103,7 +104,7 @@ const CalendarHeader = ({
>
</button>
</div>
</div> */}
</div>
);
};

View File

@@ -1,5 +1,6 @@
import { useEffect } from "react";
import Portal from "@/components/Portal";
import { IconCalendarClock, IconCheck, IconClockCircle } from "@arco-design/web-react/icon";
const EventDetailModal = ({ isOpen, event, onClose }) => {
// ESC键关闭模态框
@@ -25,11 +26,29 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
}
// 事件类型映射
const eventTypeNames = {
class: "课程",
meeting: "会议",
lab: "实验",
exam: "考试",
const eventTypeMap = {
'compound-skill': '复合技能课',
'vertical-skill': '垂直技能课',
'public-course': '公开课',
'one-on-one': '1v1规划',
'interview': '线下面试模拟',
'class': '课程',
'meeting': '会议',
'lab': '实验',
'exam': '考试',
};
// 事件类型颜色映射
const eventTypeColorMap = {
'compound-skill': { bg: '#667eea', light: '#e0e7ff' },
'vertical-skill': { bg: '#22c55e', light: '#dcfce7' },
'public-course': { bg: '#f59e0b', light: '#fef3c7' },
'one-on-one': { bg: '#ec4899', light: '#fce7f3' },
'interview': { bg: '#3b82f6', light: '#dbeafe' },
'class': { bg: '#667eea', light: '#e0e7ff' },
'meeting': { bg: '#f093fb', light: '#fae8ff' },
'lab': { bg: '#fa709a', light: '#fce7f3' },
'exam': { bg: '#ff6b6b', light: '#fee2e2' },
};
// 处理遮罩层点击
@@ -45,89 +64,129 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
};
// 格式化时间显示
const formatTimeRange = (startTime, endTime) => {
const startDate = new Date(startTime.replace(" ", "T"));
const endDate = new Date(endTime.replace(" ", "T"));
const formatTime = (date) => {
return date.toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
};
const formatDate = (date) => {
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
});
};
return {
date: formatDate(startDate),
timeRange: `${formatTime(startDate)} - ${formatTime(endDate)}`,
};
const formatTime = (timeStr) => {
if (!timeStr) return '';
// 处理多种格式: "2024-09-18 09:00:00" 或 "2024-09-18 09:00" 或 "09:00:00" 或 "09:00"
const parts = timeStr.toString().split(' ');
let time = '';
if (parts.length > 1) {
// 格式如 "2024-09-18 09:00:00" 或 "2024-09-18 09:00"
time = parts[1];
} else {
// 格式如 "09:00:00" 或 "09:00"
time = parts[0];
}
// 确保时间存在且格式正确
if (!time || time === 'undefined') {
return '00:00';
}
// 只显示小时和分钟 (前5个字符)
return time.substring(0, 5);
};
const { date, timeRange } = formatTimeRange(event.startTime, event.endTime);
// 格式化日期
const formatDate = (date) => {
if (!date) return '';
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
return date.toLocaleDateString('zh-CN', options);
};
// 判断是否为多事件模式(点击日期时显示当天所有事件)
const isMultiEventMode = event.events && Array.isArray(event.events);
const events = isMultiEventMode ? event.events : [event];
const displayDate = isMultiEventMode ? formatDate(event.date) : '';
// 获取事件状态 - 所有事项都显示为已完成
const getEventStatus = (eventItem) => {
return { text: '已完成', icon: <IconCheck />, color: '#52c41a' };
};
return (
<Portal className="event-detail-portal">
<div className="event-detail-overlay" onClick={handleOverlayClick}>
<div className="event-detail-modal">
<div className="event-detail-modal-new">
{/* 模态框头部 */}
<div className="event-detail-header">
<h3 className="event-detail-title">事件详情</h3>
<div className="event-detail-header-new">
<h3 className="event-detail-title-new">
{isMultiEventMode ? '日程详情' : '事件详情'}
</h3>
<button
className="event-detail-close"
className="event-detail-close-new"
onClick={handleCloseClick}
type="button"
aria-label="关闭"
>
×
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
</button>
</div>
{/* 日期显示(多事件模式) */}
{isMultiEventMode && (
<div className="event-detail-date-header">
<IconCalendarClock style={{ fontSize: 20, color: '#3b82f6' }} />
<span>{displayDate}</span>
</div>
)}
{/* 模态框内容 */}
<div className="event-detail-content">
{/* 事件标题 */}
<div className="event-detail-field">
<div className="event-detail-label">事件标题</div>
<div className="event-detail-value">{event.title}</div>
</div>
{/* 事件类型 */}
<div className="event-detail-field">
<div className="event-detail-label">事件类型</div>
<div className="event-detail-value">
<span className={`event-type-badge event-type-${event.type}`}>
{eventTypeNames[event.type] || event.type}
</span>
<div className="event-detail-content-new">
{events.length === 0 ? (
<div className="event-empty-state">
<IconCalendarClock style={{ fontSize: 48, color: '#c3c5c9', marginBottom: 16 }} />
<p style={{ fontSize: 16, color: '#86909c', margin: 0 }}>当日无事项</p>
</div>
) : (
<div className="event-list-container">
{events.map((eventItem, index) => {
const status = getEventStatus(eventItem);
const typeColor = eventTypeColorMap[eventItem.type] || { bg: '#667eea', light: '#e0e7ff' };
return (
<div key={eventItem.id || index} className="event-card-new">
<div className="event-card-header">
<div className="event-type-indicator" style={{ backgroundColor: typeColor.bg }}></div>
<div className="event-card-title">
<h4>{eventItem.title}</h4>
<span className="event-type-tag" style={{
backgroundColor: typeColor.light,
color: typeColor.bg
}}>
{eventTypeMap[eventItem.type] || eventItem.type}
</span>
</div>
</div>
<div className="event-card-body">
<div className="event-info-row">
<IconClockCircle style={{ fontSize: 16, color: '#8c8c8c' }} />
<span className="event-time">
{formatTime(eventItem.startTime)} - {formatTime(eventItem.endTime)}
</span>
</div>
<div className="event-info-row">
<div className="event-status" style={{ color: status.color }}>
{status.icon}
<span>{status.text}</span>
</div>
</div>
{eventItem.description && (
<div className="event-description">
{eventItem.description}
</div>
)}
</div>
</div>
);
})}
</div>
{/* 日期 */}
<div className="event-detail-field">
<div className="event-detail-label">日期</div>
<div className="event-detail-value">{date}</div>
</div>
{/* 时间 */}
<div className="event-detail-field">
<div className="event-detail-label">时间</div>
<div className="event-detail-value">
<div className="event-time-range">{timeRange}</div>
</div>
</div>
{/* 详细描述 */}
{event.description && (
<div className="event-detail-field">
<div className="event-detail-label">详细描述</div>
<div className="event-detail-value">{event.description}</div>
</div>
)}
</div>
</div>
@@ -136,4 +195,4 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
);
};
export default EventDetailModal;
export default EventDetailModal;

View File

@@ -26,10 +26,11 @@ const MonthView = ({
});
};
const handleDateClick = (day) => {
const handleDateClick = (day, dayEvents) => {
// 所有日期都可以点击
if (onDateClick) {
const clickedDate = new Date(day.year, day.month, day.date);
onDateClick(clickedDate);
onDateClick(clickedDate, dayEvents || []);
}
};
@@ -106,7 +107,7 @@ const MonthView = ({
className={`day-cell ${!isCurrentMonth ? "other-month" : ""} ${
isToday ? "today" : ""
} ${isSelectedDate ? "selected" : ""}`}
onClick={() => handleDateClick(day)}
onClick={() => handleDateClick(day, dayEvents)}
>
<div className="day-number">{day.date}</div>

View File

@@ -153,11 +153,14 @@
flex-direction: column;
cursor: pointer;
transition: all 0.3s ease;
min-height: 90px;
min-height: 100px;
max-height: 100px;
height: 100px;
position: relative;
border-radius: 10px;
border: 1px solid #e8e8f0;
margin: 3px;
overflow: hidden;
}
.day-cell:hover {
@@ -185,6 +188,8 @@
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.15);
}
/* 删除no-events样式限制允许所有日期都可点击 */
.day-number {
font-size: 14px;
font-weight: 500;
@@ -216,16 +221,18 @@
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-size: 11px;
padding: 4px 8px;
border-radius: 6px;
padding: 3px 6px;
border-radius: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s ease;
margin: 2px 0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin: 1px 0;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font-weight: 500;
max-width: 100%;
display: block;
}
.event-item:hover {
@@ -233,6 +240,32 @@
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
}
/* 复合技能课 */
.event-item.compound-skill {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* 垂直技能课 */
.event-item.vertical-skill {
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
}
/* 公开课 */
.event-item.public-course {
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
}
/* 1v1规划 */
.event-item.one-on-one {
background: linear-gradient(135deg, #ec4899 0%, #f472b6 100%);
}
/* 线下面试模拟 */
.event-item.interview {
background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
}
/* 默认类型保留 */
.event-item.class {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
@@ -499,7 +532,7 @@
}
/* 事件详情模态框样式 - 遵循跨国企业级后台系统设计哲学v2.0 */
/* 事件详情模态框样式 - 新设计 */
/* 模态框遮罩层 */
.event-detail-overlay {
@@ -508,12 +541,13 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.45);
z-index: var(--z-modal);
display: flex;
align-items: center;
justify-content: center;
animation: overlayFadeIn 200ms ease-out;
backdrop-filter: blur(4px);
}
@keyframes overlayFadeIn {
@@ -525,22 +559,23 @@
}
}
/* 模态框主体 - 零线原则,依赖留白分隔 */
.event-detail-modal {
/* 模态框主体 */
.event-detail-modal-new {
background: #ffffff;
border-radius: 8px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 4px 6px rgba(0, 0, 0, 0.1);
width: 480px;
border-radius: 12px;
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.08);
width: 520px;
max-width: 90vw;
max-height: 80vh;
overflow: auto;
animation: modalSlideIn 250ms ease-out;
max-height: 75vh;
display: flex;
flex-direction: column;
animation: modalSlideIn 250ms cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
transform: scale(0.96) translateY(-20px);
}
to {
opacity: 1;
@@ -548,122 +583,201 @@
}
}
/* 模态框头部 - 呼吸感间距 */
.event-detail-header {
padding: 24px 24px 16px 24px;
border-bottom: 1px solid #f3f4f6;
/* 模态框头部 */
.event-detail-header-new {
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
/* 标题 - 意图驱动的信息层级 L1 */
.event-detail-title {
/* 标题样式 */
.event-detail-title-new {
font-size: 18px;
font-weight: 600;
color: #111827;
color: #1f2937;
margin: 0;
letter-spacing: -0.01em;
}
/* 关闭按钮 - 上下文感知交互 */
.event-detail-close {
width: 32px;
height: 32px;
/* 关闭按钮 */
.event-detail-close-new {
width: 36px;
height: 36px;
border: none;
background: #f9fafb;
border-radius: 6px;
background: transparent;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #6b7280;
font-size: 18px;
transition: all 150ms ease;
}
.event-detail-close:hover {
.event-detail-close-new:hover {
background: #f3f4f6;
color: #374151;
}
.event-detail-close:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
.event-detail-close-new:active {
background: #e5e7eb;
}
/* 模态框内容区域 - 慷慨的留白 */
.event-detail-content {
padding: 24px;
}
/* 字段容器 - 基于8px栅格的间距 */
.event-detail-field {
margin-bottom: 24px;
}
.event-detail-field:last-child {
margin-bottom: 0;
}
/* 字段标签 - 信息层级 L3 */
.event-detail-label {
font-size: 12px;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
/* 字段值 - 信息层级 L2 */
.event-detail-value {
/* 日期头部(多事件模式) */
.event-detail-date-header {
padding: 16px 24px;
background: linear-gradient(to right, #f0f9ff, #e0f2fe);
border-bottom: 1px solid #e0e7ff;
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
color: #111827;
line-height: 1.5;
}
/* 事件类型徽章 - 语义化状态色板 */
.event-type-badge {
display: inline-block;
padding: 6px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* 克制的色彩语言 - 柔和且低饱和度 */
.event-type-class {
background: #dbeafe;
color: #1e40af;
}
.event-type-meeting {
background: #d1fae5;
color: #065f46;
}
.event-type-lab {
background: #fef3c7;
color: #92400e;
}
.event-type-exam {
background: #fee2e2;
color: #991b1b;
}
/* 时间范围显示 - 等宽字体增强可读性 */
.event-time-range {
font-family: "SF Mono", "Monaco", "Cascadia Code", "Consolas", monospace;
font-size: 14px;
color: #374151;
background: #f9fafb;
padding: 12px 16px;
border-radius: 6px;
border-left: 3px solid #3b82f6;
font-weight: 500;
color: #1e40af;
flex-shrink: 0;
}
/* 新内容区域 */
.event-detail-content-new {
flex: 1;
overflow-y: auto;
padding: 20px 24px;
}
/* 事件列表容器 */
.event-list-container {
display: flex;
flex-direction: column;
gap: 16px;
}
/* 新事件卡片 */
.event-card-new {
background: #fafbfc;
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 16px;
transition: all 150ms ease;
}
.event-card-new:hover {
background: #f9fafb;
border-color: #d1d5db;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
/* 事件卡片头部 */
.event-card-header {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
/* 事件类型指示器 */
.event-type-indicator {
width: 4px;
border-radius: 2px;
flex-shrink: 0;
}
/* 事件卡片标题区 */
.event-card-title {
flex: 1;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.event-card-title h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #111827;
line-height: 1.4;
}
/* 事件类型标签 */
.event-type-tag {
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
/* 事件卡片内容 */
.event-card-body {
padding-left: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
/* 事件信息行 */
.event-info-row {
display: flex;
align-items: center;
gap: 8px;
}
/* 事件时间 */
.event-time {
font-size: 14px;
color: #6b7280;
font-family: "SF Mono", "Monaco", "Cascadia Code", monospace;
}
/* 事件状态 */
.event-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
}
/* 事件描述 */
.event-description {
font-size: 14px;
color: #4b5563;
line-height: 1.5;
padding: 12px;
background: white;
border-radius: 6px;
border-left: 3px solid #e5e7eb;
margin-top: 4px;
}
/* 更多事件指示器 */
.event-more {
font-size: 11px;
color: #6b7280;
background: #f3f4f6;
padding: 2px 6px;
border-radius: 4px;
text-align: center;
cursor: pointer;
transition: all 150ms ease;
}
.event-more:hover {
background: #e5e7eb;
color: #374151;
}
/* 无事项状态 */
.event-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
/* 响应式设计 */
@@ -764,19 +878,47 @@
}
}
/* 响应式设计 */
/* 响应式设计 - 移动端适配 */
@media (max-width: 640px) {
.event-detail-modal {
.event-detail-modal-new {
width: 95vw;
max-height: 85vh;
margin: 16px;
}
.event-detail-header,
.event-detail-content {
.event-detail-header-new,
.event-detail-content-new {
padding: 16px;
}
.event-detail-field {
margin-bottom: 16px;
.event-detail-date-header {
padding: 12px 16px;
font-size: 14px;
}
.event-card-new {
padding: 12px;
}
.event-card-title h4 {
font-size: 15px;
}
.event-type-tag {
font-size: 11px;
padding: 3px 8px;
}
.event-time {
font-size: 13px;
}
.event-status {
font-size: 12px;
}
.event-description {
font-size: 13px;
padding: 10px;
}
}

View File

@@ -23,14 +23,14 @@ const CalendarPage = () => {
setCurrentDate(newDate);
};
const handleDateClick = (date) => {
const handleDateClick = (date, events) => {
setSelectedDate(date);
// 如果在月视图中点击日期,可以切换到周视图
if (currentView === "month") {
setCurrentDate(date);
// 可选:自动切换到周视图
// setCurrentView('week');
// 如果有事件,显示事件详情弹窗
if (events && events.length > 0) {
// 设置选中的事件为当天的所有事件
setSelectedEvent({ date, events });
setShowEventDetail(true);
}
};

View File

@@ -29,15 +29,26 @@ export default ({ visible, onClose, data }) => {
if (studentInfo) {
// 处理教育经历
resumeData.educational_experience = studentInfo.educational_experience || ["相关专业大学 本科"];
if (studentInfo.education) {
resumeData.educational_experience = [
`${studentInfo.education.university || '苏州信息职业技术学院'} ${studentInfo.education.period || '2020.9 - 2023.6'}`
];
} else {
resumeData.educational_experience = studentInfo.educational_experience || ["相关专业大学 本科"];
}
// 处理项目经历
if (studentInfo.project_experience) {
// 处理项目经历 - 支持新格式
if (studentInfo.projectExperience) {
// 新格式projectExperience是字符串
resumeData.project_experience = [{
name: "项目经历",
description: studentInfo.projectExperience
}];
} else if (studentInfo.project_experience) {
// 旧格式兼容
if (Array.isArray(studentInfo.project_experience)) {
// 如果已经是数组格式
resumeData.project_experience = studentInfo.project_experience;
} else if (typeof studentInfo.project_experience === 'object') {
// 如果是对象格式,转换为数组
const proj = studentInfo.project_experience;
resumeData.project_experience = [
{
@@ -48,10 +59,18 @@ export default ({ visible, onClose, data }) => {
}
}
// 处理核心技能和复合技能
resumeData.core_skills = studentInfo.core_skills || [];
resumeData.compound_skills = studentInfo.compound_skills || [];
resumeData.personal_summary = studentInfo.personal_summary || "具有扎实的专业基础和实践经验";
// 处理专业技能 - 支持新格式
if (studentInfo.skills) {
// 新格式skills是字符串
resumeData.skills_text = studentInfo.skills;
} else {
// 旧格式兼容
resumeData.core_skills = studentInfo.core_skills || [];
resumeData.compound_skills = studentInfo.compound_skills || [];
}
// 处理个人总结 - 支持新格式
resumeData.personal_summary = studentInfo.personalSummary || studentInfo.personal_summary || "具有扎实的专业基础和实践经验";
}
return (
@@ -107,32 +126,38 @@ export default ({ visible, onClose, data }) => {
{/* 专业技能 */}
<li className="resume-info-moda-item">
<p className="resume-info-moda-item-title">专业技能</p>
<ul className="professional-skills-list">
{resumeData.core_skills && resumeData.core_skills.length > 0 && (
<li className="professional-skills-list-item">
<p className="skill-name">核心能力</p>
<div className="core-capabilities-list">
{resumeData.core_skills.map((skill, index) => (
<p key={index} className="core-capabilities-list-item">
{skill}
</p>
))}
</div>
</li>
)}
{resumeData.compound_skills && resumeData.compound_skills.length > 0 && (
<li className="professional-skills-list-item">
<p className="skill-name">复合能力</p>
<div className="core-capabilities-list">
{resumeData.compound_skills.map((skill, index) => (
<p key={index} className="core-capabilities-list-item">
{skill}
</p>
))}
</div>
</li>
)}
</ul>
{resumeData.skills_text ? (
<div className="professional-skills-content" style={{ whiteSpace: 'pre-wrap', lineHeight: '1.8', padding: '0 20px' }}>
{resumeData.skills_text}
</div>
) : (
<ul className="professional-skills-list">
{resumeData.core_skills && resumeData.core_skills.length > 0 && (
<li className="professional-skills-list-item">
<p className="skill-name">核心能力</p>
<div className="core-capabilities-list">
{resumeData.core_skills.map((skill, index) => (
<p key={index} className="core-capabilities-list-item">
{skill}
</p>
))}
</div>
</li>
)}
{resumeData.compound_skills && resumeData.compound_skills.length > 0 && (
<li className="professional-skills-list-item">
<p className="skill-name">复合能力</p>
<div className="core-capabilities-list">
{resumeData.compound_skills.map((skill, index) => (
<p key={index} className="core-capabilities-list-item">
{skill}
</p>
))}
</div>
</li>
)}
</ul>
)}
</li>
{/* 个人总结 */}
{resumeData.personal_summary && resumeData.personal_summary.trim() !== '' && (

View File

@@ -24,9 +24,9 @@
.interview-rating-header-title {
cursor: default;
font-size: 12px;
font-size: 22px;
font-weight: 600;
color: #000;
color: #1d2129;
}
}
@@ -64,11 +64,6 @@
}
/* 面试评分图表区域 */
.interview-rating-header {
justify-content: flex-start;
}
.interview-evaluation-charts-wrapper {
width: 100%;
box-sizing: border-box;
@@ -77,93 +72,164 @@
border-radius: 8px;
flex-shrink: 0;
.charts-content {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.interview-rating-header {
justify-content: flex-start;
}
.charts-content-top {
width: 100%;
height: 255px;
box-sizing: border-box;
border-bottom: 1px dashed #e5e6eb;
.charts-content {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: column;
.charts-content-top {
width: 100%;
height: 262px;
box-sizing: border-box;
display: flex;
justify-content: space-around;
align-items: center;
background-color: #f4f7f9;
border-radius: 12px;
margin-bottom: 10px;
.score-chart {
width: 402px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
&::after {
content: "";
width: 340px;
height: 170px;
position: absolute;
bottom: 55px;
background-image: url("@/assets/images/InterviewSimulationPage/chart_bg.png");
background-size: 100% 100%;
}
}
.score-info {
width: 402px;
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
.score-chart {
width: 220px;
height: 220px;
}
.score-info {
width: 300px;
height: 210px;
.score-info-item {
width: 354px;
height: 99px;
border-radius: 12px;
display: flex;
justify-content: space-around;
align-items: center;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
box-sizing: border-box;
padding: 20px 10px 20px 20px;
background-color: #fff;
.score-info-item {
width: 300px;
height: 80px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
.score-info-item-title {
width: 100%;
height: 24px;
line-height: 24px;
color: #1d2129;
font-size: 16px;
font-weight: 400;
position: relative;
box-sizing: border-box;
padding: 16px;
padding-left: 24px;
.score-info-item-title {
color: #333333;
font-size: 16px;
font-weight: 400;
&::before {
content: "";
width: 24px;
height: 24px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png");
background-size: 100% 100%;
}
.score-info-item-value {
font-size: 34px;
font-weight: 800;
position: absolute;
right: 0px;
font-size: 24px;
font-weight: 700;
}
}
.score-info-item.item1 {
border: 1px solid #7fc6ff;
background-image: linear-gradient(to right, #f1f9ff, #ffffff);
.score-info-item-value {
color: #2c7aff;
.score-info-line {
margin-top: 20px;
width: 299px;
height: 12px;
background-color: #f4f7f9;
border-radius: 12px;
position: relative;
> i {
position: absolute;
left: 0;
top: 0;
height: 12px;
border-radius: 12px;
}
}
.score-info-item.item2 {
border: 1px solid #00b42a80;
background-image: linear-gradient(
to right,
rgba(255, 255, 255, 1),
rgba(35, 195, 67, 0.1)
);
.score-info-item-value {
color: #009a29;
.score-info-line-total-value {
color: #86909c;
font-size: 12px;
font-weight: 500;
position: absolute;
right: -25px;
top: 50%;
transform: translateY(-50%);
}
}
}
}
.charts-content-bottom {
width: 100%;
height: 330px;
display: flex;
justify-content: space-between;
align-items: center;
.radar-chart {
width: 50%;
height: 100%;
.score-info-item.item1 {
.score-info-item-value {
color: #2c7aff;
}
.score-info-line {
> i {
background-image: linear-gradient(to right, #2d7fff, #38aaff);
}
}
}
.score-info-item.item2 {
.score-info-item-value {
color: #25c343;
}
.score-info-line {
> i {
background-image: linear-gradient(to right, #2dc546, #8de46c);
}
}
}
}
}
.charts-content-bottom {
width: 100%;
height: 360px;
display: flex;
justify-content: space-between;
align-items: center;
.radar-chart {
width: 394px;
height: 100%;
background-color: #f4f7f9;
border-radius: 12px;
}
}
}
}
.interview-evaluation-text-wrapper {
width: 100%;
@@ -185,10 +251,44 @@
box-sizing: border-box;
padding: 16px;
color: #1d2129;
font-size: 16px;
font-size: 14px;
font-weight: 400;
line-height: 1.6;
white-space: pre-wrap;
h1 {
font-size: 18px;
font-weight: 600;
margin: 16px 0 12px 0;
color: #1d2129;
}
h2 {
font-size: 16px;
font-weight: 600;
margin: 14px 0 10px 0;
color: #1d2129;
}
p {
margin: 8px 0;
line-height: 1.8;
text-indent: 2em;
}
ol, ul {
margin: 8px 0;
padding-left: 24px;
li {
margin: 6px 0;
line-height: 1.8;
text-indent: 2em;
}
}
strong {
font-weight: 600;
}
}
}
}

View File

@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import ScoreChart from "../ScoreChart";
import RadarChart from "../RadarChart";
import "./index.css";
@@ -8,7 +9,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
const getVideoUrl = () => {
switch(selectedItem) {
case "求职面试初体验":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJSOKoqAm.mov";
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_ago.mov";
case "未来的自己":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJT02CMM5.mp4";
case "第一次线下面试模拟":
@@ -33,61 +34,143 @@ export default ({ selectedItem = "求职面试初体验" }) => {
const getEvaluationData = () => {
if (selectedItem === "未来的自己") {
return {
totalScore: 92,
professionalScore: 39.5,
performanceScore: 55.0,
radarData: [10, 9, 10, 9],
title: "优秀表现评价",
content: `在专业能力方面,候选人对相关专业知识掌握扎实,能够深入且准确地回答与项目经验相关的复杂问题。在描述具体项目时,不仅能清晰说明自己的职责和成果,还能主动分析项目中的技术难点和解决方案,体现出优秀的实践操作能力和技术思维。对行业前沿动态有深入了解,能够结合实际工作提出创新性见解。
totalScore: 97, // 根据JSON计算的真实总分
professionalScore: 57, // (9+10+10+9+10+9)/6*10*0.6 = 57
performanceScore: 40, // (10+10+10+10)/4*10*0.4 = 40
radarData: [9, 10, 10, 9, 10, 9], // 六项专业能力指标来自JSON
radarData2: [10, 10, 10, 10], // 四项现场表现指标来自JSON
title: "面试评价",
content: `# 专业能力
沟通表达上,候选人语言表达流畅自然,逻辑思维清晰有条理,能够积极主动地与面试官互动。在面对开放性问题时展现出良好的思维发散能力,回答层次分明,重点突出,表现出强烈的学习意愿和职业发展规划意识。整体表现超出预期,具备优秀的职场素养。`
1. 专业知识学得特别扎实,讲核心概念的时候又准又清楚,还能随手举例子把道理说透,不光能把知识点讲明白,还能用实例帮人理解,一看就是专业底子特别厚,也很会用学到的知识;
2. 对行业里的产业链、还有未来的发展趋势摸得特别透,不光能说清产业链各个环节是怎么连起来的,还能具体讲明白这些趋势会影响到岗位工作、业务方向,聊到行业相关的话题,总能说出有想法的观点,跟行业里的人交流完全没障碍;
3. 对企业从头到尾的工作流程门儿清,知道业务怎么推进、关键环节在哪,还有跟其他部门怎么配合,既能说清自己进了企业要干些啥,也能找准自己在流程里的位置,跟不同部门合作的关键点也拎得很清,跟实际工作场景特别适配;
4. 碰到具体任务或问题,拆解得特别有条理,分析思路也很系统,能从实际需要出发理出解决办法,想的方案既靠谱能落地,又有新点子,还能用具体数据说明能达到啥效果,不管是想办法还是实际操作,都做得挺到位;
5. 对目标岗位的工作职责、要干的活、需要啥能力都摸得很清楚,聊职业规划和自己跟岗位合不合适的时候,能精准说清自己能发挥啥作用,还能把自己的优势、经历跟岗位需求、业务目标绑在一起说,一看就跟岗位特别对路;
6. 做过的项目又多又完整,说项目的时候能讲清背景、自己具体干了啥、负责哪些环节,还能好好总结从里面学到了啥、能力咋提升的,通过这些项目,能实实在在看出专业能力和动手做事的本事;
# 现场表现力
1. 说话表达能力特别好,嘴皮子利索还挺有劲儿,说事儿的时候结构很清楚,每个信息都能说到点子上,既能让人准确 get 到核心观点,又能高效把关键信息传出去,沟通起来又快又准;
2. 心态特别稳,不管是被提问、有工作压力,还是碰到突发情况,一直都很自信、不慌不乱,思路也很清晰,既能把工作或回答做好,还能巧妙化解压力,扛事儿能力和临场应变都挺强;
3. 跟人交流、做展示的时候,姿态特别专业,眼神交流自然又真诚,手势动作跟说话配合得刚好,既显得得体专业,又能通过动作让自己的观点更有说服力,让人觉得特别靠谱;
4. 对时间和流程把控得特别好,不管是做事、聊天还是做展示,每个环节的时间都掐得准,环节之间过渡也很顺,还会留时间做总结,能保证整个过程顺畅推进,干活、沟通的效率都很高。
# 综合评价
总的来说,这学生在专业基础、行业认知、懂企业流程、解决问题、适配岗位、项目经验、说话表达、心态调整、职业仪态还有时间管理这些关键方面,都表现得特别优秀,综合能力和职业素养都很突出。这些优点不光能让专业交流更顺畅、解决问题更合理,还能看出他特别有职业潜力,跟岗位也特别匹配,是个很有发展前途的好苗子。`
};
} else if (selectedItem === "第一次线下面试模拟") {
return {
totalScore: 72,
professionalScore: 28.0,
performanceScore: 44.0,
radarData: [7, 6, 7, 7],
radarData2: [6, 6, 5, 6],
title: "初次模拟评价",
content: `专业能力方面,候选人对基础知识有一定了解,但在深入问题上表现不够自信。回答问题时思路基本清晰,但缺乏具体案例支撑。对行业动态的了解较为表面,需要加强专业知识的深度学习。在描述项目经验时,表达略显生疏,未能充分展现个人价值。
totalScore: 42, // 根据JSON计算的真实总分
professionalScore: 28, // (4+6+5+4+6+3)/6*10*0.6 = 28
performanceScore: 14, // (2+4+5+3)/4*10*0.4 = 14
radarData: [4, 6, 5, 4, 6, 3], // 六项专业能力指标来自JSON
radarData2: [2, 4, 5, 3], // 四项现场表现指标来自JSON
title: "面试评价",
content: `# 专业能力
沟通表达上,候选人表现出一定的紧张情绪,语速较快,有时会出现词不达意的情况。逻辑性有待加强,回答问题时容易偏离主题。肢体语言不够自然,眼神交流不足。建议多进行面试练习,提升表达的流畅度和自信心。整体来看,还有较大的提升空间。`
1. 对知识点的概念总弄混,回答问题也只停在表面,没法往深了说 —— 比如问个核心概念,要么跟别的概念搅在一起,要么就说几句皮毛,根本挖不出背后的门道,能看出来对知识的理解还差得远;
2. 知道的行业知识都是零零散散的,没形成系统,尤其说不明白行业趋势跟岗位、业务的关系 —— 比如问某个趋势会影响工作内容不,他就答不上来,对行业的认知特别散,没串起来;
3. 对工作流程的概念特别模糊,连自己该干啥都搞不清 —— 比如问企业里某个业务流程怎么走,他说不明白,再问他在里面要承担啥角色,更是没头绪,完全没找准自己的位置;
4. 分析问题的时候特别局限,想的方案也很片面,连怎么落地的步骤都没有 —— 比如让他给个解决办法,只能说个大概方向,至于需要哪些资源、分几步做、怎么推进,根本没考虑,这样的方案根本没法用;
5. 对目标岗位的认知特别模糊,连岗位的核心工作是啥、该干到啥程度、哪些活不归自己管,都弄不明白 —— 问他这个岗位主要负责啥,他说的颠三倒四,工作边界更是完全没概念,明显没搞懂岗位到底是干啥的;
6. 做过的项目特别少,就算有一两个,也说不明白情况 —— 要么讲不清项目是做什么的,要么说不出自己在里面具体干了啥,连自己到底是啥角色都模模糊糊,根本没法用项目证明自己有能力;
# 现场表现力
1. 说话特别散乱,抓不住重点,逻辑还老跳 —— 比如跟他聊个事儿,他东说一句西说一句,关键信息没几句,还经常突然从一个话题跳到另一个,听的人根本跟不上,半天搞不清他想表达啥;
2. 情绪波动特别大,一会儿好一会儿坏,特别影响沟通 —— 可能刚开始聊得好好的,稍微有点问题就慌了,或者没耐心了,跟他交流的时候,很容易因为他的情绪受影响,沟通效果特别差;
3. 跟人说话或者坐着的时候,小动作特别多,坐姿也不稳 —— 一会儿摸笔、一会儿挠头,身子还总晃来晃去,这些动作特别容易分散别人的注意力,让人没法专心听他说话,印象分也会打折扣;
4. 不管是做事、做展示还是跟人聊天,时间把控得特别差 —— 要么说起来没个完,严重超时;要么没说几句就结束了,整个过程一点条理都没有,结构乱得很,完全没规划。
# 综合评价
总的来说,这学生在知识理解、行业认知、流程和岗位把握、方案设计、项目经验、表达逻辑,还有情绪管理、行为仪态、时间把控这些方面,都有挺明显的问题。这些问题不光让日常沟通和解决问题受影响,也能看出来他现在还不太能适应实际工作的要求,之后得重点补补知识的深度、多了解行业和岗位,再好好练练说话的逻辑和心态,慢慢把综合能力提上来才行。`
};
} else if (selectedItem === "第二次线下面试模拟") {
return {
totalScore: 81,
professionalScore: 32.5,
performanceScore: 48.5,
radarData: [8, 7, 8, 8],
radarData2: [7, 7, 6, 7],
title: "进步明显评价",
content: `专业能力方面,候选人相比第一次有明显进步,对专业知识的理解更加深入。能够结合实际案例阐述观点,展现出一定的实践经验。对于技术问题的回答更有条理,能够抓住问题的关键点。行业认知有所提升,能够说出一些前沿趋势。
totalScore: 67, // 根据JSON计算的真实总分
professionalScore: 41, // (7+7+6+6+7+8)/6*10*0.6 = 41
performanceScore: 26, // (8+7+6+5)/4*10*0.4 = 26
radarData: [7, 7, 6, 6, 7, 8], // 六项专业能力指标来自JSON
radarData2: [8, 7, 6, 5], // 四项现场表现指标来自JSON
title: "面试评价",
content: `# 专业能力
沟通表达上,紧张情绪明显缓解,语速适中,表达更加清晰。逻辑结构有所改善,能够按照"总-分-总"的结构组织答案。肢体语言更加自然,与面试官的互动增多。自信心有所提升,敢于表达自己的观点。但在一些压力问题上仍需加强应对能力。`
1. 关键知识掌握得挺全面的,大部分内容都能抓准,就是偶尔在小细节上有点马虎,比如个别知识点的细微区别会记混,但整体来看,知识的准确性还是不错的,核心内容都能掌握到位;
2. 对市场上的主要动态有了解,比如行业里近期的热门方向、大家关注的重点,都能说出个大概,而且能简单讲讲这些动态对业务开展有啥意义,虽然说得不算深入,但至少能把 "动态和业务" 的关联点到,有基本的认知;
3. 明白工作的主要流程是啥,比如一项业务从开始到结束要经过哪些关键步骤,都能说清楚,也知道自己在流程里负责哪个环节、要干些啥,但在细节上就有点粗糙了,比如环节之间怎么衔接、遇到小问题该怎么处理,就说不太细;
4. 面对问题或者任务时,能有个初步的思路雏形,比如想解决某个问题,能先提出一两个大概的方向,但思路不够系统,没有把 "为什么这么做、步骤是啥、需要啥支持" 串成完整的逻辑,论证的时候也缺乏足够的理由或者例子支撑,说服力还差了点;
5. 知道目标岗位的主要任务是啥,能说出大概的工作范围,比如日常要处理哪些事、负责哪些板块,但没法深入剖析岗位 —— 像岗位的核心价值是啥、不同任务之间的优先级怎么排、需要具备哪些隐藏技能,这些深入的内容就说不出来了;
6. 也参与过一定数量的项目,不是没经验的,聊项目的时候能大体描述自己在里面做了啥任务,比如负责过数据整理、协助过方案讨论,但说到项目成果就有点笼统了,比如只说 "完成了任务",没说清 "任务带来了啥效果、自己的贡献让项目有啥提升",成果没法具体体现;
# 现场表现力
1. 说话的逻辑基本能让人听明白,不会让人抓不着重点,但偶尔会有重复的情况,比如同一句话换个说法又说一遍,或者讲到一半会停顿一下,想不起来下一句该说啥,不过整体的表达节奏还能跟上,不影响理解;
2. 面对交流或者任务时,基本能保持镇定,不会慌慌张张的,就算偶尔有点紧张,比如说话声音稍微变小、语速变快,也能自己调整过来,很快恢复平稳的状态,不会让紧张影响整体表现;
3. 平时的体态看起来挺得体的,坐姿、站姿都比较规范,跟人交流时也不会有太随意的动作,就是偶尔会有点僵硬,比如坐着的时候身体绷得太紧、手势不太自然,但这些小问题不影响整体的印象,还是显得比较专业的;
4. 不管是做事、做展示还是跟人沟通,基本能在规定时间内完成,不会出现严重超时或者没做完的情况,就是偶尔会有点小偏差 —— 要么比规定时间多花个几分钟,要么为了赶时间稍微省略一点内容,但整体的进度和完整性还是有保障的。
# 综合评价
总的来说,这学生在知识掌握、市场认知、流程理解、思路形成、岗位认知、项目经验、表达逻辑、心态调整、体态和时间把控上,都有基础的能力,没有特别明显的短板,但在 "细节、深度、系统性" 上还有提升空间。之后可以重点补补细节知识、多深入思考岗位和项目的核心价值、把思路梳理得更系统,这样综合能力就能再上一个台阶,也会更适配实际工作的要求。`
};
} else if (selectedItem === "第三次线下面试模拟") {
return {
totalScore: 89,
professionalScore: 36.5,
performanceScore: 52.5,
radarData: [9, 8, 9, 9],
radarData2: [8, 8, 7, 8],
title: "接近优秀评价",
content: `专业能力方面,候选人展现出扎实的专业功底,能够深入浅出地解释复杂概念。项目经验描述详实,能够清楚说明技术难点和解决方案。对行业发展有独到见解,能够将理论与实践很好地结合。专业深度和广度都达到了较高水平。
totalScore: 91, // 根据JSON计算的真实总分
professionalScore: 54, // (8+10+9+8+10+9)/6*10*0.6 = 54
performanceScore: 37, // (10+8+10+10)/4*10*0.4 = 38 (约37)
radarData: [8, 10, 9, 8, 10, 9], // 六项专业能力指标来自JSON
radarData2: [10, 8, 10, 10], // 四项现场表现指标来自JSON
title: "面试评价",
content: `# 专业能力
沟通表达上,表现自然从容,完全没有紧张感。语言组织能力强,逻辑清晰,重点突出。能够根据面试官的反应及时调整表达方式,展现出良好的沟通技巧。肢体语言得体,眼神交流充分,整体气场稳定。已经基本具备了通过正式面试的能力,继续保持即可。`
1. 关键知识掌握得特别全面,不管是核心考点还是重要内容,都能稳稳抓住,就是偶尔在小细节上会有点疏漏,比如个别细碎知识点记不太准,但整体来看,知识的准确性特别好,不会出大差错;
2. 对行业里的产业链和发展趋势摸得很透,不光能说清产业链各个环节怎么联动,还能具体讲明白这些趋势会给岗位工作、业务开展带来啥影响,比如哪种趋势会让岗位多些新任务,哪种趋势能帮业务找新方向,分析得特别实在;
3. 能把企业从头到尾的工作流程说得明明白白,哪个环节该干啥、流程里的关键节点是啥,都门儿清,而且能找准自己在流程里的角色,就连跟其他部门怎么配合、配合的关键点是啥,也能说得很到位,完全不像没接触过实际工作的;
4. 就算单说具体的主要流程,也能讲清楚自己负责的环节要做啥,比如流程里的资料整理、对接沟通这些活儿,都能说透,就是在细节上稍微有点粗糙,比如环节之间怎么交接更顺畅、遇到小问题怎么快速处理,说得没那么细;
5. 对目标岗位的职责了解得特别全面,岗位要干的活儿、承担的责任都能说全,还能精准找到自己在岗位上的价值 —— 比如自己能帮岗位解决啥问题、能给团队带来啥助力,更厉害的是,能结合实际例子说明这些职责和价值怎么跟业务目标挂钩,比如做好某项工作能帮业务完成多少指标,逻辑特别顺;
6. 做过的项目又多又完整,不管是校园里的实践项目,还是外面的实习项目,都有涉及,聊项目的时候,能清清楚楚说清自己在里面扮演啥角色、过程中具体做了哪些贡献,就连最后项目拿到啥成果、带来啥效果,也能说得明明白白,不会含糊其辞;
# 现场表现力
1. 说话特别流畅,而且很有劲儿,不管是回答问题还是分享想法,表达的结构都很严谨,不会东拉西扯,每个信息点都能精准说到点子上,让人一听就懂,还能快速 get 到核心内容,沟通效率特别高;
2. 面对提问或者展示这些场景,基本能保持镇定,不会慌里慌张的,就算偶尔有点紧张,比如语速稍微变快、声音有点抖,也能自己快速调整过来,很快就恢复平稳状态,不会让紧张影响整体发挥;
3. 跟人交流的时候,目光交流特别自然,不会躲躲闪闪,肢体动作也跟说话内容配合得刚好,比如讲重点的时候会配合手势强调,坐着的时候姿态也很放松,这些细节让说的话更有说服力,让人觉得特别靠谱;
4. 不管是做展示、答问题,还是走流程,每个环节的时间都控制得特别准,不会出现超时或者没说完的情况,环节之间衔接得也很自然,不会有生硬的停顿,更难得的是,还会特意留时间做总结,把核心内容再梳理一遍,让人印象更深刻。
# 综合评价
总的来说,这学生在知识掌握、行业认知、流程理解、岗位适配、项目经验、表达能力、心态调整、沟通仪态和时间把控上,都表现得特别出色,基础扎实还懂实际应用,就算偶尔有小瑕疵也不影响整体实力。这样的学生不管是继续学习还是去工作,都能快速适应,后续再把流程细节打磨打磨,综合能力还能再上一个大台阶,绝对是个好苗子。`
};
} else {
return {
totalScore: 85,
professionalScore: 34.0,
performanceScore: 51.0,
radarData: [9, 8, 9, 9],
title: "基础面试评价",
content: `在专业能力方面,候选人对 [岗位相关专业知识] 有基本掌握,能够清晰回答与过往项目经验相关的问题,例如在描述 [具体项目] 时,能准确说明自己承担的职责和达成的成果,体现出一定的实践操作能力。但在深入探讨 [某一专业难点] 时,表述略显浅显,对行业前沿动态的了解不够全面,专业深度有提升空间。
totalScore: 14, // 根据JSON计算的真实总分 (求职面试初体验)
professionalScore: 7, // (2+1+1+1+1+1)/6*10*0.6 = 7
performanceScore: 7, // (2+1+2+2)/4*10*0.4 = 7
radarData: [2, 1, 1, 1, 1, 1], // 六项专业能力指标来自JSON
radarData2: [2, 1, 2, 2], // 四项现场表现指标来自JSON
title: "面试评价",
content: `# 专业能力
沟通表达上,候选人语言流畅,逻辑较为清晰,能够主动回应面试官的问题,展现出良好的沟通意愿。不过在面对一些开放性问题时,思维发散性稍弱,回答的层次感不够突出,有时会出现表述重复的情况。`
1. 基础概念掌握得很不好,经常犯错误,连最基本的知识点都拎不清,这样一来根本没办法展开有效的交流,说出来的内容因为概念错了,也没什么参考价值;
2. 对行业基本情况几乎一无所知,不管问什么跟行业相关的问题,都答不上来,完全没接触过行业里的常识,聊起行业话题根本插不上话;
3. 一点都不理解企业的工作流程,不知道一项业务是怎么推进的,也说不清楚如果自己进了企业,该在哪个环节做事、要干些什么,对实际工作场景完全没概念;
4. 碰到问题或任务的时候,一点清晰的思路都没有,东想西想没章法,想出来的方案要么不切实际、没法落地,要么根本没解决核心问题,完全拿不出能用的办法;
5. 对目标岗位的工作职责彻底不了解,不知道岗位要干哪些活、需要什么能力,聊到跟岗位相关的内容,根本说不出有用的信息,也没法表达自己跟岗位的关联;
6. 要么就没做过什么项目,要么就是有项目经历也说不明白 —— 既讲不清项目背景,也说不出自己在里面干了啥,更总结不出从项目里学到了什么、能力有没有提升,完全没法用项目证明自己的能力;
# 现场表现力
1. 说话特别不连贯,一句完整的话都说不利索,想表达的观点颠三倒四,听的人得费劲猜才能勉强懂一点,信息传递特别低效,很容易让人误解;
2. 心态特别差,一碰到提问或者有点压力的情况,就慌慌张张的,要么说不出话,要么越说越乱,根本没办法持续把问题答完,稍微有点压力就扛不住;
3. 跟人交流或者做展示的时候,姿态特别不专业,要么不敢抬头看人、眼神躲躲闪闪,要么肢体动作很僵硬、很别扭,显得特别紧张、不靠谱,根本没法让人信服;
4. 完全没有时间概念,不管是做事、聊天还是做展示,都把控不好时间 —— 要么一个环节拖半天,要么节奏乱得一塌糊涂,严重影响整个流程的推进,让整体效率特别低。
# 综合评价
总的来说,这学生在专业基础、行业认知、企业流程理解、问题解决、岗位认知、项目经验、表达能力、心态调整、职业仪态和时间管理这些关键方面,都存在比较明显的不足。这些问题不光让专业交流没法顺利进行、碰到问题没法有效解决,也能看出目前他还不太适配实际工作场景,职业潜力还需要花很多功夫去挖掘和提升,得好好补补基础、多积累实践经验才行。`
};
}
};
@@ -107,7 +190,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
<div className="interview-rating-video-section">
<div className="interview-rating-header">
<span className="interview-rating-header-title">
钢铁是怎样炼成的
{selectedItem}
</span>
</div>
<div className="interview-rating-video">
@@ -189,6 +272,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
{/* 面试评分区域 */}
<div className="interview-evaluation-charts-wrapper">
<div className="interview-rating-header">
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5uY7Ek50.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '10px' }} />
<span className="interview-rating-header-title">面试评分</span>
</div>
<div className="charts-content">
@@ -196,16 +280,32 @@ export default ({ selectedItem = "求职面试初体验" }) => {
<ScoreChart className="score-chart" value={getEvaluationData().totalScore} />
<div className="score-info">
<div className="score-info-item item1">
<span className="score-info-item-title">
专业能力评分(40)
</span>
<span className="score-info-item-value">{getEvaluationData().professionalScore}</span>
<p className="score-info-item-title">
专业能力评分
<span className="score-info-item-value">{getEvaluationData().professionalScore}</span>
</p>
<p className="score-info-line">
<i
style={{
width: `${(getEvaluationData().professionalScore / 60) * 100}%`,
}}
/>
<span className="score-info-line-total-value">60</span>
</p>
</div>
<div className="score-info-item item2">
<span className="score-info-item-title">
现场表现评分(60)
</span>
<span className="score-info-item-value">{getEvaluationData().performanceScore}</span>
<p className="score-info-item-title">
现场表现评分
<span className="score-info-item-value">{getEvaluationData().performanceScore}</span>
</p>
<p className="score-info-line">
<i
style={{
width: `${(getEvaluationData().performanceScore / 40) * 100}%`,
}}
/>
<span className="score-info-line-total-value">40</span>
</p>
</div>
</div>
</div>
@@ -214,20 +314,22 @@ export default ({ selectedItem = "求职面试初体验" }) => {
className="radar-chart"
data={getEvaluationData().radarData}
indicator={[
{ name: "核心知识掌握与精准应用", max: 10 },
{ name: "问题分析方案设计与成果表达", max: 10 },
{ name: "产业认知与行业趋势洞察", max: 10 },
{ name: "企业工作流理解与实践", max: 10 },
{ name: "基础知识\n掌握水平", max: 10 },
{ name: "产业链\n认知程度", max: 10 },
{ name: "企业生产\n体系了解", max: 10 },
{ name: "典型问题\n解决能力", max: 10 },
{ name: "岗位职责\n理解程度", max: 10 },
{ name: "项目经历\n丰富程度", max: 10 },
]}
/>
<RadarChart
className="radar-chart"
data={getEvaluationData().radarData2 || [7, 8, 6, 7]}
indicator={[
{ name: "沟通表达与逻辑思维", max: 10 },
{ name: "团队协作与责任意识", max: 10 },
{ name: "学习能力与适应能力", max: 10 },
{ name: "职业素养与发展潜力", max: 10 },
{ name: "语言表达与逻辑", max: 10 },
{ name: "自信与情绪管理", max: 10 },
{ name: "仪表与职场礼仪", max: 10 },
{ name: "时间管理与条理性", max: 10 },
]}
lineClolr="#FFE4D9"
areaColor="#FFD4C1"
@@ -239,11 +341,12 @@ export default ({ selectedItem = "求职面试初体验" }) => {
{/* 基础面试评价区域 */}
<div className="interview-evaluation-text-wrapper">
<div className="interview-rating-header">
<div className="interview-rating-header" style={{ justifyContent: 'flex-start' }}>
<img src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5vlvUj2X.png" alt="icon" style={{ width: '28px', height: '28px', marginRight: '10px' }} />
<span className="interview-rating-header-title">{getEvaluationData().title}</span>
</div>
<div className="interview-rating-text">
{getEvaluationData().content}
<ReactMarkdown>{getEvaluationData().content}</ReactMarkdown>
</div>
</div>
</>

View File

@@ -23,10 +23,10 @@ export default function RadarChart({
grid: { show: false },
radar: [
{
center: ["50%", "55%"],
center: ["50%", "50%"],
indicator,
radius: "65%", // 默认 65%,调大即可把文字整体外推
nameGap: 20, // 再额外增加 6-12px 间距
radius: "55%", // 调小半径以确保文字不超出容器
nameGap: 8, // 调整文字与雷达图的间距
shape: "circle", // 设置雷达图外圈为圆形
// 网格线样式配置
splitLine: {
@@ -40,9 +40,18 @@ export default function RadarChart({
axisName: {
color: "#4E5969",
fontSize: 12,
fontWeight: "900", // 更粗的文字
lineHeight: 16,
formatter: function(value) {
// 移除富文本标记,只返回纯文本
return value.replace(/{[ab]\|([^}]+)}/g, '$1').replace(/\n/g, '');
// 已经包含换行符的直接返回
if (value.includes('\n')) {
return value;
}
// 对较长的文本进行换行处理
if (value.length > 8) {
return value.substring(0, 8) + '\n' + value.substring(8);
}
return value;
},
},
axisLine: {

View File

@@ -19,10 +19,11 @@ const ScoreChart = ({
useEffect(() => {
if (!chartRef.current) return;
// 如果实例不存在,则初始化
if (!chartInstance.current) {
chartInstance.current = echarts.init(chartRef.current);
}
// 销毁旧实例
if (chartInstance.current) chartInstance.current.dispose();
// 新建实例
chartInstance.current = echarts.init(chartRef.current);
const option = {
series: [
@@ -81,26 +82,15 @@ const ScoreChart = ({
],
};
// 设置或更新配置,避免重新触发动画
chartInstance.current.setOption(option, {
notMerge: false,
lazyUpdate: true,
silent: false,
});
chartInstance.current.setOption(option);
// 监听窗口变化
window.addEventListener("resize", resize);
return () => {
window.removeEventListener("resize", resize);
};
}, [value, colors]);
// 组件卸载时清理
useEffect(() => {
return () => {
chartInstance.current?.dispose();
};
}, []);
}, [value, colors]);
return <div ref={chartRef} className={className} />;
};

View File

@@ -48,7 +48,7 @@ const JobStrategyDetailPage = () => {
onClick={() => setActiveItem("2")}
>
<span className="nav-icon curved-icon"></span>
<span className="nav-text">曲线就业方案</span>
<span className="nav-text">就业与晋升路径</span>
</div>
</div>

View File

@@ -72,7 +72,7 @@ const ProfileCard = () => {
src="recuUY5pWmMoaQ"
/>
<span className="profile-card-class-info-item-title">
就业管家课程
复合能力课
</span>
<span className="profile-card-class-info-item-text">
{studentInfo?.className || "-"}
@@ -83,7 +83,7 @@ const ProfileCard = () => {
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-title">垂直能力课</span>
<span className="profile-card-class-info-item-text">
{studentInfo?.stageName || "-"}
</span>

View File

@@ -100,6 +100,7 @@
background-color: #f4f7f9;
position: relative;
margin-bottom: 10px;
overflow: visible;
> i {
position: absolute;
top: 0;

View File

@@ -120,30 +120,71 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading }) => {
{/* 时长 */}
<li className="study-studes-card-study-info">
<div className="study-studes-card-time-wrapper">
<p className="study-studes-card-study-info-title">
个人学习时长h
<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: `${Math.min(
(personalStudyHours / classAverageStudyHours) * 70,
100
)}%`,
width: "100%",
position: 'relative',
display: 'block'
}}
>
<span className="icon1">{personalStudyHours}</span>
<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">
班级平均学习时长h
<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: "80%" }}>
<span className="icon2">{classAverageStudyHours}</span>
<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>
@@ -154,8 +195,9 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading }) => {
</li>
{/* 课程整体完成情况 */}
<li className="study-studes-card-curriculum">
<p className="study-studes-card-curriculum-title">
整体课程完成情况
<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完成情况`}
@@ -179,8 +221,9 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading }) => {
</li>
{/* 课后作业完成情况 */}
<li className="study-studes-card-curriculum-homework study-studes-card-curriculum">
<p className="study-studes-card-curriculum-title">
课后作业完成情况
<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}
@@ -203,7 +246,10 @@ const StudyStudes = ({ studyStatistics, learningProgress, loading }) => {
</li>
{/* 考勤情况 */}
<li className="study-studes-card-curriculum">
<p className="study-studes-card-curriculum-title">考勤情况</p>
<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={

View File

@@ -13,10 +13,9 @@
width: 100%;
height: 805px;
box-sizing: border-box;
padding: 0 20px;
padding: 20px;
display: flex;
justify-content: space-between;
margin-top: 20px;
.unified-profile-left {
width: 373px;

View File

@@ -5,6 +5,7 @@ import { mockData } from "@/data/mockData";
import "./index.css";
const PublicCourses = () => {
// 默认不选中任何课程,显示黑屏状态
const [selectedCourse, setSelectedCourse] = useState(null);
const handleCourseClick = (course) => {

View File

@@ -309,7 +309,7 @@ const ResumeInterviewPage = () => {
className="job-level-tag"
/>
<div className="job-name">
<p>岗位名称{position.title}</p>
<p>{position.title}</p>
<span className="job-arrow"></span>
</div>
</div>

View File

@@ -9,12 +9,21 @@ export async function getPublicCourseLiveList() {
});
}
// 获取课程直播列表
// 获取课程直播列表(包含复合能力课和垂直能力课)
export async function getCourseLiveList() {
// 模拟异步请求
// 合并复合能力课和垂直能力课
const compoundCourses = mockData.courseLiveList || [];
const verticalCourses = mockData.verticalCourseLiveList || [];
// 返回包含两种课程类型的数据
return Promise.resolve({
success: true,
data: mockData.courseLiveList || [],
data: {
compound: compoundCourses,
vertical: verticalCourses,
// 为了兼容旧版本,保留原始格式
all: [...compoundCourses, ...verticalCourses]
}
});
}