feat: 作业页面单元分类导航和样式优化

- 为复合能力课和垂直能力课添加单元分组结构
- 实现单元导航栏和课程筛选功能
- 优化导航栏样式,采用胶囊式设计
- 调整页面布局和间距,提升视觉体验
- 修复营销能力课日历事件显示问题
- 修复1v1规划时间为14:00-16:00
- 修复作业页面iframe返回后滚动失效问题

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
KQL
2025-09-08 03:53:49 +08:00
parent 27f2339c9e
commit d1f6f2ee0d
29 changed files with 16359 additions and 187 deletions

View File

@@ -177,6 +177,28 @@ const EventDetailModal = ({ isOpen, event, onClose }) => {
</div>
</div>
{/* 企业高管公开课添加线下参与标签 */}
{eventItem.type === 'public-course' && (
<div className="event-info-row" style={{ marginTop: '8px' }}>
<div style={{
padding: '4px 12px',
backgroundColor: '#f0f9ff',
color: '#0ea5e9',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '500',
display: 'inline-flex',
alignItems: 'center',
gap: '4px'
}}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
可报名线下参与
</div>
</div>
)}
{eventItem.description && (
<div className="event-description">
{eventItem.description}

View File

@@ -242,44 +242,37 @@
/* 复合技能课 */
.event-item.compound-skill {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background: #5AC6FF;
}
/* 垂直技能课 */
.event-item.vertical-skill {
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
background: #CB78E0;
}
/* 公开课 */
/* 企业高管公开课 */
.event-item.public-course {
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
background: #D0A474;
}
/* AI课程 */
.event-item.ai-course {
background: #F7A133;
}
/* 营销课 */
.event-item.marketing-course {
background: #FF4277;
}
/* 1v1规划 */
.event-item.one-on-one {
background: linear-gradient(135deg, #ec4899 0%, #f472b6 100%);
background: #FFCC3F;
}
/* 线下面试模拟 */
.event-item.interview {
background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
}
/* 默认类型保留 */
.event-item.class {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.event-item.meeting {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.event-item.lab {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.event-item.exam {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
background: #0743DA;
}
.event-more {

View File

@@ -16,26 +16,68 @@
.homework-page-content-list {
width: 1120px;
min-height: 360px;
border-radius: 8px;
border-radius: 12px;
background-color: #fff;
margin-bottom: 20px;
margin-bottom: 24px;
box-sizing: border-box;
padding: 20px 15px;
padding: 24px 20px 20px;
position: relative;
overflow: hidden; /* 重要:隐藏溢出内容 */
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
.homework-page-content-list-title {
font-size: 20px;
font-weight: 600;
color: #000;
.homework-page-content-list-header {
margin-bottom: 16px;
.homework-page-content-list-title {
font-size: 18px;
font-weight: 600;
color: #1d2129;
margin: 0;
line-height: 1;
}
}
.homework-page-unit-nav {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f2f3f5;
.unit-nav-item {
padding: 5px 14px;
font-size: 13px;
color: #4e5969;
background: #f7f8fa;
border: 1px solid transparent;
border-radius: 14px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
font-weight: 400;
&:hover {
background: #e8f3ff;
color: #2c7aff;
}
&.active {
background: #2c7aff;
color: #fff;
font-weight: 500;
}
}
}
.homework-page-content-list-class {
width: calc(100% + 30px); /* 扩展到padding外 */
height: 300px;
margin-top: 20px;
margin-left: -15px;
margin-right: -15px;
padding: 0 15px;
width: calc(100% + 40px); /* 扩展到padding外 */
height: 280px;
margin-top: 0;
margin-left: -20px;
margin-right: -20px;
padding: 0 20px;
padding-bottom: 10px;
overflow-x: auto;
overflow-y: hidden;

View File

@@ -9,7 +9,12 @@ import "./index.css";
const HomeworkPage = () => {
const { homework } = mockData;
const scrollContainerRef = useRef(null);
const verticalScrollContainerRef = useRef(null);
const [showIframe, setShowIframe] = useState(false);
const [selectedUnits, setSelectedUnits] = useState({
1: "全部", // 复合能力课的选中单元
2: "全部" // 垂直能力课的选中单元
});
// 调试:打印课程数量
console.log('作业数据:', homework);
@@ -19,67 +24,90 @@ const HomeworkPage = () => {
// 添加鼠标滚轮横向滚动支持(更丝滑的滚动)
useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
// 如果显示iframe不初始化滚动
if (showIframe) return;
const containers = [scrollContainerRef.current, verticalScrollContainerRef.current].filter(Boolean);
if (containers.length === 0) return;
let animationId = null;
let targetScrollLeft = container.scrollLeft;
const animationIds = new Map();
const targetScrollLefts = new Map();
const handlers = new Map();
// 平滑滚动动画
const smoothScroll = () => {
const currentScrollLeft = container.scrollLeft;
const diff = targetScrollLeft - currentScrollLeft;
containers.forEach(container => {
targetScrollLefts.set(container, container.scrollLeft);
if (Math.abs(diff) > 0.5) {
container.scrollLeft = currentScrollLeft + diff * 0.15; // 缓动系数
animationId = requestAnimationFrame(smoothScroll);
} else {
container.scrollLeft = targetScrollLeft;
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
// 平滑滚动动画
const smoothScroll = () => {
const currentScrollLeft = container.scrollLeft;
const targetScrollLeft = targetScrollLefts.get(container);
const diff = targetScrollLeft - currentScrollLeft;
if (Math.abs(diff) > 0.5) {
container.scrollLeft = currentScrollLeft + diff * 0.15; // 缓动系数
const animId = requestAnimationFrame(smoothScroll);
animationIds.set(container, animId);
} else {
container.scrollLeft = targetScrollLeft;
const animId = animationIds.get(container);
if (animId) {
cancelAnimationFrame(animId);
animationIds.delete(container);
}
}
}
};
};
// 鼠标滚轮横向滚动
const handleWheel = (e) => {
e.preventDefault();
// 鼠标滚轮横向滚动
const handleWheel = (e) => {
e.preventDefault();
// 计算滚动距离,使用较小的值让滚动更平滑
const scrollAmount = e.deltaY * 0.8;
let newTargetScrollLeft = targetScrollLefts.get(container) + scrollAmount;
// 限制滚动范围
newTargetScrollLeft = Math.max(0, Math.min(newTargetScrollLeft, container.scrollWidth - container.clientWidth));
targetScrollLefts.set(container, newTargetScrollLeft);
// 如果没有正在进行的动画,启动平滑滚动
if (!animationIds.has(container)) {
const animId = requestAnimationFrame(smoothScroll);
animationIds.set(container, animId);
}
};
// 计算滚动距离,使用较小的值让滚动更平滑
const scrollAmount = e.deltaY * 0.8;
targetScrollLeft = container.scrollLeft + scrollAmount;
// 限制滚动范围
targetScrollLeft = Math.max(0, Math.min(targetScrollLeft, container.scrollWidth - container.clientWidth));
// 如果没有正在进行的动画,启动平滑滚动
if (!animationId) {
animationId = requestAnimationFrame(smoothScroll);
}
};
container.addEventListener('wheel', handleWheel, { passive: false });
handlers.set(container, handleWheel);
container.addEventListener('wheel', handleWheel, { passive: false });
});
return () => {
container.removeEventListener('wheel', handleWheel);
if (animationId) {
cancelAnimationFrame(animationId);
}
containers.forEach(container => {
const handleWheel = handlers.get(container);
if (handleWheel) {
container.removeEventListener('wheel', handleWheel);
}
const animId = animationIds.get(container);
if (animId) {
cancelAnimationFrame(animId);
}
});
};
}, []);
}, [showIframe]);
const handleClickBtn = (sectionId, itemId) => {
// 只有复合能力培养的第72个项目展会策划教学可以点击
if (sectionId === 1 && itemId === 72) {
const handleClickBtn = (sectionId, item) => {
// 垂直能力课中标记为isShowCase的课程可以点击
if (sectionId === 2 && item.isShowCase) {
setShowIframe(true);
}
};
// 判断是否应该显示灰色图片和禁用按钮
const isDisabled = (sectionId, itemId) => {
// 只有复合能力培养的展会策划教学第72个是启用状态
return !(sectionId === 1 && itemId === 72);
const isDisabled = (sectionId, item) => {
// 垂直能力课中标记为isShowCase的课程是启用状态
if (sectionId === 2 && item.isShowCase) {
return false;
}
return true;
};
// 如果显示iframe渲染全页面的iframe
@@ -105,18 +133,62 @@ const HomeworkPage = () => {
);
}
// 获取筛选后的课程列表
const getFilteredCourses = (sectionId) => {
const section = homework.find(h => h.id === sectionId);
if (!section) return [];
const selectedUnit = selectedUnits[sectionId];
// 如果有units结构使用新结构
if (section.units) {
if (selectedUnit === "全部") {
// 将所有单元的课程合并
return section.units.flatMap(unit => unit.courses);
} else {
// 返回选中单元的课程
const unit = section.units.find(u => u.name === selectedUnit);
return unit ? unit.courses : [];
}
}
// 兼容旧结构
return section.list || [];
};
// 正常渲染作业列表
return (
<div className="homework-page-wrapper">
<ul className="homework-page-content">
{homework.map((item) => (
<li key={item.id} className="homework-page-content-list">
<p className="homework-page-content-list-title">{item.name}</p>
<div className="homework-page-content-list-header">
<p className="homework-page-content-list-title">{item.name}</p>
</div>
{item.units && (
<div className="homework-page-unit-nav">
<span
className={`unit-nav-item ${selectedUnits[item.id] === "全部" ? "active" : ""}`}
onClick={() => setSelectedUnits({...selectedUnits, [item.id]: "全部"})}
>
全部
</span>
{item.units.map((unit, index) => (
<span
key={index}
className={`unit-nav-item ${selectedUnits[item.id] === unit.name ? "active" : ""}`}
onClick={() => setSelectedUnits({...selectedUnits, [item.id]: unit.name})}
>
{unit.name}
</span>
))}
</div>
)}
<ul
className="homework-page-content-list-class"
ref={item.id === 1 ? scrollContainerRef : null}
ref={item.id === 1 ? scrollContainerRef : item.id === 2 ? verticalScrollContainerRef : null}
>
{item.list.map((contentItem) => (
{getFilteredCourses(item.id).map((contentItem) => (
<li
key={contentItem.id}
className="homework-page-content-list-content-item"
@@ -133,9 +205,9 @@ const HomeworkPage = () => {
</Tooltip>
<div
className={`homework-page-content-list-content-item-btn ${
isDisabled(item.id, contentItem.id) ? "disabled" : "completed"
isDisabled(item.id, contentItem) ? "disabled" : "completed"
}`}
onClick={() => !isDisabled(item.id, contentItem.id) && handleClickBtn(item.id, contentItem.id)}
onClick={() => !isDisabled(item.id, contentItem) && handleClickBtn(item.id, contentItem)}
>
已完成
</div>

View File

@@ -11,7 +11,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
case "求职面试初体验":
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";
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_simulation/3years_later.mov";
case "第一次线下面试模拟":
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/video/teach_sys/interview_offline_vedio/recuUpJSOKoqAm.mov"; // 使用相同视频作为示例
case "第二次线下面试模拟":

View File

@@ -18,7 +18,8 @@ const PublicCourses = () => {
<CoursesVideoPlayer
selectedCourse={selectedCourse}
teacherData={mockData.teacherData}
unitPosters={mockData.unitPosters}
unitPosters={mockData.publicCourseBackgrounds}
isPublicCourse={true}
/>
<PublicCourseList onCourseClick={handleCourseClick} />
</div>