修改密码、管理员删帖、促销、退出登录
Change-Id: I2cc0e211ac5a04f9e89d0736fadd25541a5fccb9
diff --git a/src/pages/Forum/posts-main/components/PostList.jsx b/src/pages/Forum/posts-main/components/PostList.jsx
index 5d14fdd..533e726 100644
--- a/src/pages/Forum/posts-main/components/PostList.jsx
+++ b/src/pages/Forum/posts-main/components/PostList.jsx
@@ -4,9 +4,9 @@
import { GoodTwo, Star, Delete } from '@icon-park/react';
import { likePost } from '../../posts-detail/api';
import { formatAvatarUrl } from '../../../../components/utils/avatar';
+import { useUser } from '../../../../context/UserContext'; // 路径根据实际调整
import './PostList.css';
-// 修改后的封面图 URL 拼接函数
const formatImageUrl = (url) => {
if (!url) return '';
const filename = url.split('/').pop(); // 提取文件名部分
@@ -14,6 +14,7 @@
};
const PostList = ({ search }) => {
+ const { user } = useUser(); // 获取当前登录用户
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
@@ -116,7 +117,17 @@
}
};
- const handleDeletePost = async (postNo) => {
+ const handleDeletePost = async (postNo, postUserId) => {
+ // 权限判断:管理员或者帖子发布者本人
+ if (!user) {
+ alert('请先登录');
+ return;
+ }
+ if (user.role !== 'admin' && user.userId !== postUserId) {
+ alert('您没有权限删除此帖子');
+ return;
+ }
+
if (window.confirm('确定要删除这篇帖子吗?')) {
try {
await axios.delete(`/echo/forum/posts/${postNo}/deletePost`);
@@ -144,17 +155,10 @@
coverImage = imgs.length > 0 ? formatImageUrl(imgs[0]) : null;
}
+ const canDelete = user && (user.role === 'admin' || user.userId === post.user_id);
+
return (
<div key={post.postNo} className="post-card" style={{ backgroundColor: '#e9ded2' }}>
- {/* <div className="user-info">
- <img
- className="avatar"
- src={post.avatarUrl}
- alt="头像"
- />
- <span className="nickname" style={{ color: '#755e50' }}>{post.username}</span>
- </div> */}
-
<div className="user-info">
<Link href={`/information/${post.user_id}`}>
<img
@@ -167,7 +171,6 @@
<span className="nickname" style={{ color: '#755e50' }}>{post.username}</span>
</div>
-
{coverImage && <img className="cover-image" src={coverImage} alt="封面" />}
<h3 style={{ color: '#000000' }}>{post.title || '无标题'}</h3>
@@ -182,9 +185,12 @@
<Star theme="outline" size="24" fill={post.collected ? '#ffd700' : '#fff'} />
<span>{post.collectCount}</span>
</button>
- <button className="icon-btn" onClick={() => handleDeletePost(post.postNo)}>
- <Delete theme="outline" size="24" fill="#333" />
- </button>
+
+ {canDelete && (
+ <button className="icon-btn" onClick={() => handleDeletePost(post.postNo, post.user_id)}>
+ <Delete theme="outline" size="24" fill="#333" />
+ </button>
+ )}
</div>
</div>
<div className="detail-button-wrapper">
@@ -205,3 +211,4 @@
};
export default PostList;
+
diff --git a/src/pages/Forum/promotion-part/Promotion.css b/src/pages/Forum/promotion-part/Promotion.css
index 2232ba5..46e7a0d 100644
--- a/src/pages/Forum/promotion-part/Promotion.css
+++ b/src/pages/Forum/promotion-part/Promotion.css
@@ -108,3 +108,49 @@
font-size: 16px;
text-align: center;
}
+
+.dialog-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.4);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 999;
+}
+.dialog {
+ background: #fff;
+ padding: 20px;
+ border-radius: 6px;
+ width: 400px;
+ max-width: 90%;
+ box-shadow: 0 0 10px rgba(0,0,0,0.25);
+}
+.form-item {
+ margin-bottom: 12px;
+ display: flex;
+ flex-direction: column;
+}
+.form-item label {
+ font-weight: bold;
+ margin-bottom: 4px;
+}
+.form-item input, .form-item textarea {
+ padding: 6px 8px;
+ font-size: 14px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+.dialog-buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+}
+.dialog-buttons button {
+ padding: 6px 16px;
+ cursor: pointer;
+}
+
diff --git a/src/pages/Forum/promotion-part/Promotion.jsx b/src/pages/Forum/promotion-part/Promotion.jsx
index 28fd7ac..8ca4067 100644
--- a/src/pages/Forum/promotion-part/Promotion.jsx
+++ b/src/pages/Forum/promotion-part/Promotion.jsx
@@ -1,17 +1,32 @@
-// export default Promotion;
-
import React, { useEffect, useState, useRef } from 'react';
import './Promotion.css';
+import { useUser } from '../../../context/UserContext';
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 [formData, setFormData] = useState({
+ name: '',
+ startTime: '',
+ endTime: '',
+ discountPercentage: '',
+ uploadCoeff: '',
+ downloadCoeff: '',
+ description: ''
+ });
+
useEffect(() => {
fetchData();
+ fetchTorrentList();
}, []);
useEffect(() => {
@@ -25,11 +40,9 @@
const fetchData = async () => {
try {
- // ✅ 获取促销活动列表(新接口)
- const promoResponse = await fetch(`/seeds/promotions`);
- const promoJson = await promoResponse.json();
- console.log('接口返回数据:', promoJson);
- const promoData = Array.isArray(promoJson?.result) ? promoJson.result : [];
+ const response = await fetch('/seeds/promotions');
+ const json = await response.json();
+ const promoData = Array.isArray(json?.data) ? json.data : [];
setPromotions(promoData);
} catch (error) {
console.error('获取促销活动失败:', error);
@@ -38,19 +51,140 @@
}
};
+ const fetchTorrentList = async () => {
+ try {
+ const response = await fetch('/seeds/list');
+ const json = await response.json();
+ const torrentList = Array.isArray(json?.data) ? json.data : [];
+ setTorrents(torrentList);
+ } catch (error) {
+ console.error('获取种子列表失败:', error);
+ }
+ };
+
+ // 打开创建促销活动弹窗
+ const openCreateDialog = () => {
+ // 重置表单数据
+ setFormData({
+ name: '',
+ startTime: '',
+ endTime: '',
+ discountPercentage: '',
+ uploadCoeff: '',
+ downloadCoeff: '',
+ description: ''
+ });
+ setShowCreateDialog(true);
+ };
+
+ // 关闭弹窗
+ const closeCreateDialog = () => {
+ setShowCreateDialog(false);
+ };
+
+ // 处理表单输入变化
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ };
+
+ // 提交创建促销活动
+ const handleCreatePromotion = async () => {
+ if (torrents.length === 0) {
+ alert('没有可用的种子,请先上传种子');
+ return;
+ }
+ 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;
+ }
+
+ const applicableTorrentIds = torrents.map(t => t.id);
+
+ const newPromo = {
+ name: formData.name,
+ startTime: new Date(formData.startTime).toISOString(),
+ endTime: new Date(formData.endTime).toISOString(),
+ discountPercentage: Number(formData.discountPercentage),
+ uploadCoeff: formData.uploadCoeff ? Number(formData.uploadCoeff) : undefined,
+ downloadCoeff: formData.downloadCoeff ? Number(formData.downloadCoeff) : undefined,
+ applicableTorrentIds: JSON.stringify(applicableTorrentIds), // ✅ 关键修改
+ description: formData.description
+ };
+
+
+ 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 === 200) {
+ alert('促销活动创建成功');
+ fetchData();
+ setShowCreateDialog(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.success) {
+ alert('删除成功');
+ fetchData();
+ } else {
+ alert('删除失败: ' + json.message);
+ }
+ } 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];
+
if (loading) {
return <div className="promotion-container">加载中...</div>;
}
- const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
- const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
- const currentPromo = promotions[promoIndex];
-
return (
<div className="promotion-container carousel-container">
- {/* 促销活动轮播 */}
<section className="carousel-section">
<h2>当前促销活动</h2>
+
+ {isAdmin && (
+ <button className="create-btn" onClick={openCreateDialog}>
+ 创建促销活动
+ </button>
+ )}
+
{promotions.length === 0 || !currentPromo ? (
<div className="empty-state">暂无促销活动</div>
) : (
@@ -76,14 +210,616 @@
{currentPromo?.description && (
<div><strong>描述:</strong>{currentPromo.description}</div>
)}
+ {isAdmin && (
+ <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+ 删除该活动
+ </button>
+ )}
</div>
<button className="arrow right" onClick={nextPromo}>></button>
</div>
)}
</section>
+
+ {/* 创建促销活动弹窗 */}
+ {showCreateDialog && (
+ <div className="dialog-overlay">
+ <div className="dialog">
+ <h3>创建促销活动</h3>
+ <div className="form-item">
+ <label>促销名称:</label>
+ <input
+ type="text"
+ name="name"
+ value={formData.name}
+ onChange={handleInputChange}
+ placeholder="请输入促销名称"
+ />
+ </div>
+ <div className="form-item">
+ <label>开始时间:</label>
+ <input
+ type="datetime-local"
+ name="startTime"
+ value={formData.startTime}
+ onChange={handleInputChange}
+ />
+ </div>
+ <div className="form-item">
+ <label>结束时间:</label>
+ <input
+ type="datetime-local"
+ name="endTime"
+ value={formData.endTime}
+ onChange={handleInputChange}
+ />
+ </div>
+ <div className="form-item">
+ <label>折扣百分比(数字):</label>
+ <input
+ type="number"
+ name="discountPercentage"
+ value={formData.discountPercentage}
+ onChange={handleInputChange}
+ placeholder="例如:20 表示 20% 折扣"
+ min="0"
+ max="100"
+ />
+ </div>
+ <div className="form-item">
+ <label>上传奖励系数(可选):</label>
+ <input
+ type="number"
+ name="uploadCoeff"
+ value={formData.uploadCoeff}
+ onChange={handleInputChange}
+ placeholder="例如:1.5"
+ step="0.1"
+ />
+ </div>
+ <div className="form-item">
+ <label>下载折扣系数(可选):</label>
+ <input
+ type="number"
+ name="downloadCoeff"
+ value={formData.downloadCoeff}
+ onChange={handleInputChange}
+ placeholder="例如:0.8"
+ step="0.1"
+ />
+ </div>
+ <div className="form-item">
+ <label>描述(可选):</label>
+ <textarea
+ name="description"
+ value={formData.description}
+ onChange={handleInputChange}
+ placeholder="促销活动描述"
+ rows={3}
+ />
+ </div>
+ <div className="dialog-buttons">
+ <button onClick={handleCreatePromotion}>确定</button>
+ <button onClick={closeCreateDialog}>取消</button>
+ </div>
+ </div>
+ </div>
+ )}
</div>
);
};
export default Promotion;
+
+// import React, { useEffect, useState, useRef } from 'react';
+// import './Promotion.css';
+// import { useUser } from '../../../context/UserContext';
+
+// 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 [showCreateModal, setShowCreateModal] = useState(false);
+// const [formData, setFormData] = useState({
+// name: '',
+// description: '',
+// discountPercentage: 0,
+// startTime: '',
+// endTime: '',
+// applicableTorrentIds: [],
+// });
+
+// 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();
+// const promoData = Array.isArray(json?.data) ? json.data : [];
+// setPromotions(promoData);
+// } catch (error) {
+// console.error('获取促销活动失败:', error);
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// const fetchTorrentList = async () => {
+// try {
+// const response = await fetch('/seeds/list');
+// const json = await response.json();
+// const torrentList = Array.isArray(json?.data) ? json.data : [];
+// setTorrents(torrentList);
+// } catch (error) {
+// console.error('获取种子列表失败:', error);
+// }
+// };
+
+// // 打开模态框时,重置表单数据,默认设置时间并填入所有种子ID
+// const openCreateModal = () => {
+// if (torrents.length === 0) {
+// alert('没有可用的种子,请先上传种子');
+// return;
+// }
+// setFormData({
+// name: '',
+// description: '',
+// discountPercentage: 20,
+// startTime: new Date().toISOString().slice(0, 16), // 用于datetime-local输入框,格式 YYYY-MM-DDTHH:mm
+// endTime: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 16),
+// applicableTorrentIds: torrents.map(t => t.id),
+// });
+// setShowCreateModal(true);
+// };
+
+// // 表单输入处理
+// const handleInputChange = (e) => {
+// const { name, value } = e.target;
+// setFormData(prev => ({
+// ...prev,
+// [name]: name === 'discountPercentage' ? Number(value) : value,
+// }));
+// };
+
+// // 点击确定提交创建
+// const handleCreateConfirm = async () => {
+// if (!formData.name) {
+// alert('促销名称不能为空');
+// return;
+// }
+// if (!formData.startTime || !formData.endTime) {
+// alert('请选择开始时间和结束时间');
+// return;
+// }
+// if (formData.discountPercentage <= 0 || formData.discountPercentage >= 100) {
+// alert('折扣百分比应在1-99之间');
+// return;
+// }
+// if (!formData.applicableTorrentIds.length) {
+// alert('请选择适用的种子');
+// return;
+// }
+
+// // 准备发送数据,适配后端字段名
+// const newPromo = {
+// name: formData.name,
+// description: formData.description,
+// discountPercentage: formData.discountPercentage,
+// startTime: new Date(formData.startTime).toISOString(),
+// endTime: new Date(formData.endTime).toISOString(),
+// applicableTorrentIds: formData.applicableTorrentIds,
+// };
+
+// 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 === 200) {
+// alert('促销活动创建成功');
+// setShowCreateModal(false);
+// fetchData();
+// } else {
+// alert('创建失败: ' + (json.msg || '未知错误'));
+// }
+// } catch (err) {
+// console.error('创建促销失败:', err);
+// alert('创建促销失败');
+// }
+// };
+
+// const handleCancel = () => {
+// setShowCreateModal(false);
+// };
+
+// 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.success) {
+// alert('删除成功');
+// fetchData();
+// } else {
+// alert('删除失败: ' + json.message);
+// }
+// } 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];
+
+// if (loading) {
+// return <div className="promotion-container">加载中...</div>;
+// }
+
+// return (
+// <div className="promotion-container carousel-container">
+// <section className="carousel-section">
+// <h2>当前促销活动</h2>
+
+// {isAdmin && (
+// <button className="create-btn" onClick={openCreateModal}>
+// 创建促销活动
+// </button>
+// )}
+
+// {promotions.length === 0 || !currentPromo ? (
+// <div className="empty-state">暂无促销活动</div>
+// ) : (
+// <div
+// className="carousel"
+// onMouseEnter={() => clearInterval(promoTimerRef.current)}
+// onMouseLeave={() => {
+// promoTimerRef.current = setInterval(() => {
+// setPromoIndex(prev => (prev + 1) % promotions.length);
+// }, 3000);
+// }}
+// >
+// <button className="arrow left" onClick={prevPromo}><</button>
+// <div className="slide">
+// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
+// <div><strong>促销时间:</strong>
+// {currentPromo?.startTime && currentPromo?.endTime
+// ? `${new Date(currentPromo.startTime).toLocaleString()} ~ ${new Date(currentPromo.endTime).toLocaleString()}`
+// : '未知'}
+// </div>
+// <div><strong>折扣百分比:</strong>{currentPromo?.discountPercentage ?? '无'}</div>
+// {currentPromo?.description && (
+// <div><strong>描述:</strong>{currentPromo.description}</div>
+// )}
+// {isAdmin && (
+// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+// 删除该活动
+// </button>
+// )}
+// </div>
+// <button className="arrow right" onClick={nextPromo}>></button>
+// </div>
+// )}
+// </section>
+
+// {/* 创建促销模态框 */}
+// {showCreateModal && (
+// <div className="modal-overlay">
+// <div className="modal-content">
+// <h3>创建促销活动</h3>
+// <label>
+// 促销名称:
+// <input
+// type="text"
+// name="name"
+// value={formData.name}
+// onChange={handleInputChange}
+// />
+// </label>
+// <label>
+// 描述:
+// <textarea
+// name="description"
+// value={formData.description}
+// onChange={handleInputChange}
+// rows={3}
+// />
+// </label>
+// <label>
+// 折扣百分比:
+// <input
+// type="number"
+// name="discountPercentage"
+// value={formData.discountPercentage}
+// min={1}
+// max={99}
+// onChange={handleInputChange}
+// />
+// </label>
+// <label>
+// 开始时间:
+// <input
+// type="datetime-local"
+// name="startTime"
+// value={formData.startTime}
+// onChange={handleInputChange}
+// />
+// </label>
+// <label>
+// 结束时间:
+// <input
+// type="datetime-local"
+// name="endTime"
+// value={formData.endTime}
+// onChange={handleInputChange}
+// />
+// </label>
+// <label>
+// 适用种子ID(逗号分隔,可留空默认所有):
+// <input
+// type="text"
+// name="applicableTorrentIds"
+// value={formData.applicableTorrentIds.join(',')}
+// onChange={(e) => {
+// const ids = e.target.value
+// .split(',')
+// .map(id => id.trim())
+// .filter(id => id !== '')
+// .map(id => Number(id))
+// .filter(id => !isNaN(id));
+// setFormData(prev => ({ ...prev, applicableTorrentIds: ids }));
+// }}
+// />
+// </label>
+
+// <div className="modal-buttons">
+// <button onClick={handleCreateConfirm}>确定</button>
+// <button onClick={handleCancel}>取消</button>
+// </div>
+// </div>
+// </div>
+// )}
+
+// {/* 模态框简单样式 */}
+// <style>{`
+// .modal-overlay {
+// position: fixed;
+// top: 0; left: 0; right: 0; bottom: 0;
+// background: rgba(0,0,0,0.4);
+// display: flex;
+// justify-content: center;
+// align-items: center;
+// z-index: 999;
+// }
+// .modal-content {
+// background: white;
+// padding: 20px;
+// border-radius: 6px;
+// width: 320px;
+// max-width: 90%;
+// }
+// .modal-content label {
+// display: block;
+// margin-bottom: 10px;
+// font-size: 14px;
+// }
+// .modal-content input[type="text"],
+// .modal-content input[type="number"],
+// .modal-content input[type="datetime-local"],
+// .modal-content textarea {
+// width: 100%;
+// box-sizing: border-box;
+// padding: 5px;
+// font-size: 14px;
+// margin-top: 4px;
+// }
+// .modal-buttons {
+// margin-top: 15px;
+// text-align: right;
+// }
+// .modal-buttons button {
+// margin-left: 10px;
+// padding: 6px 12px;
+// font-size: 14px;
+// }
+// `}</style>
+// </div>
+// );
+// };
+
+// export default Promotion;
+
+
+// import React, { useEffect, useState, useRef } from 'react';
+// import './Promotion.css';
+// import { useUser } from '../../../context/UserContext';
+
+// 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);
+
+// 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();
+// const promoData = Array.isArray(json?.data) ? json.data : [];
+// setPromotions(promoData);
+// } catch (error) {
+// console.error('获取促销活动失败:', error);
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// // 获取种子列表,赋值给torrents
+// const fetchTorrentList = async () => {
+// try {
+// const response = await fetch('/seeds/list');
+// const json = await response.json();
+// const torrentList = Array.isArray(json?.data) ? json.data : [];
+// setTorrents(torrentList);
+// } catch (error) {
+// console.error('获取种子列表失败:', error);
+// }
+// };
+
+// // 创建促销时,自动使用当前种子的id列表,而不是写死
+// const handleCreatePromotion = async () => {
+// if (torrents.length === 0) {
+// alert('没有可用的种子,请先上传种子');
+// return;
+// }
+
+// const applicableTorrentIds = torrents.map(t => t.id); // 获取所有种子id数组
+
+// const newPromo = {
+// name: '测试促销活动',
+// startTime: new Date().toISOString(),
+// endTime: new Date(Date.now() + 7 * 86400000).toISOString(),
+// discountPercentage: 20,
+// applicableTorrentIds: applicableTorrentIds, // 动态传入种子ID数组
+// description: '这是一个测试促销活动'
+// };
+
+// 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 === 200) {
+// alert('促销活动创建成功');
+// fetchData();
+// } 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.success) {
+// alert('删除成功');
+// fetchData();
+// } else {
+// alert('删除失败: ' + json.message);
+// }
+// } 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];
+
+// if (loading) {
+// return <div className="promotion-container">加载中...</div>;
+// }
+
+// return (
+// <div className="promotion-container carousel-container">
+// <section className="carousel-section">
+// <h2>当前促销活动</h2>
+
+// {isAdmin && (
+// <button className="create-btn" onClick={handleCreatePromotion}>
+// 创建促销活动
+// </button>
+// )}
+
+// {promotions.length === 0 || !currentPromo ? (
+// <div className="empty-state">暂无促销活动</div>
+// ) : (
+// <div
+// className="carousel"
+// onMouseEnter={() => clearInterval(promoTimerRef.current)}
+// onMouseLeave={() => {
+// promoTimerRef.current = setInterval(() => {
+// setPromoIndex(prev => (prev + 1) % promotions.length);
+// }, 3000);
+// }}
+// >
+// <button className="arrow left" onClick={prevPromo}><</button>
+// <div className="slide">
+// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
+// <div><strong>促销时间:</strong>
+// {currentPromo?.pStartTime && currentPromo?.pEndTime
+// ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
+// : '未知'}
+// </div>
+// <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
+// <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
+// {currentPromo?.description && (
+// <div><strong>描述:</strong>{currentPromo.description}</div>
+// )}
+// {isAdmin && (
+// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
+// 删除该活动
+// </button>
+// )}
+// </div>
+// <button className="arrow right" onClick={nextPromo}>></button>
+// </div>
+// )}
+// </section>
+// </div>
+// );
+// };
+
+// export default Promotion;
+