Files
jiaowu-test/src/pages/CalendarPage/components/EventDetailModal.jsx
KQL 561d5c286d feat: 实现日历课程点击跳转到直播间功能
- 添加日历课程详情弹窗的点击跳转功能
- 公共课直播间和课程直播间支持URL参数自动选中课程
- 优化岗位详情页面样式,复用简洁卡片样式
- 为岗位详情标题添加图标
- 调整不同类型课程的跳转逻辑

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 14:14:45 +08:00

265 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Portal from "@/components/Portal";
import { IconCalendarClock, IconCheck, IconClockCircle } from "@arco-design/web-react/icon";
const EventDetailModal = ({ isOpen, event, onClose }) => {
const navigate = useNavigate();
// ESC键关闭模态框
useEffect(() => {
const handleEscKey = (e) => {
if (e.key === "Escape" && isOpen) {
onClose();
}
};
if (isOpen) {
document.addEventListener("keydown", handleEscKey);
}
return () => {
document.removeEventListener("keydown", handleEscKey);
};
}, [isOpen, onClose]);
// 如果未打开或无事件数据,不渲染
if (!isOpen || !event) {
return null;
}
// 事件类型映射
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' },
};
// 处理遮罩层点击
const handleOverlayClick = (e) => {
if (e.target === e.currentTarget) {
onClose();
}
};
// 处理关闭按钮点击
const handleCloseClick = () => {
onClose();
};
// 格式化时间显示
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 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' };
};
// 处理课程点击 - 跳转到对应的课程页面
const handleCourseClick = (eventItem) => {
// 构建URL参数
const params = new URLSearchParams();
if (eventItem.id) {
params.append('courseId', eventItem.id);
}
if (eventItem.title) {
params.append('courseTitle', eventItem.title);
}
// 根据课程类型跳转到不同页面
switch(eventItem.type) {
case 'compound-skill':
case 'vertical-skill':
// 复合技能课/垂直技能课 - 跳转到课程直播间
navigate(`/live?${params.toString()}`);
break;
case 'public-course':
// 公开课包括AI课、企业高管公开课、营销课 - 跳转到公共课直播间
navigate(`/public-courses?${params.toString()}`);
break;
case 'one-on-one':
// 1v1规划 - 跳转到定制求职策略页面
navigate('/job-strategy');
break;
case 'interview':
// 线下面试模拟 - 不跳转,仅关闭弹窗
break;
default:
// 其他类型暂不跳转
break;
}
// 关闭弹窗
onClose();
};
return (
<Portal className="event-detail-portal">
<div className="event-detail-overlay" onClick={handleOverlayClick}>
<div className="event-detail-modal-new">
{/* 模态框头部 */}
<div className="event-detail-header-new">
<h3 className="event-detail-title-new">
{isMultiEventMode ? '日程详情' : '课程详情'}
</h3>
<button
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-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 ${eventItem.type !== 'interview' ? 'clickable-course' : ''}`}
onClick={eventItem.type !== 'interview' ? () => handleCourseClick(eventItem) : undefined}
style={{ cursor: eventItem.type !== 'interview' ? 'pointer' : 'default' }}
>
<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.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}
</div>
)}
</div>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</Portal>
);
};
export default EventDetailModal;