feat: 实现日历课程点击跳转到直播间功能
- 添加日历课程详情弹窗的点击跳转功能 - 公共课直播间和课程直播间支持URL参数自动选中课程 - 优化岗位详情页面样式,复用简洁卡片样式 - 为岗位详情标题添加图标 - 调整不同类型课程的跳转逻辑 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user