blob: 37730f38176c64fce51db46b115087e811fa7c23 [file] [log] [blame]
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;