| import React, { useEffect, useState, useRef } from 'react'; |
| import './Promotion.css'; |
| import { useUser } from '../../../context/UserContext'; |
| import axios from 'axios'; |
| import PromotionCarousel from './PromotionCarousel'; |
| import PromotionDetailDialog from './PromotionDetailDialog'; |
| import TorrentDetailDialog from './TorrentDetailDialog'; |
| import CreatePromotionDialog from './CreatePromotionDialog'; |
| import CategoryPromotionDialog from './CategoryPromotionDialog'; |
| import ColdTorrentsDialog from './ColdTorrentsDialog'; |
| |
| const Promotion = () => { |
| const { user } = useUser(); |
| const [promotions, setPromotions] = useState([]); |
| const [torrents, setTorrents] = useState([]); |
| const [loading, setLoading] = useState(true); |
| const [promoIndex, setPromoIndex] = useState(0); |
| const promoTimerRef = useRef(null); |
| const [showCreateDialog, setShowCreateDialog] = useState(false); |
| const [showCategoryDialog, setShowCategoryDialog] = useState(false); |
| const [formData, setFormData] = useState({ |
| name: '', |
| startTime: '', |
| endTime: '', |
| discountPercentage: '', |
| applicableTorrentIds: [] |
| }); |
| const [categoryFormData, setCategoryFormData] = useState({ |
| name: '', |
| startTime: '', |
| endTime: '', |
| discountPercentage: '', |
| category: 'movie' |
| }); |
| |
| // 冷门资源列表状态 |
| const [coldTorrents, setColdTorrents] = useState([]); |
| const [showColdDialog, setShowColdDialog] = useState(false); |
| // 新增状态:控制促销对话框中冷门资源表格的显示 |
| const [showPromoColdTable, setShowPromoColdTable] = useState(false); |
| |
| // 新增状态:促销详情数据和详情对话框显示控制 |
| const [promotionDetail, setPromotionDetail] = useState(null); |
| const [showDetailDialog, setShowDetailDialog] = useState(false); |
| |
| // 新增状态:种子详情数据和种子详情对话框显示控制 |
| const [torrentDetail, setTorrentDetail] = useState(null); |
| const [showTorrentDialog, setShowTorrentDialog] = useState(false); |
| |
| useEffect(() => { |
| fetchData(); |
| fetchTorrentList(); |
| }, []); |
| |
| useEffect(() => { |
| if (promotions.length === 0) return; |
| clearInterval(promoTimerRef.current); |
| promoTimerRef.current = setInterval(() => { |
| setPromoIndex(prev => (prev + 1) % promotions.length); |
| }, 5000); |
| return () => clearInterval(promoTimerRef.current); |
| }, [promotions]); |
| |
| const fetchData = async () => { |
| try { |
| const response = await fetch('/seeds/promotions'); |
| const json = await response.json(); |
| if (json.code === 0 || json.code === 200) { |
| setPromotions(json.data || []); |
| } else { |
| console.error('获取促销活动失败:', json.msg); |
| } |
| } catch (error) { |
| console.error('获取促销活动失败:', error); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| const formatImageUrl = (url) => { |
| if (!url) return ''; |
| const filename = url.split('/').pop(); |
| return `http://localhost:5011/uploads/torrents/${filename}`; |
| }; |
| |
| // 修正后的获取种子详情函数 |
| const fetchTorrentDetail = async (torrentId) => { |
| try { |
| // 修正参数名称为torrentId |
| const res = await axios.post(`/seeds/info/${torrentId}`); |
| if (res.data.code === 0) { |
| const seedData = res.data.data; |
| |
| // 处理封面图片 |
| let cover = seedData.imageUrl; |
| if (!cover && seedData.imgUrl) { |
| const imgs = seedData.imgUrl |
| .split(',') |
| .map((i) => i.trim()) |
| .filter(Boolean); |
| cover = imgs.length > 0 ? formatImageUrl(imgs[0]) : null; |
| } |
| |
| setTorrentDetail({...seedData, coverImage: cover}); |
| setShowTorrentDialog(true); |
| } else { |
| alert(`获取种子详情失败: ${res.data.msg || '未知错误'}`); |
| } |
| } catch (err) { |
| console.error('获取种子详情失败:', err); |
| alert('获取种子详情失败'); |
| } |
| }; |
| |
| |
| const fetchPromotionDetail = async (promotionId) => { |
| try { |
| const response = await fetch(`/seeds/promotions/${promotionId}`); |
| const json = await response.json(); |
| if (json.code === 0 || json.code === 200) { |
| // 正确解析applicableTorrentIds |
| const data = { |
| ...json.data, |
| applicableTorrentIds: parseTorrentIds(json.data.applicableTorrentIds) |
| }; |
| setPromotionDetail(data); |
| setShowDetailDialog(true); |
| } else { |
| alert(`获取促销详情失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (error) { |
| console.error('获取促销详情失败:', error); |
| alert('获取促销详情失败'); |
| } |
| }; |
| |
| // 解析种子ID字符串为数组 |
| const parseTorrentIds = (idString) => { |
| try { |
| // 处理类似 "[69, 49, 6]" 的字符串 |
| return JSON.parse(idString); |
| } catch (e) { |
| console.error('解析种子ID失败:', e); |
| return []; |
| } |
| }; |
| |
| // 关闭详情对话框 |
| const closeDetailDialog = () => { |
| setShowDetailDialog(false); |
| setPromotionDetail(null); |
| }; |
| |
| // 关闭种子详情对话框 |
| const closeTorrentDialog = () => { |
| setShowTorrentDialog(false); |
| setTorrentDetail(null); |
| }; |
| |
| const fetchTorrentList = async () => { |
| try { |
| const response = await fetch('/seeds/list'); |
| const json = await response.json(); |
| if (json.code === 0 || json.code === 200) { |
| // 为每个种子添加selected状态 |
| const torrentsWithSelection = (json.data || []).map(torrent => ({ |
| ...torrent, |
| selected: false |
| })); |
| setTorrents(torrentsWithSelection); |
| } else { |
| console.error('获取种子列表失败:', json.msg); |
| } |
| } catch (error) { |
| console.error('获取种子列表失败:', error); |
| } |
| }; |
| |
| const fetchColdTorrents = async () => { |
| try { |
| const response = await fetch('/seeds/cold'); |
| const json = await response.json(); |
| if (json.code === 0 || json.code === 200) { |
| setColdTorrents(json.data || []); // 存储冷门资源数据 |
| setShowColdDialog(true); // 打开模态框 |
| } else { |
| alert(`获取冷门资源失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (error) { |
| console.error('获取冷门资源失败:', error); |
| alert('获取冷门资源失败'); |
| } |
| }; |
| |
| // 从服务器获取冷门资源并显示在促销对话框中 |
| const fetchPromoColdTorrents = async () => { |
| try { |
| const response = await fetch('/seeds/cold'); |
| const json = await response.json(); |
| if (json.code === 0 || json.code === 200) { |
| setColdTorrents(json.data || []); |
| setShowPromoColdTable(true); |
| } else { |
| alert(`获取冷门资源失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (error) { |
| console.error('获取冷门资源失败:', error); |
| alert('获取冷门资源失败'); |
| } |
| }; |
| |
| // 处理促销对话框中种子的选择 |
| const handlePromoTorrentSelection = (torrentId, isChecked) => { |
| setFormData(prev => { |
| const ids = [...prev.applicableTorrentIds]; |
| if (isChecked) { |
| ids.push(torrentId); |
| } else { |
| const index = ids.indexOf(torrentId); |
| if (index !== -1) ids.splice(index, 1); |
| } |
| return { |
| ...prev, |
| applicableTorrentIds: ids |
| }; |
| }); |
| }; |
| |
| // 关闭冷门资源模态框 |
| const closeColdDialog = () => { |
| setShowColdDialog(false); |
| setColdTorrents([]); |
| }; |
| |
| const openCreateDialog = () => { |
| setFormData({ |
| name: '', |
| startTime: '', |
| endTime: '', |
| discountPercentage: '', |
| applicableTorrentIds: [] |
| }); |
| setShowCreateDialog(true); |
| }; |
| |
| const closeCreateDialog = () => { |
| setShowCreateDialog(false); |
| }; |
| |
| const openCategoryDialog = () => { |
| setCategoryFormData({ |
| name: '', |
| startTime: '', |
| endTime: '', |
| discountPercentage: '', |
| category: 'movie' |
| }); |
| setShowCategoryDialog(true); |
| }; |
| |
| const closeCategoryDialog = () => { |
| setShowCategoryDialog(false); |
| }; |
| |
| const handleInputChange = (e) => { |
| const { name, value } = e.target; |
| setFormData(prev => ({ |
| ...prev, |
| [name]: value |
| })); |
| }; |
| |
| const handleCategoryInputChange = (e) => { |
| const { name, value } = e.target; |
| setCategoryFormData(prev => ({ |
| ...prev, |
| [name]: value |
| })); |
| }; |
| |
| const formatSize = (bytes) => { |
| if (bytes === 0) return '0 B'; |
| |
| const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(1024)); |
| |
| return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i]; |
| }; |
| |
| const handleTorrentSelection = (torrentId, isChecked) => { |
| setTorrents(prev => prev.map(torrent => |
| torrent.id === torrentId |
| ? {...torrent, selected: isChecked} |
| : torrent |
| )); |
| |
| setFormData(prev => { |
| const ids = [...prev.applicableTorrentIds]; |
| if (isChecked) { |
| ids.push(torrentId); |
| } else { |
| const index = ids.indexOf(torrentId); |
| if (index !== -1) ids.splice(index, 1); |
| } |
| return { |
| ...prev, |
| applicableTorrentIds: ids |
| }; |
| }); |
| }; |
| |
| const handleCreatePromotion = async () => { |
| if (!formData.name.trim()) { |
| alert('促销名称不能为空'); |
| return; |
| } |
| if (!formData.startTime || !formData.endTime) { |
| alert('促销开始时间和结束时间不能为空'); |
| return; |
| } |
| if (new Date(formData.startTime) >= new Date(formData.endTime)) { |
| alert('促销结束时间必须晚于开始时间'); |
| return; |
| } |
| if (!formData.discountPercentage || isNaN(formData.discountPercentage)) { |
| alert('折扣百分比必须是数字'); |
| return; |
| } |
| if (formData.applicableTorrentIds.length === 0) { |
| alert('请至少选择一个适用的种子'); |
| return; |
| } |
| |
| const newPromo = { |
| name: formData.name, |
| startTime: new Date(formData.startTime).toISOString(), |
| endTime: new Date(formData.endTime).toISOString(), |
| discountPercentage: Number(formData.discountPercentage), |
| applicableTorrentIds: formData.applicableTorrentIds // 使用formData中的种子ID |
| }; |
| |
| try { |
| const res = await fetch('/seeds/promotions', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(newPromo) |
| }); |
| const json = await res.json(); |
| if (json.code === 0 || json.code === 200) { |
| alert('促销活动创建成功'); |
| fetchData(); |
| setShowCreateDialog(false); |
| } else { |
| alert(`创建失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (err) { |
| console.error('创建促销失败:', err); |
| alert('创建促销失败'); |
| } |
| }; |
| |
| const handleCreateCategoryPromotion = async () => { |
| if (!categoryFormData.name.trim()) { |
| alert('促销名称不能为空'); |
| return; |
| } |
| if (!categoryFormData.startTime || !categoryFormData.endTime) { |
| alert('促销开始时间和结束时间不能为空'); |
| return; |
| } |
| if (new Date(categoryFormData.startTime) >= new Date(categoryFormData.endTime)) { |
| alert('促销结束时间必须晚于开始时间'); |
| return; |
| } |
| if (!categoryFormData.discountPercentage || isNaN(categoryFormData.discountPercentage)) { |
| alert('折扣百分比必须是数字'); |
| return; |
| } |
| |
| const newPromo = { |
| name: categoryFormData.name, |
| startTime: new Date(categoryFormData.startTime).toISOString(), |
| endTime: new Date(categoryFormData.endTime).toISOString(), |
| discountPercentage: Number(categoryFormData.discountPercentage) |
| }; |
| |
| try { |
| const res = await fetch(`/seeds/promotions/category?category=${categoryFormData.category}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(newPromo) |
| }); |
| const json = await res.json(); |
| if (json.code === 0 || json.code === 200) { |
| alert('分类促销活动创建成功'); |
| fetchData(); |
| setShowCategoryDialog(false); |
| } else { |
| alert(`创建失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (err) { |
| console.error('创建分类促销失败:', err); |
| alert('创建分类促销失败'); |
| } |
| }; |
| |
| const handleDeletePromotion = async (promotionId) => { |
| if (!window.confirm('确认删除该促销活动吗?')) return; |
| |
| try { |
| const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' }); |
| const json = await res.json(); |
| if (json.code === 0 || json.code === 200) { |
| alert('删除成功'); |
| fetchData(); |
| } else { |
| alert(`删除失败: ${json.msg || '未知错误'}`); |
| } |
| } catch (err) { |
| console.error('删除失败:', err); |
| } |
| }; |
| |
| const isAdmin = user?.role === 'admin'; |
| const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length); |
| const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length); |
| const currentPromo = promotions[promoIndex]; |
| |
| const formatDateTime = (dateTime) => { |
| if (!dateTime) return '未知'; |
| return new Date(dateTime).toLocaleString(); |
| }; |
| |
| const getUploadBonusDisplay = (promo) => { |
| if (!promo || !promo.discountPercentage) return '无'; |
| const bonus = promo.discountPercentage; |
| return bonus > 0 ? `+${bonus}%` : `-${Math.abs(bonus)}%`; |
| }; |
| |
| const getDownloadDiscountDisplay = (promo) => { |
| if (!promo || !promo.downloadDiscount) return '无'; |
| const discount = (1 - promo.downloadDiscount) * 100; |
| return discount > 0 ? `-${discount.toFixed(1)}%` : '无'; |
| }; |
| |
| if (loading) { |
| return <div className="promotion-container">加载中...</div>; |
| }; |
| |
| return ( |
| <div className="promotion-container carousel-container"> |
| <PromotionCarousel |
| promotions={promotions} |
| currentPromo={currentPromo} |
| prevPromo={prevPromo} |
| nextPromo={nextPromo} |
| isAdmin={isAdmin} |
| openCreateDialog={openCreateDialog} |
| openCategoryDialog={openCategoryDialog} |
| fetchColdTorrents={fetchColdTorrents} |
| handleDeletePromotion={handleDeletePromotion} |
| fetchPromotionDetail={fetchPromotionDetail} |
| /> |
| |
| {/* 新增:冷门资源模态框 */} |
| <ColdTorrentsDialog |
| showColdDialog={showColdDialog} |
| coldTorrents={coldTorrents} |
| closeColdDialog={closeColdDialog} |
| fetchTorrentDetail={fetchTorrentDetail} |
| /> |
| |
| {/* 促销详情对话框 */} |
| <PromotionDetailDialog |
| showDetailDialog={showDetailDialog} |
| promotionDetail={promotionDetail} |
| closeDetailDialog={closeDetailDialog} |
| torrents={torrents} |
| fetchTorrentDetail={fetchTorrentDetail} |
| /> |
| |
| {/* 种子详情对话框 */} |
| <TorrentDetailDialog |
| showTorrentDialog={showTorrentDialog} |
| torrentDetail={torrentDetail} |
| closeTorrentDialog={closeTorrentDialog} |
| /> |
| |
| {/* 创建冷门资源促销弹窗 */} |
| <CreatePromotionDialog |
| showCreateDialog={showCreateDialog} |
| formData={formData} |
| handleInputChange={handleInputChange} |
| closeCreateDialog={closeCreateDialog} |
| handleCreatePromotion={handleCreatePromotion} |
| fetchPromoColdTorrents={fetchPromoColdTorrents} |
| showPromoColdTable={showPromoColdTable} |
| coldTorrents={coldTorrents} |
| handlePromoTorrentSelection={handlePromoTorrentSelection} |
| /> |
| |
| {/* 创建特定分类促销弹窗 */} |
| <CategoryPromotionDialog |
| showCategoryDialog={showCategoryDialog} |
| categoryFormData={categoryFormData} |
| handleCategoryInputChange={handleCategoryInputChange} |
| closeCategoryDialog={closeCategoryDialog} |
| handleCreateCategoryPromotion={handleCreateCategoryPromotion} |
| /> |
| </div> |
| ); |
| }; |
| |
| export default Promotion; |
| |