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