ZBD | ff4d40a | 2025-05-27 17:05:20 +0800 | [diff] [blame^] | 1 | import React, { useState } from 'react'; |
| 2 | import { |
| 3 | Box, Container, Typography, Paper, |
| 4 | TextField, Button, Stack |
| 5 | } from '@mui/material'; |
| 6 | import CloudUploadIcon from '@mui/icons-material/CloudUpload'; |
| 7 | // Assuming your base.css is in a relative path like src/styles/base/base.css |
| 8 | // If your component is in src/components/Upload.js, this path would be ../styles/base/base.css |
| 9 | // Adjust if your project structure is different. |
| 10 | // For this example, I'll assume it's correctly linked. |
| 11 | // import '../styles/base/base.css'; |
| 12 | |
| 13 | function Upload() { |
| 14 | const [title, setTitle] = useState(''); |
| 15 | const [file, setFile] = useState(null); |
| 16 | |
| 17 | const handleUpload = () => { |
| 18 | const formData = new FormData(); |
| 19 | if (file) { |
| 20 | formData.append('file', file); |
| 21 | } |
| 22 | formData.append('title', title); |
| 23 | |
| 24 | // Replace with your actual API endpoint and error handling |
| 25 | fetch('/api/torrents/upload', { |
| 26 | method: 'POST', |
| 27 | body: formData |
| 28 | }) |
| 29 | .then(response => { |
| 30 | if (response.ok) { |
| 31 | // Use a more modern way to show messages, e.g., a Snackbar |
| 32 | console.log('上传成功'); |
| 33 | alert('上传成功 (建议使用Snackbar等UI组件替代alert)'); |
| 34 | } else { |
| 35 | console.error('上传失败'); |
| 36 | alert('上传失败 (建议使用Snackbar等UI组件替代alert)'); |
| 37 | } |
| 38 | }) |
| 39 | .catch(error => { |
| 40 | console.error('上传出错:', error); |
| 41 | alert('上传出错 (建议使用Snackbar等UI组件替代alert)'); |
| 42 | }); |
| 43 | }; |
| 44 | |
| 45 | return ( |
| 46 | <Box sx={{ minHeight: '100vh', py: 4, background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', color: 'white' }}> {/* Moved body styles here for self-containment */} |
| 47 | <Container maxWidth="sm" sx={{ position: 'relative', zIndex: 10 }}> |
| 48 | <Typography variant="h4" sx={{ textAlign: 'center', mb: 3, fontWeight: 'bold' }}> {/* Replaced className with sx */} |
| 49 | ⬆️ 上传种子 · Mini-Tracker |
| 50 | </Typography> |
| 51 | |
| 52 | <Paper sx={{ position: 'relative', zIndex: 20, padding: '2rem', backgroundColor: 'rgba(30, 30, 30, 0.9)' }}> {/* Replaced className with sx */} |
| 53 | <Stack spacing={3}> {/* Increased spacing a bit */} |
| 54 | <TextField |
| 55 | label="资源名称" |
| 56 | variant="outlined" |
| 57 | fullWidth |
| 58 | value={title} |
| 59 | onChange={e => setTitle(e.target.value)} |
| 60 | placeholder="请输入资源名称" |
| 61 | InputLabelProps={{ |
| 62 | sx: { |
| 63 | color: '#ccc', |
| 64 | '&.Mui-focused': { // Ensure label color remains consistent when focused |
| 65 | color: '#ccc', |
| 66 | } |
| 67 | } |
| 68 | }} |
| 69 | sx={{ |
| 70 | // Root element of the TextField |
| 71 | backgroundColor: 'transparent', |
| 72 | |
| 73 | // Styles for the HTML input element itself |
| 74 | '& .MuiInputBase-input': { |
| 75 | color: 'white', // Text color |
| 76 | backgroundColor: 'transparent', // **** THIS IS THE FIX **** Ensure input background is transparent |
| 77 | caretColor: 'white', // Cursor color |
| 78 | '&::placeholder': { |
| 79 | color: '#bbb', |
| 80 | opacity: 1, |
| 81 | }, |
| 82 | }, |
| 83 | // Styles for the outlined border |
| 84 | '& .MuiOutlinedInput-root': { |
| 85 | '& fieldset': { |
| 86 | borderColor: '#888', // Default border color |
| 87 | }, |
| 88 | '&:hover fieldset': { |
| 89 | borderColor: '#aaa', // Border color on hover |
| 90 | }, |
| 91 | '&.Mui-focused fieldset': { |
| 92 | borderColor: '#fff', // Border color when focused |
| 93 | }, |
| 94 | }, |
| 95 | }} |
| 96 | /> |
| 97 | |
| 98 | <Button |
| 99 | variant="contained" |
| 100 | component="label" // Allows the button to act as a label for a hidden input |
| 101 | startIcon={<CloudUploadIcon />} |
| 102 | sx={{ backgroundColor: '#00bcd4', color: 'white', '&:hover': { backgroundColor: '#0097a7' } }} // Replaced className with sx |
| 103 | > |
| 104 | 选择种子文件 |
| 105 | <input |
| 106 | hidden |
| 107 | type="file" |
| 108 | accept=".torrent" |
| 109 | onChange={e => { |
| 110 | if (e.target.files && e.target.files[0]) { |
| 111 | setFile(e.target.files[0]); |
| 112 | } |
| 113 | }} |
| 114 | /> |
| 115 | </Button> |
| 116 | |
| 117 | {file && ( |
| 118 | <Typography variant="body2" sx={{ color: '#eee', textAlign: 'center' }}> |
| 119 | 已选择文件: {file.name} |
| 120 | </Typography> |
| 121 | )} |
| 122 | |
| 123 | <Button |
| 124 | variant="outlined" |
| 125 | onClick={handleUpload} |
| 126 | disabled={!file || !title} |
| 127 | sx={{ borderColor: 'white', color: 'white', '&:hover': { borderColor: '#ddd', backgroundColor: 'rgba(255,255,255,0.1)'}, '&.Mui-disabled': { borderColor: '#777', color: '#777' } }} // Replaced className with sx |
| 128 | > |
| 129 | 提交上传 |
| 130 | </Button> |
| 131 | </Stack> |
| 132 | </Paper> |
| 133 | </Container> |
| 134 | |
| 135 | {/* Background bubbles animation - assuming CSS for .bubbles and .bubble is globally available or defined in an imported CSS file */} |
| 136 | {/* For self-containment, these styles would ideally be JSS or defined in a <style> tag if not using styled-components or similar */} |
| 137 | <Box className="bubbles" sx={{ |
| 138 | pointerEvents: 'none', |
| 139 | position: 'fixed', |
| 140 | top: 0, |
| 141 | left: 0, |
| 142 | width: '100%', |
| 143 | height: '100%', |
| 144 | overflow: 'hidden', |
| 145 | zIndex: 1, // Ensure it's behind the content paper but above the main background |
| 146 | }}> |
| 147 | {[...Array(40)].map((_, i) => ( |
| 148 | <Box |
| 149 | key={i} |
| 150 | className="bubble" // Assuming .bubble and @keyframes rise are defined in your CSS |
| 151 | sx={{ |
| 152 | position: 'absolute', |
| 153 | bottom: '-100px', |
| 154 | background: 'rgba(255, 255, 255, 0.15)', |
| 155 | borderRadius: '50%', |
| 156 | animation: 'rise 20s infinite ease-in', // Make sure @keyframes rise is defined |
| 157 | width: `${Math.random() * 20 + 10}px`, |
| 158 | height: `${Math.random() * 20 + 10}px`, |
| 159 | left: `${Math.random() * 100}%`, |
| 160 | animationDuration: `${10 + Math.random() * 20}s`, |
| 161 | animationDelay: `${Math.random() * 5}s` |
| 162 | }} |
| 163 | /> |
| 164 | ))} |
| 165 | </Box> |
| 166 | {/* Define keyframes if not in external CSS */} |
| 167 | <style> |
| 168 | {` |
| 169 | @keyframes rise { |
| 170 | 0% { |
| 171 | transform: translateY(0) scale(1); |
| 172 | opacity: 0.5; |
| 173 | } |
| 174 | 50% { |
| 175 | opacity: 1; |
| 176 | } |
| 177 | 100% { |
| 178 | transform: translateY(-120vh) scale(0.5); |
| 179 | opacity: 0; |
| 180 | } |
| 181 | } |
| 182 | body { /* Basic reset if base.css is not loaded */ |
| 183 | margin: 0; |
| 184 | font-family: 'Roboto', sans-serif; |
| 185 | } |
| 186 | `} |
| 187 | </style> |
| 188 | </Box> |
| 189 | ); |
| 190 | } |
| 191 | |
| 192 | export default Upload; |