feat: 实现日历课程点击跳转到直播间功能
- 添加日历课程详情弹窗的点击跳转功能 - 公共课直播间和课程直播间支持URL参数自动选中课程 - 优化岗位详情页面样式,复用简洁卡片样式 - 为岗位详情标题添加图标 - 调整不同类型课程的跳转逻辑 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,220 +1,265 @@
|
||||
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键关闭模态框
|
||||
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' };
|
||||
};
|
||||
|
||||
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">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -662,6 +662,19 @@
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 可点击的课程卡片样式 */
|
||||
.event-card-new.clickable-course {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.event-card-new.clickable-course:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: #4080ff;
|
||||
box-shadow: 0 4px 12px rgba(64, 128, 255, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 事件卡片头部 */
|
||||
.event-card-header {
|
||||
display: flex;
|
||||
|
||||
@@ -277,25 +277,11 @@
|
||||
.job-info-modal-content-position-info-companyInfo {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f7f8fa 100%);
|
||||
margin: 8px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(102, 126, 234, 0.1);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
min-height: auto;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #e5e6eb;
|
||||
|
||||
> p {
|
||||
width: 100%;
|
||||
@@ -304,47 +290,42 @@
|
||||
.description-title,
|
||||
.requirements-title,
|
||||
.companyInfo-title {
|
||||
font-size: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
line-height: 28px;
|
||||
color: #1d2129;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
position: relative;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 2px;
|
||||
.title-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.description-content {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
text-align: left;
|
||||
|
||||
.description-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 8px;
|
||||
text-align: left;
|
||||
|
||||
.description-number {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
min-width: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-right: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -352,7 +333,7 @@
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -368,16 +349,9 @@
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
|
||||
.company-paragraph {
|
||||
margin-bottom: 12px;
|
||||
text-indent: 2em;
|
||||
text-align: justify;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.requirements-content {
|
||||
width: 100%;
|
||||
@@ -386,16 +360,16 @@
|
||||
.requirements-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 8px;
|
||||
text-align: left;
|
||||
|
||||
.requirement-number {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
min-width: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #667eea;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-right: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -403,7 +377,7 @@
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
line-height: 24px;
|
||||
color: #4e5969;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -269,7 +269,10 @@ export default ({ visible, onClose, data, directToResume = false, hideDeliverBut
|
||||
)}
|
||||
{data?.details?.description && (
|
||||
<div className="job-info-modal-content-position-info-description">
|
||||
<p className="description-title">岗位描述</p>
|
||||
<p className="description-title">
|
||||
<img className="title-icon" src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="" />
|
||||
岗位描述
|
||||
</p>
|
||||
<div className="description-content">
|
||||
{data?.details?.description.split(/\d+\.\s*/).filter(item => item.trim()).map((item, index) => (
|
||||
<div key={index} className="description-item">
|
||||
@@ -282,7 +285,10 @@ export default ({ visible, onClose, data, directToResume = false, hideDeliverBut
|
||||
)}
|
||||
{(data?.details?.requirements?.length > 0 || data?.details?.requirementsText) && (
|
||||
<div className="job-info-modal-content-position-info-requirements">
|
||||
<p className="requirements-title">岗位要求</p>
|
||||
<p className="requirements-title">
|
||||
<img className="title-icon" src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="" />
|
||||
岗位要求
|
||||
</p>
|
||||
<div className="requirements-content">
|
||||
{data?.details?.requirements ? (
|
||||
data?.details?.requirements?.map((item, index) => (
|
||||
@@ -304,7 +310,10 @@ export default ({ visible, onClose, data, directToResume = false, hideDeliverBut
|
||||
)}
|
||||
{data?.details?.companyInfo && (
|
||||
<div className="job-info-modal-content-position-info-companyInfo">
|
||||
<p className="companyInfo-title">公司介绍</p>
|
||||
<p className="companyInfo-title">
|
||||
<img className="title-icon" src="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuW0XRVB1bpV.png" alt="" />
|
||||
公司介绍
|
||||
</p>
|
||||
<div className="companyInfo-content">
|
||||
{data?.details?.companyInfo.split('\n').map((paragraph, index) => (
|
||||
<p key={index} className="company-paragraph">
|
||||
|
||||
@@ -38,7 +38,12 @@ const Dashboard = () => {
|
||||
|
||||
// 根据选中日期筛选任务
|
||||
const getTasksForDate = (date) => {
|
||||
if (!dashboardData?.tasks?.allTasks) return [];
|
||||
console.log("getTasksForDate called with date:", date);
|
||||
console.log("dashboardData.tasks:", dashboardData?.tasks);
|
||||
if (!dashboardData?.tasks?.allTasks) {
|
||||
console.log("No allTasks found in dashboardData");
|
||||
return [];
|
||||
}
|
||||
// Check if date is valid before calling getTime
|
||||
if (!date || isNaN(date.getTime())) {
|
||||
console.warn("Invalid date provided to getTasksForDate:", date);
|
||||
|
||||
@@ -59,7 +59,7 @@ const ExpertSupportPage = () => {
|
||||
{
|
||||
id: 1,
|
||||
type: "user",
|
||||
content: "您好,我在使用TypeScript时遇到了一些泛型定义的问题",
|
||||
content: "您好,我遇到了一些问题",
|
||||
time: "2025-01-08 16:45",
|
||||
avatar: "👤",
|
||||
},
|
||||
@@ -67,10 +67,10 @@ const ExpertSupportPage = () => {
|
||||
id: 2,
|
||||
type: "expert",
|
||||
content:
|
||||
"您好!我是王开发专家,很高兴为您解答TypeScript相关问题。请详细描述一下您遇到的具体问题。",
|
||||
"你好我是多多ai智能问答小助手,有什么问题就来问我吧",
|
||||
time: "2025-01-08 16:46",
|
||||
avatar: "👨💻",
|
||||
expertName: "王开发专家",
|
||||
expertName: "多多机器人",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
|
||||
@@ -41,10 +41,30 @@
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f2f3f5;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c9cdd4;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #86909c;
|
||||
}
|
||||
}
|
||||
|
||||
.unit-nav-item {
|
||||
padding: 5px 14px;
|
||||
|
||||
@@ -10,6 +10,7 @@ const HomeworkPage = () => {
|
||||
const { homework } = mockData;
|
||||
const scrollContainerRef = useRef(null);
|
||||
const verticalScrollContainerRef = useRef(null);
|
||||
const unitNavRefs = useRef({});
|
||||
const [showIframe, setShowIframe] = useState(false);
|
||||
const [selectedUnits, setSelectedUnits] = useState({
|
||||
1: "全部", // 复合能力课的选中单元
|
||||
@@ -27,7 +28,9 @@ const HomeworkPage = () => {
|
||||
// 如果显示iframe,不初始化滚动
|
||||
if (showIframe) return;
|
||||
|
||||
const containers = [scrollContainerRef.current, verticalScrollContainerRef.current].filter(Boolean);
|
||||
// 收集所有需要横向滚动的容器
|
||||
const unitNavContainers = Object.values(unitNavRefs.current).filter(Boolean);
|
||||
const containers = [scrollContainerRef.current, verticalScrollContainerRef.current, ...unitNavContainers].filter(Boolean);
|
||||
if (containers.length === 0) return;
|
||||
|
||||
const animationIds = new Map();
|
||||
@@ -177,7 +180,10 @@ const HomeworkPage = () => {
|
||||
<p className="homework-page-content-list-title">{item.name}</p>
|
||||
</div>
|
||||
{item.units && (
|
||||
<div className="homework-page-unit-nav">
|
||||
<div
|
||||
className="homework-page-unit-nav"
|
||||
ref={el => unitNavRefs.current[item.id] = el}
|
||||
>
|
||||
<span
|
||||
className={`unit-nav-item ${selectedUnits[item.id] === "全部" ? "active" : ""}`}
|
||||
onClick={() => setSelectedUnits({...selectedUnits, [item.id]: "全部"})}
|
||||
|
||||
@@ -4,11 +4,11 @@ import ScoreChart from "../ScoreChart";
|
||||
import RadarChart from "../RadarChart";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ selectedItem = "求职面试初体验" }) => {
|
||||
export default ({ selectedItem = "面试初体验" }) => {
|
||||
// 根据选中项目获取对应的视频URL
|
||||
const getVideoUrl = () => {
|
||||
switch(selectedItem) {
|
||||
case "求职面试初体验":
|
||||
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_simulation/3years_later.mov";
|
||||
@@ -146,7 +146,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
totalScore: 14, // 根据JSON计算的真实总分 (求职面试初体验)
|
||||
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)
|
||||
@@ -177,7 +177,7 @@ export default ({ selectedItem = "求职面试初体验" }) => {
|
||||
|
||||
// 判断是否应该显示评价内容
|
||||
const shouldShowEvaluation = () => {
|
||||
return selectedItem === "求职面试初体验" ||
|
||||
return selectedItem === "面试初体验" ||
|
||||
selectedItem === "未来的自己" ||
|
||||
selectedItem === "第一次线下面试模拟" ||
|
||||
selectedItem === "第二次线下面试模拟" ||
|
||||
@@ -331,9 +331,10 @@ export default ({ selectedItem = "求职面试初体验" }) => {
|
||||
{ name: "仪表与职场礼仪", max: 10 },
|
||||
{ name: "时间管理与条理性", max: 10 },
|
||||
]}
|
||||
lineClolr="#FFE4D9"
|
||||
areaColor="#FFD4C1"
|
||||
areaBorderColor="#FFB89A"
|
||||
lineClolr="#E8F5E9"
|
||||
areaColor="#C8E6C9"
|
||||
areaBorderColor="#66BB6A"
|
||||
isGreenTheme={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import "./index.css";
|
||||
const TimelineItem = Timeline.Item;
|
||||
|
||||
export default ({ onItemSelect }) => {
|
||||
const [selectedItem, setSelectedItem] = useState("求职面试初体验");
|
||||
const [selectedItem, setSelectedItem] = useState("面试初体验");
|
||||
|
||||
const handleItemClick = (itemName) => {
|
||||
setSelectedItem(itemName);
|
||||
@@ -29,16 +29,16 @@ export default ({ onItemSelect }) => {
|
||||
<TimelineItem
|
||||
lineType="dashed"
|
||||
dot={
|
||||
<div className={`time-line-dot-icon ${selectedItem === "求职面试初体验" ? "time-line-dot-icon-active" : ""}`} />
|
||||
<div className={`time-line-dot-icon ${selectedItem === "面试初体验" ? "time-line-dot-icon-active" : ""}`} />
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`time-line-item ${selectedItem === "求职面试初体验" ? "time-line-item-active" : ""}`}
|
||||
onClick={() => handleItemClick("求职面试初体验")}
|
||||
className={`time-line-item ${selectedItem === "面试初体验" ? "time-line-item-active" : ""}`}
|
||||
onClick={() => handleItemClick("面试初体验")}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<p>
|
||||
求职面试初体验
|
||||
面试初体验
|
||||
<span style={{
|
||||
marginLeft: '8px',
|
||||
padding: '2px 8px',
|
||||
|
||||
@@ -4,7 +4,7 @@ import InterviewRating from "./components/InterviewRating";
|
||||
import "./index.css";
|
||||
|
||||
const InterviewSimulationPage = () => {
|
||||
const [selectedItem, setSelectedItem] = useState("求职面试初体验");
|
||||
const [selectedItem, setSelectedItem] = useState("面试初体验");
|
||||
|
||||
return (
|
||||
<div className="interview-simulation-page">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Modal, Message } from "@arco-design/web-react";
|
||||
import { Modal, Message, Tooltip } from "@arco-design/web-react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import {
|
||||
DndContext,
|
||||
@@ -344,9 +344,51 @@ export default ({ locked = false }) => {
|
||||
<div className="target-position-wrapper">
|
||||
<div className="target-position-content">
|
||||
<div className="batch-icon">
|
||||
<span>第一批次</span>
|
||||
<span>第二批次</span>
|
||||
<span>第三批次</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第一批次
|
||||
<Tooltip content="通过培训后能直接上岗的岗位,入职成功率最高。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第二批次
|
||||
<Tooltip content="需积累一定工作经验后可争取的晋升岗位方向。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第三批次
|
||||
<Tooltip content="需长期经验和能力沉淀,可作为学员的终极职业目标。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 第一批次 */}
|
||||
|
||||
@@ -41,7 +41,7 @@ const JobStrategyDetailPage = () => {
|
||||
onClick={() => setActiveItem("1")}
|
||||
>
|
||||
<span className="nav-icon target-icon"></span>
|
||||
<span className="nav-text">优先目标岗位</span>
|
||||
<span className="nav-text">目标岗位优先级</span>
|
||||
</div>
|
||||
<div
|
||||
className={`nav-item ${activeItem === "2" ? "item-active" : ""}`}
|
||||
|
||||
@@ -7,7 +7,7 @@ const JobStrategyPage = () => {
|
||||
<div className="job-strategy-page">
|
||||
<CoursesVideoPlayer
|
||||
isLock
|
||||
backgroundImage="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuW8VeXQDVI2.jpg"
|
||||
backgroundImage="https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/public_bg/recuWolCCOlOCz.jpg"
|
||||
/>
|
||||
<LiveSummary showBtn />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import CoursesVideoPlayer from "@/components/CoursesVideoPlayer";
|
||||
import CourseList from "@/components/CourseList";
|
||||
import { mockData } from "@/data/mockData";
|
||||
@@ -6,6 +7,29 @@ import "./index.css";
|
||||
|
||||
const LivePage = () => {
|
||||
const [selectedCourse, setSelectedCourse] = useState(null);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// 检查URL参数,如果有courseId或courseTitle则自动打开对应课程
|
||||
useEffect(() => {
|
||||
const courseId = searchParams.get('courseId');
|
||||
const courseTitle = searchParams.get('courseTitle');
|
||||
|
||||
if (courseId || courseTitle) {
|
||||
// 查找对应的课程
|
||||
const allCourses = [
|
||||
...(mockData.compoundSkillCourses || []),
|
||||
...(mockData.verticalSkillCourses || [])
|
||||
];
|
||||
|
||||
const targetCourse = allCourses.find(course =>
|
||||
course.id === courseId || course.title === courseTitle
|
||||
);
|
||||
|
||||
if (targetCourse) {
|
||||
setSelectedCourse(targetCourse);
|
||||
}
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleCourseClick = (course) => {
|
||||
setSelectedCourse(course);
|
||||
|
||||
@@ -18,24 +18,6 @@ const Portfolio = () => {
|
||||
|
||||
return (
|
||||
<div className="user-portfolio-page">
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "10px",
|
||||
right: "10px",
|
||||
zIndex: 1000,
|
||||
padding: "8px 16px",
|
||||
backgroundColor: "#3491fa",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
>
|
||||
刷新页面
|
||||
</button>
|
||||
{iframeSrc && (
|
||||
<iframe
|
||||
key={iframeSrc} // 使用key强制重新渲染iframe
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
width: 210px;
|
||||
height: 82px;
|
||||
background-color: #f7f8fa;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
@@ -137,6 +138,13 @@
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f3f5;
|
||||
border-color: #4080ff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
> span {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Modal from "@/components/Modal";
|
||||
import PDFICON from "@/assets/images/Common/pdf_icon.png";
|
||||
import FileIcon from "@/components/FileIcon";
|
||||
@@ -7,6 +8,7 @@ import ImagePreviewModal from "../ImagePreviewModal";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ visible, onClose, data }) => {
|
||||
const navigate = useNavigate();
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewIndex, setPreviewIndex] = useState(0);
|
||||
|
||||
@@ -14,6 +16,14 @@ export default ({ visible, onClose, data }) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 处理岗位点击,跳转到简历与面试题页面
|
||||
const handlePositionClick = (positionName) => {
|
||||
// 关闭当前模态框
|
||||
onClose();
|
||||
// 跳转到简历与面试题页面,并传递岗位名称参数
|
||||
navigate('/resume-interview', { state: { selectedPosition: positionName } });
|
||||
};
|
||||
|
||||
// 处理图片点击预览
|
||||
const handleImageClick = (index) => {
|
||||
setPreviewIndex(index);
|
||||
@@ -91,7 +101,12 @@ export default ({ visible, onClose, data }) => {
|
||||
<p className="project-cases-modal-item-title">适用岗位</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
{data?.applicablePositions?.map((pos, index) => (
|
||||
<li key={index} className="high-count-list-item">
|
||||
<li
|
||||
key={index}
|
||||
className="high-count-list-item"
|
||||
onClick={() => handlePositionClick(pos.position)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<span className={pos.level === '普通岗' ? 'low' : pos.level === '技术骨干岗' ? 'medium' : 'high'}>
|
||||
{pos.level}
|
||||
</span>
|
||||
@@ -108,23 +123,72 @@ export default ({ visible, onClose, data }) => {
|
||||
{/* 对应单元 */}
|
||||
<li className="project-cases-modal-item">
|
||||
<p className="project-cases-modal-item-title">对应单元</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
{data?.units?.map((unit, index) => (
|
||||
<li key={index} className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>{unit}</span>
|
||||
</div>
|
||||
</li>
|
||||
)) || (
|
||||
<li className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>暂无对应单元信息</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{/* 复合能力课 */}
|
||||
<div className="unit-category-section">
|
||||
<p className="unit-category-subtitle">复合能力课</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
{data?.units?.filter(unit =>
|
||||
unit.includes('商业活动') ||
|
||||
unit.includes('营销') ||
|
||||
unit.includes('品牌') ||
|
||||
unit.includes('项目全周期')
|
||||
).map((unit, index) => (
|
||||
<li key={`compound-${index}`} className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>{unit}</span>
|
||||
</div>
|
||||
</li>
|
||||
)) || null}
|
||||
{(!data?.units || data?.units?.filter(unit =>
|
||||
unit.includes('商业活动') ||
|
||||
unit.includes('营销') ||
|
||||
unit.includes('品牌') ||
|
||||
unit.includes('项目全周期')
|
||||
).length === 0) && (
|
||||
<li className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>暂无复合能力课信息</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 垂直能力课 */}
|
||||
<div className="unit-category-section">
|
||||
<p className="unit-category-subtitle">垂直能力课</p>
|
||||
<ul className="project-cases-modal-horizontal-list">
|
||||
{data?.units?.filter(unit =>
|
||||
!unit.includes('商业活动') &&
|
||||
!unit.includes('营销') &&
|
||||
!unit.includes('品牌') &&
|
||||
!unit.includes('项目全周期')
|
||||
).map((unit, index) => (
|
||||
<li key={`vertical-${index}`} className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>{unit}</span>
|
||||
</div>
|
||||
</li>
|
||||
)) || null}
|
||||
{(!data?.units || data?.units?.filter(unit =>
|
||||
!unit.includes('商业活动') &&
|
||||
!unit.includes('营销') &&
|
||||
!unit.includes('品牌') &&
|
||||
!unit.includes('项目全周期')
|
||||
).length === 0) && (
|
||||
<li className="class-list-item">
|
||||
<div className="class-list-item-title">
|
||||
<i />
|
||||
<span>暂无垂直能力课信息</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{/* 项目整体流程介绍 - Markdown格式 */}
|
||||
<li className="project-cases-modal-item">
|
||||
|
||||
301
src/pages/ProjectLibraryPage/components/UploadModal/index.css
Normal file
301
src/pages/ProjectLibraryPage/components/UploadModal/index.css
Normal file
@@ -0,0 +1,301 @@
|
||||
/*/* 全局样式控制弹窗容器 */
|
||||
.upload-modal.arco-modal-wrapper {
|
||||
.arco-modal {
|
||||
width: 720px !important;
|
||||
max-width: 90vw;
|
||||
}
|
||||
}
|
||||
|
||||
/* 强制控制 Arco Modal 外层容器为横向矩形 */
|
||||
body {
|
||||
[data-focus-lock-disabled="false"] {
|
||||
.arco-modal-wrapper {
|
||||
.arco-modal {
|
||||
width: 960px !important;
|
||||
max-width: 90vw;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*/* 针对上传弹窗的特定样式 */
|
||||
.arco-modal-wrapper:has(.upload-modal-content) {
|
||||
.arco-modal {
|
||||
width: 720px !important;
|
||||
height: auto !important;
|
||||
max-width: 90vw;
|
||||
}
|
||||
}
|
||||
.upload-modal {
|
||||
.arco-modal {
|
||||
/* 横向矩形布局 - 16:9 比例 */
|
||||
width: 720px !important;
|
||||
.arco-modal-content {
|
||||
height: 420px;
|
||||
max-height: 420px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-modal-header {
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
padding: 20px 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.arco-modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.arco-modal-body {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-modal-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.upload-header {
|
||||
padding: 24px 24px 20px;
|
||||
background: linear-gradient(180deg, #f7f8fa 0%, #ffffff 100%);
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #86909c;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
margin: 20px 24px;
|
||||
flex: 1;
|
||||
max-height: 150px;
|
||||
min-height: 120px;
|
||||
border: 2px dashed #e5e6eb;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(to bottom, #fafafa, #f5f5f5);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg,
|
||||
transparent 48%,
|
||||
rgba(22, 93, 255, 0.03) 49%,
|
||||
rgba(22, 93, 255, 0.03) 51%,
|
||||
transparent 52%
|
||||
);
|
||||
background-size: 20px 20px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #165dff;
|
||||
background-color: #f0f7ff;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
color: #165dff;
|
||||
}
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
border-color: #165dff;
|
||||
background-color: #e8f4ff;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-file {
|
||||
border-color: #00b42a;
|
||||
background-color: #ffffff;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.upload-icon {
|
||||
font-size: 48px;
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 16px;
|
||||
color: #4e5969;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
font-size: 14px;
|
||||
color: #86909c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.uploaded-file-display {
|
||||
width: 100%;
|
||||
padding: 15px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.file-icon-wrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.file-icon {
|
||||
font-size: 24px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.file-name {
|
||||
font-size: 15px;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
|
||||
.file-size {
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.file-status {
|
||||
color: #00b42a;
|
||||
padding: 2px 8px;
|
||||
background: #f0ffef;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remove-file-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: #f2f3f5;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #86909c;
|
||||
|
||||
&:hover {
|
||||
background: #ff4d4f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arco-modal-footer {
|
||||
border-top: 1px solid #e5e6eb;
|
||||
padding: 16px 24px;
|
||||
flex-shrink: 0;
|
||||
background: linear-gradient(to bottom, #ffffff, #fafafa);
|
||||
|
||||
.arco-btn {
|
||||
min-width: 100px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.arco-btn-default {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e6eb;
|
||||
color: #4e5969;
|
||||
|
||||
&:hover {
|
||||
background: #f7f8fa;
|
||||
border-color: #c9cdd4;
|
||||
color: #1d2129;
|
||||
}
|
||||
}
|
||||
|
||||
&.arco-btn-primary {
|
||||
background: linear-gradient(135deg, #165dff 0%, #3c7eff 100%);
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(22, 93, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, #1450db 0%, #3673ff 100%);
|
||||
box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 6px rgba(22, 93, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
src/pages/ProjectLibraryPage/components/UploadModal/index.jsx
Normal file
161
src/pages/ProjectLibraryPage/components/UploadModal/index.jsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { Modal } from "@arco-design/web-react";
|
||||
import { IconUpload, IconFile, IconDelete } from "@arco-design/web-react/icon";
|
||||
import toast from "@/components/Toast";
|
||||
import "./index.css";
|
||||
|
||||
const UploadModal = ({ visible, onClose }) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [uploadedFile, setUploadedFile] = useState(null);
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
handleFileSelect(files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (file) => {
|
||||
// 验证文件类型
|
||||
const allowedTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain',
|
||||
'text/markdown',
|
||||
'text/x-markdown'
|
||||
];
|
||||
|
||||
const allowedExtensions = ['.pdf', '.doc', '.docx', '.txt', '.md', '.markdown'];
|
||||
const fileExtension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0];
|
||||
|
||||
if (!allowedTypes.includes(file.type) && !allowedExtensions.includes(fileExtension)) {
|
||||
toast.error("请上传 PDF、Word、Markdown 或 TXT 格式的文件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证文件大小(限制为10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
toast.error("文件大小不能超过10MB");
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadedFile(file);
|
||||
toast.success(`文件 ${file.name} 已选择`);
|
||||
};
|
||||
|
||||
const handleFileInputChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
handleFileSelect(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!uploadedFile) {
|
||||
toast.error("请先选择要上传的文件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里可以添加实际的上传逻辑
|
||||
toast.success("文件上传成功");
|
||||
setUploadedFile(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setUploadedFile(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={false}
|
||||
visible={visible}
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
okText="确认上传"
|
||||
cancelText="取消"
|
||||
style={{ width: '720px' }}
|
||||
width={720}
|
||||
className="upload-modal"
|
||||
width={960}
|
||||
maskClosable={false}
|
||||
>
|
||||
<div className="upload-modal-content">
|
||||
<div className="upload-header">
|
||||
<h3>选择文件上传</h3>
|
||||
<p>请选择您的项目文档进行上传,支持批量上传多个文件</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`upload-area ${isDragging ? 'dragging' : ''} ${uploadedFile ? 'has-file' : ''}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={handleUploadClick}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".pdf,.doc,.docx,.txt,.md,.markdown"
|
||||
onChange={handleFileInputChange}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
|
||||
{uploadedFile ? (
|
||||
<div className="uploaded-file-display">
|
||||
<div className="file-icon-wrapper">
|
||||
<IconFile className="file-icon" />
|
||||
</div>
|
||||
<div className="file-info">
|
||||
<p className="file-name">{uploadedFile.name}</p>
|
||||
<p className="file-meta">
|
||||
<span className="file-size">
|
||||
{(uploadedFile.size / 1024 / 1024).toFixed(2)} MB
|
||||
</span>
|
||||
<span className="file-status">准备上传</span>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="remove-file-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setUploadedFile(null);
|
||||
}}
|
||||
>
|
||||
<IconDelete />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="upload-placeholder">
|
||||
<IconUpload className="upload-icon" />
|
||||
<p className="upload-text">点击选择文件或将文件拖拽至此处</p>
|
||||
<p className="upload-hint">支持 PDF、Word、Markdown、TXT 格式,单个文件不超过 10MB</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadModal;
|
||||
@@ -14,18 +14,50 @@
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
.project-library-title {
|
||||
/* 项目库头部样式 */
|
||||
.project-library-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.upload-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f7f8fa;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 6px;
|
||||
color: #86909c;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
svg {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #e8f4ff;
|
||||
border-color: #165dff;
|
||||
color: #165dff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-library-title {
|
||||
width: auto;
|
||||
height: 42px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
margin-bottom: 20px;
|
||||
color: #1d2129;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
padding-bottom: 10px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
@@ -40,6 +72,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 项目分类导航栏样式 */
|
||||
.project-category-nav {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e5e6eb;
|
||||
padding-bottom: 15px;
|
||||
|
||||
.category-item {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
background-color: #f7f8fa;
|
||||
color: #86909c;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: #e8f4ff;
|
||||
color: #165dff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #165dff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-library-search-area {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
@@ -157,8 +220,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.my-project-library {
|
||||
|
||||
.my-project-library {
|
||||
margin-top: 20px;
|
||||
|
||||
.project-library-empty {
|
||||
@@ -237,7 +300,33 @@
|
||||
color: #1d4ed8 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局控制上传弹窗的外层容器为横向矩形 */
|
||||
:global([data-focus-lock-disabled="false"]) {
|
||||
:global(.arco-modal-wrapper) {
|
||||
:global(.arco-modal) {
|
||||
width: 720px !important;
|
||||
max-width: 90vw !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保上传弹窗是横向矩形 */
|
||||
:global(.arco-modal-wrapper) {
|
||||
&:has(.upload-modal-content) {
|
||||
:global(.arco-modal) {
|
||||
width: 720px !important;
|
||||
max-width: 90vw !important;
|
||||
height: auto !important;
|
||||
|
||||
:global(.arco-modal-content) {
|
||||
height: 420px !important;
|
||||
max-height: 420px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import { Tooltip } from "@arco-design/web-react";
|
||||
import toast from "@/components/Toast";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll";
|
||||
import ProjectCasesModal from "./components/ProjectCasesModal";
|
||||
import UploadModal from "./components/UploadModal";
|
||||
import { getProjectsList, getProjectsdetail } from "@/services/projectLibrary";
|
||||
import { IconUpload } from "@arco-design/web-react/icon";
|
||||
// 我的项目库数据
|
||||
const myProjectsData = [
|
||||
{
|
||||
@@ -91,7 +93,7 @@ import "./index.css";
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const ProjectLibrary = () => {
|
||||
const ProjectLibraryPage = () => {
|
||||
// 处理我的项目数据
|
||||
const processMyProjects = () => {
|
||||
const projects = [];
|
||||
@@ -201,8 +203,19 @@ const ProjectLibrary = () => {
|
||||
useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [selectedCategory, setSelectedCategory] = useState("全部");
|
||||
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
||||
|
||||
// 项目分类(基于数据中的direction字段)
|
||||
const categories = ["全部", "项目经营管理", "商业活动策划", "文化服务"];
|
||||
|
||||
// 根据分类过滤项目
|
||||
const filteredProjects = useMemo(() => {
|
||||
if (selectedCategory === "全部") {
|
||||
return projectList;
|
||||
}
|
||||
return projectList.filter(project => project.direction === selectedCategory);
|
||||
}, [selectedCategory, projectList]);
|
||||
|
||||
const handleProjectClick = async (item) => {
|
||||
// 如果是我的普通项目,不允许点击
|
||||
@@ -261,13 +274,26 @@ const ProjectLibrary = () => {
|
||||
<div className="project-library-page">
|
||||
<div className="project-library-wrapper">
|
||||
<p className="project-library-title">文旅班级项目库</p>
|
||||
|
||||
{/* 项目分类导航栏 */}
|
||||
<div className="project-category-nav">
|
||||
{categories.map((category) => (
|
||||
<span
|
||||
key={category}
|
||||
className={`category-item ${selectedCategory === category ? 'active' : ''}`}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
>
|
||||
{category}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<InfiniteScroll
|
||||
loadMore={fetchProjects}
|
||||
hasMore={hasMore}
|
||||
empty={projectList.length === 0}
|
||||
className="project-library-list"
|
||||
>
|
||||
{projectList.map((item) => (
|
||||
{filteredProjects.map((item) => (
|
||||
<li className="project-library-item" key={item.id}>
|
||||
<p className="project-library-item-title">{item.description}</p>
|
||||
<div>
|
||||
@@ -281,7 +307,16 @@ const ProjectLibrary = () => {
|
||||
|
||||
{/* 我的项目库板块 */}
|
||||
<div className="project-library-wrapper my-project-library">
|
||||
<p className="project-library-title">我完成的项目库</p>
|
||||
<div className="project-library-header">
|
||||
<p className="project-library-title">我完成的项目库</p>
|
||||
<button
|
||||
className="upload-button"
|
||||
onClick={() => setUploadModalVisible(true)}
|
||||
>
|
||||
<IconUpload />
|
||||
<span>上传</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="project-library-list">
|
||||
{/* 可点击的特殊项目 */}
|
||||
{clickableProjects.map((item) => (
|
||||
@@ -322,8 +357,13 @@ const ProjectLibrary = () => {
|
||||
visible={projectCasesModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
/>
|
||||
|
||||
<UploadModal
|
||||
visible={uploadModalVisible}
|
||||
onClose={() => setUploadModalVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectLibrary;
|
||||
export default ProjectLibraryPage;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import CoursesVideoPlayer from "@/components/CoursesVideoPlayer";
|
||||
import PublicCourseList from "@/components/PublicCourseList";
|
||||
import { mockData } from "@/data/mockData";
|
||||
@@ -7,6 +8,25 @@ import "./index.css";
|
||||
const PublicCourses = () => {
|
||||
// 默认不选中任何课程,显示黑屏状态
|
||||
const [selectedCourse, setSelectedCourse] = useState(null);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// 检查URL参数,如果有courseId则自动打开对应课程
|
||||
useEffect(() => {
|
||||
const courseId = searchParams.get('courseId');
|
||||
const courseTitle = searchParams.get('courseTitle');
|
||||
|
||||
if (courseId || courseTitle) {
|
||||
// 查找对应的课程
|
||||
const publicCourses = mockData.publicCourses || [];
|
||||
const targetCourse = publicCourses.find(course =>
|
||||
course.id === courseId || course.title === courseTitle
|
||||
);
|
||||
|
||||
if (targetCourse) {
|
||||
setSelectedCourse(targetCourse);
|
||||
}
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleCourseClick = (course) => {
|
||||
setSelectedCourse(course);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Spin, Empty } from "@arco-design/web-react";
|
||||
import toast from "@/components/Toast";
|
||||
import InterviewQuestionsModal from "./components/InterviewQuestionsModal";
|
||||
@@ -14,6 +15,7 @@ import QuestionIcon from "@/assets/images/ResumeInterviewPage/question_icon2.png
|
||||
import "./index.css";
|
||||
|
||||
const ResumeInterviewPage = () => {
|
||||
const location = useLocation();
|
||||
const [activeIndustry, setActiveIndustry] = useState("frontend");
|
||||
const [interviewModalVisible, setInterviewModalVisible] = useState(false);
|
||||
const [resumeModalVisible, setResumeModalVisible] = useState(false);
|
||||
@@ -117,6 +119,7 @@ const ResumeInterviewPage = () => {
|
||||
title: position.title,
|
||||
content: selectedTemplate?.content || selectedTemplate?.oldContent || null,
|
||||
studentResume: pageData.myResume,
|
||||
selectedTemplate: selectedTemplate, // 添加整个模板数据,包括studentInfo
|
||||
};
|
||||
|
||||
setResumeModalData(resumeData);
|
||||
@@ -161,6 +164,31 @@ const ResumeInterviewPage = () => {
|
||||
fetchPageData();
|
||||
}, []);
|
||||
|
||||
// 处理从项目库页面传递过来的岗位参数
|
||||
useEffect(() => {
|
||||
if (location.state?.selectedPosition && pageData) {
|
||||
const positionName = location.state.selectedPosition;
|
||||
|
||||
// 遍历所有行业查找对应的岗位
|
||||
for (const industry of pageData.industries) {
|
||||
const position = industry.positions?.find(p => p.title === positionName);
|
||||
if (position) {
|
||||
// 找到岗位后,自动打开简历详情
|
||||
handlePositionClick(position, industry);
|
||||
// 滚动到对应的行业区域
|
||||
setActiveIndustry(industry.id);
|
||||
setTimeout(() => {
|
||||
sectionsRef.current[industry.id]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}, 100);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [location.state, pageData]);
|
||||
|
||||
// 监听滚动位置更新导航状态
|
||||
useEffect(() => {
|
||||
if (!pageData?.industries) return;
|
||||
|
||||
Reference in New Issue
Block a user