Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 1 | import React, { useEffect, useState, useRef } from 'react'; |
| 2 | import './Promotion.css'; |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 3 | import { useUser } from '../../../context/UserContext'; |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 4 | import axios from 'axios'; |
| 5 | import PromotionCarousel from './PromotionCarousel'; |
| 6 | import PromotionDetailDialog from './PromotionDetailDialog'; |
| 7 | import TorrentDetailDialog from './TorrentDetailDialog'; |
| 8 | import CreatePromotionDialog from './CreatePromotionDialog'; |
| 9 | import CategoryPromotionDialog from './CategoryPromotionDialog'; |
| 10 | import ColdTorrentsDialog from './ColdTorrentsDialog'; |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 11 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 12 | const Promotion = () => { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 13 | const { user } = useUser(); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 14 | const [promotions, setPromotions] = useState([]); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 15 | const [torrents, setTorrents] = useState([]); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 16 | const [loading, setLoading] = useState(true); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 17 | const [promoIndex, setPromoIndex] = useState(0); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 18 | const promoTimerRef = useRef(null); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 19 | const [showCreateDialog, setShowCreateDialog] = useState(false); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 20 | const [showCategoryDialog, setShowCategoryDialog] = useState(false); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 21 | const [formData, setFormData] = useState({ |
| 22 | name: '', |
| 23 | startTime: '', |
| 24 | endTime: '', |
| 25 | discountPercentage: '', |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 26 | applicableTorrentIds: [] |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 27 | }); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 28 | const [categoryFormData, setCategoryFormData] = useState({ |
| 29 | name: '', |
| 30 | startTime: '', |
| 31 | endTime: '', |
| 32 | discountPercentage: '', |
| 33 | category: 'movie' |
| 34 | }); |
| 35 | |
| 36 | // 冷门资源列表状态 |
| 37 | const [coldTorrents, setColdTorrents] = useState([]); |
| 38 | const [showColdDialog, setShowColdDialog] = useState(false); |
| 39 | // 新增状态:控制促销对话框中冷门资源表格的显示 |
| 40 | const [showPromoColdTable, setShowPromoColdTable] = useState(false); |
| 41 | |
| 42 | // 新增状态:促销详情数据和详情对话框显示控制 |
| 43 | const [promotionDetail, setPromotionDetail] = useState(null); |
| 44 | const [showDetailDialog, setShowDetailDialog] = useState(false); |
| 45 | |
| 46 | // 新增状态:种子详情数据和种子详情对话框显示控制 |
| 47 | const [torrentDetail, setTorrentDetail] = useState(null); |
| 48 | const [showTorrentDialog, setShowTorrentDialog] = useState(false); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 49 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 50 | useEffect(() => { |
| 51 | fetchData(); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 52 | fetchTorrentList(); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 53 | }, []); |
| 54 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 55 | useEffect(() => { |
| 56 | if (promotions.length === 0) return; |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 57 | clearInterval(promoTimerRef.current); |
| 58 | promoTimerRef.current = setInterval(() => { |
| 59 | setPromoIndex(prev => (prev + 1) % promotions.length); |
| 60 | }, 5000); |
| 61 | return () => clearInterval(promoTimerRef.current); |
| 62 | }, [promotions]); |
| 63 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 64 | const fetchData = async () => { |
| 65 | try { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 66 | const response = await fetch('/seeds/promotions'); |
| 67 | const json = await response.json(); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 68 | if (json.code === 0 || json.code === 200) { |
| 69 | setPromotions(json.data || []); |
| 70 | } else { |
| 71 | console.error('获取促销活动失败:', json.msg); |
| 72 | } |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 73 | } catch (error) { |
Krishya | 8f2fec8 | 2025-06-04 21:54:46 +0800 | [diff] [blame] | 74 | console.error('获取促销活动失败:', error); |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 75 | } finally { |
| 76 | setLoading(false); |
| 77 | } |
| 78 | }; |
| 79 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 80 | const formatImageUrl = (url) => { |
| 81 | if (!url) return ''; |
| 82 | const filename = url.split('/').pop(); |
| 83 | return `http://localhost:5011/uploads/torrents/${filename}`; |
| 84 | }; |
| 85 | |
| 86 | // 修正后的获取种子详情函数 |
| 87 | const fetchTorrentDetail = async (torrentId) => { |
| 88 | try { |
| 89 | // 修正参数名称为torrentId |
| 90 | const res = await axios.post(`/seeds/info/${torrentId}`); |
| 91 | if (res.data.code === 0) { |
| 92 | const seedData = res.data.data; |
| 93 | |
| 94 | // 处理封面图片 |
| 95 | let cover = seedData.imageUrl; |
| 96 | if (!cover && seedData.imgUrl) { |
| 97 | const imgs = seedData.imgUrl |
| 98 | .split(',') |
| 99 | .map((i) => i.trim()) |
| 100 | .filter(Boolean); |
| 101 | cover = imgs.length > 0 ? formatImageUrl(imgs[0]) : null; |
| 102 | } |
| 103 | |
| 104 | setTorrentDetail({...seedData, coverImage: cover}); |
| 105 | setShowTorrentDialog(true); |
| 106 | } else { |
| 107 | alert(`获取种子详情失败: ${res.data.msg || '未知错误'}`); |
| 108 | } |
| 109 | } catch (err) { |
| 110 | console.error('获取种子详情失败:', err); |
| 111 | alert('获取种子详情失败'); |
| 112 | } |
| 113 | }; |
| 114 | |
| 115 | |
| 116 | const fetchPromotionDetail = async (promotionId) => { |
| 117 | try { |
| 118 | const response = await fetch(`/seeds/promotions/${promotionId}`); |
| 119 | const json = await response.json(); |
| 120 | if (json.code === 0 || json.code === 200) { |
| 121 | // 正确解析applicableTorrentIds |
| 122 | const data = { |
| 123 | ...json.data, |
| 124 | applicableTorrentIds: parseTorrentIds(json.data.applicableTorrentIds) |
| 125 | }; |
| 126 | setPromotionDetail(data); |
| 127 | setShowDetailDialog(true); |
| 128 | } else { |
| 129 | alert(`获取促销详情失败: ${json.msg || '未知错误'}`); |
| 130 | } |
| 131 | } catch (error) { |
| 132 | console.error('获取促销详情失败:', error); |
| 133 | alert('获取促销详情失败'); |
| 134 | } |
| 135 | }; |
| 136 | |
| 137 | // 解析种子ID字符串为数组 |
| 138 | const parseTorrentIds = (idString) => { |
| 139 | try { |
| 140 | // 处理类似 "[69, 49, 6]" 的字符串 |
| 141 | return JSON.parse(idString); |
| 142 | } catch (e) { |
| 143 | console.error('解析种子ID失败:', e); |
| 144 | return []; |
| 145 | } |
| 146 | }; |
| 147 | |
| 148 | // 关闭详情对话框 |
| 149 | const closeDetailDialog = () => { |
| 150 | setShowDetailDialog(false); |
| 151 | setPromotionDetail(null); |
| 152 | }; |
| 153 | |
| 154 | // 关闭种子详情对话框 |
| 155 | const closeTorrentDialog = () => { |
| 156 | setShowTorrentDialog(false); |
| 157 | setTorrentDetail(null); |
| 158 | }; |
| 159 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 160 | const fetchTorrentList = async () => { |
| 161 | try { |
| 162 | const response = await fetch('/seeds/list'); |
| 163 | const json = await response.json(); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 164 | if (json.code === 0 || json.code === 200) { |
| 165 | // 为每个种子添加selected状态 |
| 166 | const torrentsWithSelection = (json.data || []).map(torrent => ({ |
| 167 | ...torrent, |
| 168 | selected: false |
| 169 | })); |
| 170 | setTorrents(torrentsWithSelection); |
| 171 | } else { |
| 172 | console.error('获取种子列表失败:', json.msg); |
| 173 | } |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 174 | } catch (error) { |
| 175 | console.error('获取种子列表失败:', error); |
| 176 | } |
| 177 | }; |
| 178 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 179 | const fetchColdTorrents = async () => { |
| 180 | try { |
| 181 | const response = await fetch('/seeds/cold'); |
| 182 | const json = await response.json(); |
| 183 | if (json.code === 0 || json.code === 200) { |
| 184 | setColdTorrents(json.data || []); // 存储冷门资源数据 |
| 185 | setShowColdDialog(true); // 打开模态框 |
| 186 | } else { |
| 187 | alert(`获取冷门资源失败: ${json.msg || '未知错误'}`); |
| 188 | } |
| 189 | } catch (error) { |
| 190 | console.error('获取冷门资源失败:', error); |
| 191 | alert('获取冷门资源失败'); |
| 192 | } |
| 193 | }; |
| 194 | |
| 195 | // 从服务器获取冷门资源并显示在促销对话框中 |
| 196 | const fetchPromoColdTorrents = async () => { |
| 197 | try { |
| 198 | const response = await fetch('/seeds/cold'); |
| 199 | const json = await response.json(); |
| 200 | if (json.code === 0 || json.code === 200) { |
| 201 | setColdTorrents(json.data || []); |
| 202 | setShowPromoColdTable(true); |
| 203 | } else { |
| 204 | alert(`获取冷门资源失败: ${json.msg || '未知错误'}`); |
| 205 | } |
| 206 | } catch (error) { |
| 207 | console.error('获取冷门资源失败:', error); |
| 208 | alert('获取冷门资源失败'); |
| 209 | } |
| 210 | }; |
| 211 | |
| 212 | // 处理促销对话框中种子的选择 |
| 213 | const handlePromoTorrentSelection = (torrentId, isChecked) => { |
| 214 | setFormData(prev => { |
| 215 | const ids = [...prev.applicableTorrentIds]; |
| 216 | if (isChecked) { |
| 217 | ids.push(torrentId); |
| 218 | } else { |
| 219 | const index = ids.indexOf(torrentId); |
| 220 | if (index !== -1) ids.splice(index, 1); |
| 221 | } |
| 222 | return { |
| 223 | ...prev, |
| 224 | applicableTorrentIds: ids |
| 225 | }; |
| 226 | }); |
| 227 | }; |
| 228 | |
| 229 | // 关闭冷门资源模态框 |
| 230 | const closeColdDialog = () => { |
| 231 | setShowColdDialog(false); |
| 232 | setColdTorrents([]); |
| 233 | }; |
| 234 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 235 | const openCreateDialog = () => { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 236 | setFormData({ |
| 237 | name: '', |
| 238 | startTime: '', |
| 239 | endTime: '', |
| 240 | discountPercentage: '', |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 241 | applicableTorrentIds: [] |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 242 | }); |
| 243 | setShowCreateDialog(true); |
| 244 | }; |
| 245 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 246 | const closeCreateDialog = () => { |
| 247 | setShowCreateDialog(false); |
| 248 | }; |
| 249 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 250 | const openCategoryDialog = () => { |
| 251 | setCategoryFormData({ |
| 252 | name: '', |
| 253 | startTime: '', |
| 254 | endTime: '', |
| 255 | discountPercentage: '', |
| 256 | category: 'movie' |
| 257 | }); |
| 258 | setShowCategoryDialog(true); |
| 259 | }; |
| 260 | |
| 261 | const closeCategoryDialog = () => { |
| 262 | setShowCategoryDialog(false); |
| 263 | }; |
| 264 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 265 | const handleInputChange = (e) => { |
| 266 | const { name, value } = e.target; |
| 267 | setFormData(prev => ({ |
| 268 | ...prev, |
| 269 | [name]: value |
| 270 | })); |
| 271 | }; |
| 272 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 273 | const handleCategoryInputChange = (e) => { |
| 274 | const { name, value } = e.target; |
| 275 | setCategoryFormData(prev => ({ |
| 276 | ...prev, |
| 277 | [name]: value |
| 278 | })); |
| 279 | }; |
| 280 | |
| 281 | const formatSize = (bytes) => { |
| 282 | if (bytes === 0) return '0 B'; |
| 283 | |
| 284 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; |
| 285 | const i = Math.floor(Math.log(bytes) / Math.log(1024)); |
| 286 | |
| 287 | return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i]; |
| 288 | }; |
| 289 | |
| 290 | const handleTorrentSelection = (torrentId, isChecked) => { |
| 291 | setTorrents(prev => prev.map(torrent => |
| 292 | torrent.id === torrentId |
| 293 | ? {...torrent, selected: isChecked} |
| 294 | : torrent |
| 295 | )); |
| 296 | |
| 297 | setFormData(prev => { |
| 298 | const ids = [...prev.applicableTorrentIds]; |
| 299 | if (isChecked) { |
| 300 | ids.push(torrentId); |
| 301 | } else { |
| 302 | const index = ids.indexOf(torrentId); |
| 303 | if (index !== -1) ids.splice(index, 1); |
| 304 | } |
| 305 | return { |
| 306 | ...prev, |
| 307 | applicableTorrentIds: ids |
| 308 | }; |
| 309 | }); |
| 310 | }; |
| 311 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 312 | const handleCreatePromotion = async () => { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 313 | if (!formData.name.trim()) { |
| 314 | alert('促销名称不能为空'); |
| 315 | return; |
| 316 | } |
| 317 | if (!formData.startTime || !formData.endTime) { |
| 318 | alert('促销开始时间和结束时间不能为空'); |
| 319 | return; |
| 320 | } |
| 321 | if (new Date(formData.startTime) >= new Date(formData.endTime)) { |
| 322 | alert('促销结束时间必须晚于开始时间'); |
| 323 | return; |
| 324 | } |
| 325 | if (!formData.discountPercentage || isNaN(formData.discountPercentage)) { |
| 326 | alert('折扣百分比必须是数字'); |
| 327 | return; |
| 328 | } |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 329 | if (formData.applicableTorrentIds.length === 0) { |
| 330 | alert('请至少选择一个适用的种子'); |
| 331 | return; |
| 332 | } |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 333 | |
| 334 | const newPromo = { |
| 335 | name: formData.name, |
| 336 | startTime: new Date(formData.startTime).toISOString(), |
| 337 | endTime: new Date(formData.endTime).toISOString(), |
| 338 | discountPercentage: Number(formData.discountPercentage), |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 339 | applicableTorrentIds: formData.applicableTorrentIds // 使用formData中的种子ID |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 340 | }; |
| 341 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 342 | try { |
| 343 | const res = await fetch('/seeds/promotions', { |
| 344 | method: 'POST', |
| 345 | headers: { 'Content-Type': 'application/json' }, |
| 346 | body: JSON.stringify(newPromo) |
| 347 | }); |
| 348 | const json = await res.json(); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 349 | if (json.code === 0 || json.code === 200) { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 350 | alert('促销活动创建成功'); |
| 351 | fetchData(); |
| 352 | setShowCreateDialog(false); |
| 353 | } else { |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 354 | alert(`创建失败: ${json.msg || '未知错误'}`); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 355 | } |
| 356 | } catch (err) { |
| 357 | console.error('创建促销失败:', err); |
| 358 | alert('创建促销失败'); |
| 359 | } |
| 360 | }; |
| 361 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 362 | const handleCreateCategoryPromotion = async () => { |
| 363 | if (!categoryFormData.name.trim()) { |
| 364 | alert('促销名称不能为空'); |
| 365 | return; |
| 366 | } |
| 367 | if (!categoryFormData.startTime || !categoryFormData.endTime) { |
| 368 | alert('促销开始时间和结束时间不能为空'); |
| 369 | return; |
| 370 | } |
| 371 | if (new Date(categoryFormData.startTime) >= new Date(categoryFormData.endTime)) { |
| 372 | alert('促销结束时间必须晚于开始时间'); |
| 373 | return; |
| 374 | } |
| 375 | if (!categoryFormData.discountPercentage || isNaN(categoryFormData.discountPercentage)) { |
| 376 | alert('折扣百分比必须是数字'); |
| 377 | return; |
| 378 | } |
| 379 | |
| 380 | const newPromo = { |
| 381 | name: categoryFormData.name, |
| 382 | startTime: new Date(categoryFormData.startTime).toISOString(), |
| 383 | endTime: new Date(categoryFormData.endTime).toISOString(), |
| 384 | discountPercentage: Number(categoryFormData.discountPercentage) |
| 385 | }; |
| 386 | |
| 387 | try { |
| 388 | const res = await fetch(`/seeds/promotions/category?category=${categoryFormData.category}`, { |
| 389 | method: 'POST', |
| 390 | headers: { 'Content-Type': 'application/json' }, |
| 391 | body: JSON.stringify(newPromo) |
| 392 | }); |
| 393 | const json = await res.json(); |
| 394 | if (json.code === 0 || json.code === 200) { |
| 395 | alert('分类促销活动创建成功'); |
| 396 | fetchData(); |
| 397 | setShowCategoryDialog(false); |
| 398 | } else { |
| 399 | alert(`创建失败: ${json.msg || '未知错误'}`); |
| 400 | } |
| 401 | } catch (err) { |
| 402 | console.error('创建分类促销失败:', err); |
| 403 | alert('创建分类促销失败'); |
| 404 | } |
| 405 | }; |
| 406 | |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 407 | const handleDeletePromotion = async (promotionId) => { |
| 408 | if (!window.confirm('确认删除该促销活动吗?')) return; |
| 409 | |
| 410 | try { |
| 411 | const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' }); |
| 412 | const json = await res.json(); |
Krishya | 73cd882 | 2025-06-07 15:48:41 +0800 | [diff] [blame] | 413 | if (json.code === 0 || json.code === 200) { |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 414 | alert('删除成功'); |
| 415 | fetchData(); |
| 416 | } else { |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 417 | alert(`删除失败: ${json.msg || '未知错误'}`); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 418 | } |
| 419 | } catch (err) { |
| 420 | console.error('删除失败:', err); |
| 421 | } |
| 422 | }; |
| 423 | |
| 424 | const isAdmin = user?.role === 'admin'; |
| 425 | const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length); |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 426 | const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length); |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 427 | const currentPromo = promotions[promoIndex]; |
| 428 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 429 | const formatDateTime = (dateTime) => { |
| 430 | if (!dateTime) return '未知'; |
| 431 | return new Date(dateTime).toLocaleString(); |
| 432 | }; |
| 433 | |
| 434 | const getUploadBonusDisplay = (promo) => { |
| 435 | if (!promo || !promo.discountPercentage) return '无'; |
| 436 | const bonus = promo.discountPercentage; |
| 437 | return bonus > 0 ? `+${bonus}%` : `-${Math.abs(bonus)}%`; |
| 438 | }; |
| 439 | |
| 440 | const getDownloadDiscountDisplay = (promo) => { |
| 441 | if (!promo || !promo.downloadDiscount) return '无'; |
| 442 | const discount = (1 - promo.downloadDiscount) * 100; |
| 443 | return discount > 0 ? `-${discount.toFixed(1)}%` : '无'; |
| 444 | }; |
| 445 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 446 | if (loading) { |
| 447 | return <div className="promotion-container">加载中...</div>; |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 448 | }; |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 449 | |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 450 | return ( |
| 451 | <div className="promotion-container carousel-container"> |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 452 | <PromotionCarousel |
| 453 | promotions={promotions} |
| 454 | currentPromo={currentPromo} |
| 455 | prevPromo={prevPromo} |
| 456 | nextPromo={nextPromo} |
| 457 | isAdmin={isAdmin} |
| 458 | openCreateDialog={openCreateDialog} |
| 459 | openCategoryDialog={openCategoryDialog} |
| 460 | fetchColdTorrents={fetchColdTorrents} |
| 461 | handleDeletePromotion={handleDeletePromotion} |
| 462 | fetchPromotionDetail={fetchPromotionDetail} |
| 463 | /> |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 464 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 465 | {/* 新增:冷门资源模态框 */} |
| 466 | <ColdTorrentsDialog |
| 467 | showColdDialog={showColdDialog} |
| 468 | coldTorrents={coldTorrents} |
| 469 | closeColdDialog={closeColdDialog} |
| 470 | fetchTorrentDetail={fetchTorrentDetail} |
| 471 | /> |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 472 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 473 | {/* 促销详情对话框 */} |
| 474 | <PromotionDetailDialog |
| 475 | showDetailDialog={showDetailDialog} |
| 476 | promotionDetail={promotionDetail} |
| 477 | closeDetailDialog={closeDetailDialog} |
| 478 | torrents={torrents} |
| 479 | fetchTorrentDetail={fetchTorrentDetail} |
| 480 | /> |
Krishya | 6bf199c | 2025-06-06 21:14:23 +0800 | [diff] [blame] | 481 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 482 | {/* 种子详情对话框 */} |
| 483 | <TorrentDetailDialog |
| 484 | showTorrentDialog={showTorrentDialog} |
| 485 | torrentDetail={torrentDetail} |
| 486 | closeTorrentDialog={closeTorrentDialog} |
| 487 | /> |
| 488 | |
| 489 | {/* 创建冷门资源促销弹窗 */} |
| 490 | <CreatePromotionDialog |
| 491 | showCreateDialog={showCreateDialog} |
| 492 | formData={formData} |
| 493 | handleInputChange={handleInputChange} |
| 494 | closeCreateDialog={closeCreateDialog} |
| 495 | handleCreatePromotion={handleCreatePromotion} |
| 496 | fetchPromoColdTorrents={fetchPromoColdTorrents} |
| 497 | showPromoColdTable={showPromoColdTable} |
| 498 | coldTorrents={coldTorrents} |
| 499 | handlePromoTorrentSelection={handlePromoTorrentSelection} |
| 500 | /> |
| 501 | |
| 502 | {/* 创建特定分类促销弹窗 */} |
| 503 | <CategoryPromotionDialog |
| 504 | showCategoryDialog={showCategoryDialog} |
| 505 | categoryFormData={categoryFormData} |
| 506 | handleCategoryInputChange={handleCategoryInputChange} |
| 507 | closeCategoryDialog={closeCategoryDialog} |
| 508 | handleCreateCategoryPromotion={handleCreateCategoryPromotion} |
| 509 | /> |
Krishya | f1d0ea8 | 2025-05-03 17:01:58 +0800 | [diff] [blame] | 510 | </div> |
| 511 | ); |
| 512 | }; |
| 513 | |
Krishya | dbfadaa | 2025-06-09 20:33:15 +0800 | [diff] [blame^] | 514 | export default Promotion; |
| 515 | |