蒋大力 | 35f939b | 2025-06-08 13:52:07 +0800 | [diff] [blame] | 1 | import React, { useState } from 'react' |
| 2 | import { |
| 3 | Home, |
| 4 | Settings, |
| 5 | BarChart3, |
| 6 | PieChart, |
| 7 | TrendingUp, |
| 8 | Activity, |
| 9 | BookOpen, |
| 10 | Users, |
| 11 | Upload, |
| 12 | Image, |
| 13 | Video, |
| 14 | ChevronDown, |
| 15 | User |
| 16 | } from 'lucide-react' |
| 17 | import './App.css' |
| 18 | |
| 19 | function App() { |
| 20 | const [activeTab, setActiveTab] = useState('image') |
| 21 | const [expandedMenu, setExpandedMenu] = useState('dashboard') |
| 22 | const [activePage, setActivePage] = useState('dashboard') // 新增:当前激活的页面 |
| 23 | const [isDragOver, setIsDragOver] = useState(false) |
| 24 | const [isUploading, setIsUploading] = useState(false) |
| 25 | const [uploadedFiles, setUploadedFiles] = useState([]) |
| 26 | const [uploadProgress, setUploadProgress] = useState(0) |
| 27 | |
| 28 | const validateFiles = (files) => { |
| 29 | const validImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'] |
| 30 | const validVideoTypes = ['video/mp4', 'video/mov', 'video/avi'] |
| 31 | |
| 32 | const validTypes = activeTab === 'video' ? validVideoTypes : validImageTypes |
| 33 | const maxSize = activeTab === 'video' ? 2 * 1024 * 1024 * 1024 : 32 * 1024 * 1024 // 2GB for video, 32MB for images |
| 34 | |
| 35 | const invalidFiles = files.filter(file => { |
| 36 | return !validTypes.includes(file.type) || file.size > maxSize |
| 37 | }) |
| 38 | |
| 39 | if (invalidFiles.length > 0) { |
| 40 | alert(`发现 ${invalidFiles.length} 个无效文件,请检查文件格式和大小`) |
| 41 | return false |
| 42 | } |
| 43 | |
| 44 | return true |
| 45 | } |
| 46 | |
| 47 | const simulateUpload = (files) => { |
| 48 | setIsUploading(true) |
| 49 | setUploadProgress(0) |
| 50 | setUploadedFiles(files) |
| 51 | |
| 52 | // 模拟上传进度 |
| 53 | const interval = setInterval(() => { |
| 54 | setUploadProgress(prev => { |
| 55 | if (prev >= 100) { |
| 56 | clearInterval(interval) |
| 57 | setIsUploading(false) |
| 58 | alert(`成功上传了 ${files.length} 个文件`) |
| 59 | return 100 |
| 60 | } |
| 61 | return prev + 10 |
| 62 | }) |
| 63 | }, 200) |
| 64 | } |
| 65 | |
| 66 | const handleFileUpload = () => { |
| 67 | if (isUploading) return |
| 68 | |
| 69 | const input = document.createElement('input') |
| 70 | input.type = 'file' |
| 71 | input.accept = activeTab === 'video' ? 'video/*' : 'image/*' |
| 72 | input.multiple = activeTab === 'image' |
| 73 | input.onchange = (e) => { |
| 74 | const files = Array.from(e.target.files) |
| 75 | if (files.length > 0 && validateFiles(files)) { |
| 76 | simulateUpload(files) |
| 77 | } |
| 78 | } |
| 79 | input.click() |
| 80 | } |
| 81 | |
| 82 | const handleDragOver = (e) => { |
| 83 | e.preventDefault() |
| 84 | e.stopPropagation() |
| 85 | setIsDragOver(true) |
| 86 | } |
| 87 | |
| 88 | const handleDragLeave = (e) => { |
| 89 | e.preventDefault() |
| 90 | e.stopPropagation() |
| 91 | setIsDragOver(false) |
| 92 | } |
| 93 | |
| 94 | const handleDrop = (e) => { |
| 95 | e.preventDefault() |
| 96 | e.stopPropagation() |
| 97 | setIsDragOver(false) |
| 98 | |
| 99 | if (isUploading) return |
| 100 | |
| 101 | const files = Array.from(e.dataTransfer.files) |
| 102 | if (files.length > 0 && validateFiles(files)) { |
| 103 | simulateUpload(files) |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | const clearUploadedFiles = () => { |
| 108 | setUploadedFiles([]) |
| 109 | } |
| 110 | |
| 111 | const removeFile = (indexToRemove) => { |
| 112 | setUploadedFiles(prev => prev.filter((_, index) => index !== indexToRemove)) |
| 113 | } |
| 114 | |
| 115 | const menuItems = [ |
| 116 | { id: 'home', label: '首页', icon: Home }, |
| 117 | { id: 'notebooks', label: '笔记管理', icon: BookOpen }, |
| 118 | { |
| 119 | id: 'dashboard', |
| 120 | label: '数据看板', |
| 121 | icon: BarChart3, |
| 122 | submenu: [ |
| 123 | { id: 'overview', label: '账号概况' }, |
| 124 | { id: 'content', label: '内容分析' }, |
| 125 | { id: 'fans', label: '粉丝数据' } |
| 126 | ] |
| 127 | }, |
| 128 | { id: 'activity', label: '活动中心', icon: Activity }, |
| 129 | { id: 'notes', label: '笔记灵感', icon: BookOpen }, |
| 130 | { id: 'creator', label: '创作学院', icon: Users }, |
| 131 | { id: 'journal', label: '创作日刊', icon: BookOpen } |
| 132 | ] |
| 133 | const toggleMenu = (menuId) => { |
| 134 | setExpandedMenu(expandedMenu === menuId ? null : menuId) |
| 135 | } |
| 136 | |
| 137 | // 新增:处理页面切换的函数 |
| 138 | const handlePageChange = (pageId) => { |
| 139 | setActivePage(pageId) |
| 140 | // 如果点击的是有子菜单的项目,也要展开子菜单 |
| 141 | const menuItem = menuItems.find(item => item.id === pageId) |
| 142 | if (menuItem && menuItem.submenu) { |
| 143 | setExpandedMenu(pageId) |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | return ( |
| 148 | <div className="app"> |
| 149 | {/* Header */} |
| 150 | <header className="header"> |
| 151 | <div className="header-left"> |
| 152 | <div className="logo">小红书</div> |
| 153 | <h1 className="header-title">创作服务平台</h1> |
| 154 | </div> |
| 155 | <div className="header-right"> |
| 156 | <div className="user-info"> |
| 157 | <User size={16} /> |
| 158 | <span>小红薯63081EA1</span> |
| 159 | </div> |
| 160 | </div> |
| 161 | </header> |
| 162 | |
| 163 | {/* Sidebar */} |
| 164 | <aside className="sidebar"> |
| 165 | <button className="publish-btn">发布笔记</button> |
| 166 | <nav className="nav-menu"> |
| 167 | {menuItems.map((item) => ( |
| 168 | <div key={item.id} className="nav-item"> |
| 169 | <a |
| 170 | href="#" |
| 171 | className={`nav-link ${activePage === item.id ? 'active' : ''}`} |
| 172 | onClick={(e) => { |
| 173 | e.preventDefault() |
| 174 | if (item.submenu) { |
| 175 | toggleMenu(item.id) |
| 176 | } else { |
| 177 | handlePageChange(item.id) |
| 178 | } |
| 179 | }} |
| 180 | > |
| 181 | <item.icon size={16} /> |
| 182 | <span>{item.label}</span> |
| 183 | {item.submenu && <ChevronDown size={16} style={{ marginLeft: 'auto', transform: expandedMenu === item.id ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.3s ease' }} />} |
| 184 | </a> |
| 185 | |
| 186 | {item.submenu && expandedMenu === item.id && ( |
| 187 | <div className="nav-submenu"> |
| 188 | {item.submenu.map((subItem) => ( |
| 189 | <a |
| 190 | key={subItem.id} |
| 191 | href="#" |
| 192 | className={`nav-link ${activePage === subItem.id ? 'active' : ''}`} |
| 193 | onClick={(e) => { |
| 194 | e.preventDefault() |
| 195 | handlePageChange(subItem.id) |
| 196 | }} |
| 197 | > |
| 198 | {subItem.label} |
| 199 | </a> |
| 200 | ))} |
| 201 | </div> |
| 202 | )} |
| 203 | </div> |
| 204 | ))} |
| 205 | </nav> |
| 206 | </aside> {/* Main Content */} |
| 207 | <main className="main-content"> |
| 208 | <div className="content-wrapper"> |
| 209 | {activePage === 'dashboard' || activePage === 'overview' || activePage === 'content' || activePage === 'fans' ? ( |
| 210 | // 上传页面内容(数据看板相关页面显示上传功能) |
| 211 | <> |
| 212 | {/* Upload Tabs */} |
| 213 | <div className="upload-tabs"> |
| 214 | <button |
| 215 | className={`upload-tab ${activeTab === 'video' ? 'active' : ''}`} |
| 216 | onClick={() => setActiveTab('video')} |
| 217 | > |
| 218 | 上传视频 |
| 219 | </button> |
| 220 | <button |
| 221 | className={`upload-tab ${activeTab === 'image' ? 'active' : ''}`} |
| 222 | onClick={() => setActiveTab('image')} |
| 223 | > |
| 224 | 上传图文 |
| 225 | </button> |
| 226 | </div> |
| 227 | |
| 228 | {/* Upload Area */} |
| 229 | <div |
| 230 | className={`upload-area ${isDragOver ? 'drag-over' : ''}`} |
| 231 | onDragOver={handleDragOver} |
| 232 | onDragLeave={handleDragLeave} |
| 233 | onDrop={handleDrop} |
| 234 | > |
| 235 | <div className="upload-icon"> |
| 236 | {activeTab === 'video' ? <Video /> : <Image />} |
| 237 | </div> |
| 238 | <h2 className="upload-title"> |
| 239 | {activeTab === 'video' ? '拖拽视频到此处或点击上传' : '拖拽图片到此处或点击上传'} |
| 240 | </h2> |
| 241 | <p className="upload-subtitle"> |
| 242 | {activeTab === 'video' ? '(需支持上传格式)' : '(需支持上传格式)'} |
| 243 | </p> |
| 244 | <button |
| 245 | className={`upload-btn ${isUploading ? 'uploading' : ''}`} |
| 246 | onClick={handleFileUpload} |
| 247 | disabled={isUploading} |
| 248 | > |
| 249 | {isUploading ? `上传中... ${uploadProgress}%` : (activeTab === 'video' ? '上传视频' : '上传图片')} |
| 250 | </button> |
| 251 | |
| 252 | {/* Upload Progress Bar */} |
| 253 | {isUploading && ( |
| 254 | <div className="progress-container"> |
| 255 | <div className="progress-bar"> |
| 256 | <div |
| 257 | className="progress-fill" |
| 258 | style={{ width: `${uploadProgress}%` }} |
| 259 | ></div> |
| 260 | </div> |
| 261 | <div className="progress-text">{uploadProgress}%</div> |
| 262 | </div> |
| 263 | )} |
| 264 | </div> |
| 265 | |
| 266 | {/* File Preview Area */} |
| 267 | {uploadedFiles.length > 0 && ( |
| 268 | <div className="file-preview-area"> |
| 269 | <div className="preview-header"> |
| 270 | <h3 className="preview-title">已上传文件 ({uploadedFiles.length})</h3> |
| 271 | <button className="clear-files-btn" onClick={clearUploadedFiles}> |
| 272 | 清除所有 |
| 273 | </button> |
| 274 | </div> |
| 275 | <div className="file-grid"> |
| 276 | {uploadedFiles.map((file, index) => ( |
| 277 | <div key={index} className="file-item"> |
| 278 | <button |
| 279 | className="remove-file-btn" |
| 280 | onClick={() => removeFile(index)} |
| 281 | title="删除文件" |
| 282 | > |
| 283 | × |
| 284 | </button> |
| 285 | {file.type?.startsWith('image/') ? ( |
| 286 | <div className="file-thumbnail"> |
| 287 | <img src={URL.createObjectURL(file)} alt={file.name} /> |
| 288 | </div> |
| 289 | ) : ( |
| 290 | <div className="file-thumbnail video-thumbnail"> |
| 291 | <Video size={24} /> |
| 292 | </div> |
| 293 | )} |
| 294 | <div className="file-info"> |
| 295 | <div className="file-name" title={file.name}> |
| 296 | {file.name.length > 20 ? file.name.substring(0, 17) + '...' : file.name} |
| 297 | </div> |
| 298 | <div className="file-size"> |
| 299 | {(file.size / 1024 / 1024).toFixed(2)} MB |
| 300 | </div> |
| 301 | </div> |
| 302 | </div> |
| 303 | ))} |
| 304 | </div> |
| 305 | </div> |
| 306 | )} |
| 307 | |
| 308 | {/* Upload Info */} |
| 309 | <div className="upload-info fade-in" key={activeTab}> |
| 310 | {activeTab === 'image' ? ( |
| 311 | <> |
| 312 | <div className="info-item"> |
| 313 | <h3 className="info-title">图片大小</h3> |
| 314 | <p className="info-desc"> |
| 315 | 支持上传的图片大小,<br /> |
| 316 | 最大32MB的图片文件 |
| 317 | </p> |
| 318 | </div> |
| 319 | <div className="info-item"> |
| 320 | <h3 className="info-title">图片格式</h3> |
| 321 | <p className="info-desc"> |
| 322 | 支持上传的图片格式:<br /> |
| 323 | 推荐使用png、jpg、jpeg、webp,不支持gif、live及其他转化的动图 |
| 324 | </p> |
| 325 | </div> |
| 326 | <div className="info-item"> |
| 327 | <h3 className="info-title">图片分辨率</h3> |
| 328 | <p className="info-desc"> |
| 329 | 不要竖图片尺寸,推荐上传3:4尺寸之间,分辨率不低于720*960的图片,<br /> |
| 330 | 超过17张的时候图片将自动压缩至相配尺寸 |
| 331 | </p> |
| 332 | </div> |
| 333 | </> |
| 334 | ) : ( |
| 335 | <> |
| 336 | <div className="info-item"> |
| 337 | <h3 className="info-title">视频大小</h3> |
| 338 | <p className="info-desc"> |
| 339 | 支持种类5分钟内视频,<br /> |
| 340 | 最大2GB的视频文件 |
| 341 | </p> |
| 342 | </div> |
| 343 | <div className="info-item"> |
| 344 | <h3 className="info-title">视频格式</h3> |
| 345 | <p className="info-desc"> |
| 346 | 支持常用视频格式:<br /> |
| 347 | 推荐使用mp4、mov |
| 348 | </p> |
| 349 | </div> |
| 350 | <div className="info-item"> |
| 351 | <h3 className="info-title">视频分辨率</h3> |
| 352 | <p className="info-desc"> |
| 353 | 推荐上传720P (1280*720) 及以上视频,<br /> |
| 354 | 超过1080P的视频可能可能导致上传稍慢且消耗流量 |
| 355 | </p> |
| 356 | </div> |
| 357 | </> |
| 358 | )} |
| 359 | </div> |
| 360 | </> |
| 361 | ) : ( |
| 362 | // 其他页面的内容 |
| 363 | <div className="page-content"> |
| 364 | <div className="page-header"> |
| 365 | <h1 className="page-title"> |
| 366 | {activePage === 'home' && '首页'} |
| 367 | {activePage === 'notebooks' && '笔记管理'} |
| 368 | {activePage === 'activity' && '活动中心'} |
| 369 | {activePage === 'notes' && '笔记灵感'} |
| 370 | {activePage === 'creator' && '创作学院'} |
| 371 | {activePage === 'journal' && '创作日刊'} |
| 372 | </h1> |
| 373 | </div> |
| 374 | <div className="page-body"> |
| 375 | <div className="placeholder-content"> |
| 376 | <div className="placeholder-icon"> |
| 377 | {activePage === 'home' && <Home size={48} />} |
| 378 | {activePage === 'notebooks' && <BookOpen size={48} />} |
| 379 | {activePage === 'activity' && <Activity size={48} />} |
| 380 | {activePage === 'notes' && <BookOpen size={48} />} |
| 381 | {activePage === 'creator' && <Users size={48} />} |
| 382 | {activePage === 'journal' && <BookOpen size={48} />} |
| 383 | </div> |
| 384 | <h3 className="placeholder-title"> |
| 385 | {activePage === 'home' && '欢迎来到小红书创作平台'} |
| 386 | {activePage === 'notebooks' && '笔记管理功能开发中'} |
| 387 | {activePage === 'activity' && '活动中心功能开发中'} |
| 388 | {activePage === 'notes' && '笔记灵感功能开发中'} |
| 389 | {activePage === 'creator' && '创作学院功能开发中'} |
| 390 | {activePage === 'journal' && '创作日刊功能开发中'} |
| 391 | </h3> |
| 392 | <p className="placeholder-desc"> |
| 393 | {activePage === 'home' && '在这里您可以管理您的创作内容,查看数据分析,获取创作灵感。'} |
| 394 | {activePage === 'notebooks' && '这里将显示您的所有笔记,支持编辑、删除、分类等操作。'} |
| 395 | {activePage === 'activity' && '这里将展示最新的平台活动,让您参与更多有趣的创作活动。'} |
| 396 | {activePage === 'notes' && '这里将为您提供创作灵感和写作建议,帮助您创作更好的内容。'} |
| 397 | {activePage === 'creator' && '这里将提供创作技巧教学和平台规则说明,助您成为优秀创作者。'} |
| 398 | {activePage === 'journal' && '这里将展示创作相关的最新资讯和平台动态。'} |
| 399 | </p> |
| 400 | </div> |
| 401 | </div> |
| 402 | </div> |
| 403 | )} |
| 404 | </div> |
| 405 | </main> |
| 406 | </div> |
| 407 | ) |
| 408 | } |
| 409 | |
| 410 | export default App |