blob: 953a37f11584bca1040d15f5e14dc01d8f627d8f [file] [log] [blame]
Krishya73cd8822025-06-07 15:48:41 +08001// import React, { useEffect, useState, useRef } from 'react';
2// import './Promotion.css';
3// import { useUser } from '../../../context/UserContext';
4
5// const Promotion = () => {
6// const { user } = useUser();
7// const [promotions, setPromotions] = useState([]);
8// const [torrents, setTorrents] = useState([]);
9// const [loading, setLoading] = useState(true);
10// const [promoIndex, setPromoIndex] = useState(0);
11// const promoTimerRef = useRef(null);
12
13// // 新增:控制创建对话框显示
14// const [showCreateDialog, setShowCreateDialog] = useState(false);
15
16// // 创建促销活动表单状态
17// const [formData, setFormData] = useState({
18// name: '',
19// startTime: '',
20// endTime: '',
21// discountPercentage: '',
22// uploadCoeff: '',
23// downloadCoeff: '',
24// description: ''
25// });
26
27// useEffect(() => {
28// fetchData();
29// fetchTorrentList();
30// }, []);
31
32// useEffect(() => {
33// if (promotions.length === 0) return;
34// clearInterval(promoTimerRef.current);
35// promoTimerRef.current = setInterval(() => {
36// setPromoIndex(prev => (prev + 1) % promotions.length);
37// }, 5000);
38// return () => clearInterval(promoTimerRef.current);
39// }, [promotions]);
40
41// const fetchData = async () => {
42// try {
43// const response = await fetch('/seeds/promotions');
44// const json = await response.json();
45// const promoData = Array.isArray(json?.data) ? json.data : [];
46// setPromotions(promoData);
47// } catch (error) {
48// console.error('获取促销活动失败:', error);
49// } finally {
50// setLoading(false);
51// }
52// };
53
54// const fetchTorrentList = async () => {
55// try {
56// const response = await fetch('/seeds/list');
57// const json = await response.json();
58// const torrentList = Array.isArray(json?.data) ? json.data : [];
59// setTorrents(torrentList);
60// } catch (error) {
61// console.error('获取种子列表失败:', error);
62// }
63// };
64
65// // 打开创建促销活动弹窗
66// const openCreateDialog = () => {
67// // 重置表单数据
68// setFormData({
69// name: '',
70// startTime: '',
71// endTime: '',
72// discountPercentage: '',
73// uploadCoeff: '',
74// downloadCoeff: '',
75// description: ''
76// });
77// setShowCreateDialog(true);
78// };
79
80// // 关闭弹窗
81// const closeCreateDialog = () => {
82// setShowCreateDialog(false);
83// };
84
85// // 处理表单输入变化
86// const handleInputChange = (e) => {
87// const { name, value } = e.target;
88// setFormData(prev => ({
89// ...prev,
90// [name]: value
91// }));
92// };
93
94// // 提交创建促销活动
95// const handleCreatePromotion = async () => {
96// if (torrents.length === 0) {
97// alert('没有可用的种子,请先上传种子');
98// return;
99// }
100// if (!formData.name.trim()) {
101// alert('促销名称不能为空');
102// return;
103// }
104// if (!formData.startTime || !formData.endTime) {
105// alert('促销开始时间和结束时间不能为空');
106// return;
107// }
108// if (new Date(formData.startTime) >= new Date(formData.endTime)) {
109// alert('促销结束时间必须晚于开始时间');
110// return;
111// }
112// if (!formData.discountPercentage || isNaN(formData.discountPercentage)) {
113// alert('折扣百分比必须是数字');
114// return;
115// }
116
117// const applicableTorrentIds = torrents.map(t => t.id);
118
119// const newPromo = {
120// name: formData.name,
121// startTime: new Date(formData.startTime).toISOString(),
122// endTime: new Date(formData.endTime).toISOString(),
123// discountPercentage: Number(formData.discountPercentage),
124// uploadCoeff: formData.uploadCoeff ? Number(formData.uploadCoeff) : undefined,
125// downloadCoeff: formData.downloadCoeff ? Number(formData.downloadCoeff) : undefined,
126// applicableTorrentIds: JSON.stringify(applicableTorrentIds), // ✅ 关键修改
127// description: formData.description
128// };
129
130
131// try {
132// const res = await fetch('/seeds/promotions', {
133// method: 'POST',
134// headers: { 'Content-Type': 'application/json' },
135// body: JSON.stringify(newPromo)
136// });
137// const json = await res.json();
138// if (json.code === 200) {
139// alert('促销活动创建成功');
140// fetchData();
141// setShowCreateDialog(false);
142// } else {
143// alert('创建失败: ' + (json.msg || '未知错误'));
144// }
145// } catch (err) {
146// console.error('创建促销失败:', err);
147// alert('创建促销失败');
148// }
149// };
150
151// const handleDeletePromotion = async (promotionId) => {
152// if (!window.confirm('确认删除该促销活动吗?')) return;
153
154// try {
155// const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
156// const json = await res.json();
157// if (json.success) {
158// alert('删除成功');
159// fetchData();
160// } else {
161// alert('删除失败: ' + json.message);
162// }
163// } catch (err) {
164// console.error('删除失败:', err);
165// }
166// };
167
168// const isAdmin = user?.role === 'admin';
169// const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
170// const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
171// const currentPromo = promotions[promoIndex];
172
173// if (loading) {
174// return <div className="promotion-container">加载中...</div>;
175// }
176
177// return (
178// <div className="promotion-container carousel-container">
179// <section className="carousel-section">
180// <h2>当前促销活动</h2>
181
182// {isAdmin && (
183// <button className="create-btn" onClick={openCreateDialog}>
184// 创建促销活动
185// </button>
186// )}
187
188// {promotions.length === 0 || !currentPromo ? (
189// <div className="empty-state">暂无促销活动</div>
190// ) : (
191// <div
192// className="carousel"
193// onMouseEnter={() => clearInterval(promoTimerRef.current)}
194// onMouseLeave={() => {
195// promoTimerRef.current = setInterval(() => {
196// setPromoIndex(prev => (prev + 1) % promotions.length);
197// }, 3000);
198// }}
199// >
200// <button className="arrow left" onClick={prevPromo}>&lt;</button>
201// <div className="slide">
202// <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
203// <div><strong>促销时间:</strong>
204// {currentPromo?.pStartTime && currentPromo?.pEndTime
205// ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
206// : '未知'}
207// </div>
208// <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
209// <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
210// {currentPromo?.description && (
211// <div><strong>描述:</strong>{currentPromo.description}</div>
212// )}
213// {isAdmin && (
214// <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
215// 删除该活动
216// </button>
217// )}
218// </div>
219// <button className="arrow right" onClick={nextPromo}>&gt;</button>
220// </div>
221// )}
222// </section>
223
224// {/* 创建促销活动弹窗 */}
225// {showCreateDialog && (
226// <div className="dialog-overlay">
227// <div className="dialog">
228// <h3>创建促销活动</h3>
229// <div className="form-item">
230// <label>促销名称:</label>
231// <input
232// type="text"
233// name="name"
234// value={formData.name}
235// onChange={handleInputChange}
236// placeholder="请输入促销名称"
237// />
238// </div>
239// <div className="form-item">
240// <label>开始时间:</label>
241// <input
242// type="datetime-local"
243// name="startTime"
244// value={formData.startTime}
245// onChange={handleInputChange}
246// />
247// </div>
248// <div className="form-item">
249// <label>结束时间:</label>
250// <input
251// type="datetime-local"
252// name="endTime"
253// value={formData.endTime}
254// onChange={handleInputChange}
255// />
256// </div>
257// <div className="form-item">
258// <label>折扣百分比(数字):</label>
259// <input
260// type="number"
261// name="discountPercentage"
262// value={formData.discountPercentage}
263// onChange={handleInputChange}
264// placeholder="例如:20 表示 20% 折扣"
265// min="0"
266// max="100"
267// />
268// </div>
269// <div className="form-item">
270// <label>上传奖励系数(可选):</label>
271// <input
272// type="number"
273// name="uploadCoeff"
274// value={formData.uploadCoeff}
275// onChange={handleInputChange}
276// placeholder="例如:1.5"
277// step="0.1"
278// />
279// </div>
280// <div className="form-item">
281// <label>下载折扣系数(可选):</label>
282// <input
283// type="number"
284// name="downloadCoeff"
285// value={formData.downloadCoeff}
286// onChange={handleInputChange}
287// placeholder="例如:0.8"
288// step="0.1"
289// />
290// </div>
291// <div className="form-item">
292// <label>描述(可选):</label>
293// <textarea
294// name="description"
295// value={formData.description}
296// onChange={handleInputChange}
297// placeholder="促销活动描述"
298// rows={3}
299// />
300// </div>
301// <div className="dialog-buttons">
302// <button onClick={handleCreatePromotion}>确定</button>
303// <button onClick={closeCreateDialog}>取消</button>
304// </div>
305// </div>
306// </div>
307// )}
308// </div>
309// );
310// };
311
312// export default Promotion;
313
314
315// // import React, { useEffect, useState, useRef } from 'react';
316// // import './Promotion.css';
317// // import { useUser } from '../../../context/UserContext';
318
319// // const Promotion = () => {
320// // const { user } = useUser();
321// // const [promotions, setPromotions] = useState([]);
322// // const [torrents, setTorrents] = useState([]);
323// // const [loading, setLoading] = useState(true);
324// // const [promoIndex, setPromoIndex] = useState(0);
325// // const promoTimerRef = useRef(null);
326
327// // // 新增:控制模态框显示与表单状态
328// // const [showCreateModal, setShowCreateModal] = useState(false);
329// // const [formData, setFormData] = useState({
330// // name: '',
331// // description: '',
332// // discountPercentage: 0,
333// // startTime: '',
334// // endTime: '',
335// // applicableTorrentIds: [],
336// // });
337
338// // useEffect(() => {
339// // fetchData();
340// // fetchTorrentList();
341// // }, []);
342
343// // useEffect(() => {
344// // if (promotions.length === 0) return;
345// // clearInterval(promoTimerRef.current);
346// // promoTimerRef.current = setInterval(() => {
347// // setPromoIndex(prev => (prev + 1) % promotions.length);
348// // }, 5000);
349// // return () => clearInterval(promoTimerRef.current);
350// // }, [promotions]);
351
352// // const fetchData = async () => {
353// // try {
354// // const response = await fetch('/seeds/promotions');
355// // const json = await response.json();
356// // const promoData = Array.isArray(json?.data) ? json.data : [];
357// // setPromotions(promoData);
358// // } catch (error) {
359// // console.error('获取促销活动失败:', error);
360// // } finally {
361// // setLoading(false);
362// // }
363// // };
364
365// // const fetchTorrentList = async () => {
366// // try {
367// // const response = await fetch('/seeds/list');
368// // const json = await response.json();
369// // const torrentList = Array.isArray(json?.data) ? json.data : [];
370// // setTorrents(torrentList);
371// // } catch (error) {
372// // console.error('获取种子列表失败:', error);
373// // }
374// // };
375
376// // // 打开模态框时,重置表单数据,默认设置时间并填入所有种子ID
377// // const openCreateModal = () => {
378// // if (torrents.length === 0) {
379// // alert('没有可用的种子,请先上传种子');
380// // return;
381// // }
382// // setFormData({
383// // name: '',
384// // description: '',
385// // discountPercentage: 20,
386// // startTime: new Date().toISOString().slice(0, 16), // 用于datetime-local输入框,格式 YYYY-MM-DDTHH:mm
387// // endTime: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 16),
388// // applicableTorrentIds: torrents.map(t => t.id),
389// // });
390// // setShowCreateModal(true);
391// // };
392
393// // // 表单输入处理
394// // const handleInputChange = (e) => {
395// // const { name, value } = e.target;
396// // setFormData(prev => ({
397// // ...prev,
398// // [name]: name === 'discountPercentage' ? Number(value) : value,
399// // }));
400// // };
401
402// // // 点击确定提交创建
403// // const handleCreateConfirm = async () => {
404// // if (!formData.name) {
405// // alert('促销名称不能为空');
406// // return;
407// // }
408// // if (!formData.startTime || !formData.endTime) {
409// // alert('请选择开始时间和结束时间');
410// // return;
411// // }
412// // if (formData.discountPercentage <= 0 || formData.discountPercentage >= 100) {
413// // alert('折扣百分比应在1-99之间');
414// // return;
415// // }
416// // if (!formData.applicableTorrentIds.length) {
417// // alert('请选择适用的种子');
418// // return;
419// // }
420
421// // // 准备发送数据,适配后端字段名
422// // const newPromo = {
423// // name: formData.name,
424// // description: formData.description,
425// // discountPercentage: formData.discountPercentage,
426// // startTime: new Date(formData.startTime).toISOString(),
427// // endTime: new Date(formData.endTime).toISOString(),
428// // applicableTorrentIds: formData.applicableTorrentIds,
429// // };
430
431// // try {
432// // const res = await fetch('/seeds/promotions', {
433// // method: 'POST',
434// // headers: { 'Content-Type': 'application/json' },
435// // body: JSON.stringify(newPromo),
436// // });
437// // const json = await res.json();
438// // if (json.code === 200) {
439// // alert('促销活动创建成功');
440// // setShowCreateModal(false);
441// // fetchData();
442// // } else {
443// // alert('创建失败: ' + (json.msg || '未知错误'));
444// // }
445// // } catch (err) {
446// // console.error('创建促销失败:', err);
447// // alert('创建促销失败');
448// // }
449// // };
450
451// // const handleCancel = () => {
452// // setShowCreateModal(false);
453// // };
454
455// // const handleDeletePromotion = async (promotionId) => {
456// // if (!window.confirm('确认删除该促销活动吗?')) return;
457
458// // try {
459// // const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
460// // const json = await res.json();
461// // if (json.success) {
462// // alert('删除成功');
463// // fetchData();
464// // } else {
465// // alert('删除失败: ' + json.message);
466// // }
467// // } catch (err) {
468// // console.error('删除失败:', err);
469// // }
470// // };
471
472// // const isAdmin = user?.role === 'admin';
473// // const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
474// // const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
475// // const currentPromo = promotions[promoIndex];
476
477// // if (loading) {
478// // return <div className="promotion-container">加载中...</div>;
479// // }
480
481// // return (
482// // <div className="promotion-container carousel-container">
483// // <section className="carousel-section">
484// // <h2>当前促销活动</h2>
485
486// // {isAdmin && (
487// // <button className="create-btn" onClick={openCreateModal}>
488// // 创建促销活动
489// // </button>
490// // )}
491
492// // {promotions.length === 0 || !currentPromo ? (
493// // <div className="empty-state">暂无促销活动</div>
494// // ) : (
495// // <div
496// // className="carousel"
497// // onMouseEnter={() => clearInterval(promoTimerRef.current)}
498// // onMouseLeave={() => {
499// // promoTimerRef.current = setInterval(() => {
500// // setPromoIndex(prev => (prev + 1) % promotions.length);
501// // }, 3000);
502// // }}
503// // >
504// // <button className="arrow left" onClick={prevPromo}>&lt;</button>
505// // <div className="slide">
506// // <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
507// // <div><strong>促销时间:</strong>
508// // {currentPromo?.startTime && currentPromo?.endTime
509// // ? `${new Date(currentPromo.startTime).toLocaleString()} ~ ${new Date(currentPromo.endTime).toLocaleString()}`
510// // : '未知'}
511// // </div>
512// // <div><strong>折扣百分比:</strong>{currentPromo?.discountPercentage ?? '无'}</div>
513// // {currentPromo?.description && (
514// // <div><strong>描述:</strong>{currentPromo.description}</div>
515// // )}
516// // {isAdmin && (
517// // <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
518// // 删除该活动
519// // </button>
520// // )}
521// // </div>
522// // <button className="arrow right" onClick={nextPromo}>&gt;</button>
523// // </div>
524// // )}
525// // </section>
526
527// // {/* 创建促销模态框 */}
528// // {showCreateModal && (
529// // <div className="modal-overlay">
530// // <div className="modal-content">
531// // <h3>创建促销活动</h3>
532// // <label>
533// // 促销名称:
534// // <input
535// // type="text"
536// // name="name"
537// // value={formData.name}
538// // onChange={handleInputChange}
539// // />
540// // </label>
541// // <label>
542// // 描述:
543// // <textarea
544// // name="description"
545// // value={formData.description}
546// // onChange={handleInputChange}
547// // rows={3}
548// // />
549// // </label>
550// // <label>
551// // 折扣百分比:
552// // <input
553// // type="number"
554// // name="discountPercentage"
555// // value={formData.discountPercentage}
556// // min={1}
557// // max={99}
558// // onChange={handleInputChange}
559// // />
560// // </label>
561// // <label>
562// // 开始时间:
563// // <input
564// // type="datetime-local"
565// // name="startTime"
566// // value={formData.startTime}
567// // onChange={handleInputChange}
568// // />
569// // </label>
570// // <label>
571// // 结束时间:
572// // <input
573// // type="datetime-local"
574// // name="endTime"
575// // value={formData.endTime}
576// // onChange={handleInputChange}
577// // />
578// // </label>
579// // <label>
580// // 适用种子ID(逗号分隔,可留空默认所有):
581// // <input
582// // type="text"
583// // name="applicableTorrentIds"
584// // value={formData.applicableTorrentIds.join(',')}
585// // onChange={(e) => {
586// // const ids = e.target.value
587// // .split(',')
588// // .map(id => id.trim())
589// // .filter(id => id !== '')
590// // .map(id => Number(id))
591// // .filter(id => !isNaN(id));
592// // setFormData(prev => ({ ...prev, applicableTorrentIds: ids }));
593// // }}
594// // />
595// // </label>
596
597// // <div className="modal-buttons">
598// // <button onClick={handleCreateConfirm}>确定</button>
599// // <button onClick={handleCancel}>取消</button>
600// // </div>
601// // </div>
602// // </div>
603// // )}
604
605// // {/* 模态框简单样式 */}
606// // <style>{`
607// // .modal-overlay {
608// // position: fixed;
609// // top: 0; left: 0; right: 0; bottom: 0;
610// // background: rgba(0,0,0,0.4);
611// // display: flex;
612// // justify-content: center;
613// // align-items: center;
614// // z-index: 999;
615// // }
616// // .modal-content {
617// // background: white;
618// // padding: 20px;
619// // border-radius: 6px;
620// // width: 320px;
621// // max-width: 90%;
622// // }
623// // .modal-content label {
624// // display: block;
625// // margin-bottom: 10px;
626// // font-size: 14px;
627// // }
628// // .modal-content input[type="text"],
629// // .modal-content input[type="number"],
630// // .modal-content input[type="datetime-local"],
631// // .modal-content textarea {
632// // width: 100%;
633// // box-sizing: border-box;
634// // padding: 5px;
635// // font-size: 14px;
636// // margin-top: 4px;
637// // }
638// // .modal-buttons {
639// // margin-top: 15px;
640// // text-align: right;
641// // }
642// // .modal-buttons button {
643// // margin-left: 10px;
644// // padding: 6px 12px;
645// // font-size: 14px;
646// // }
647// // `}</style>
648// // </div>
649// // );
650// // };
651
652// // export default Promotion;
653
654
655// // import React, { useEffect, useState, useRef } from 'react';
656// // import './Promotion.css';
657// // import { useUser } from '../../../context/UserContext';
658
659// // const Promotion = () => {
660// // const { user } = useUser();
661// // const [promotions, setPromotions] = useState([]);
662// // const [torrents, setTorrents] = useState([]); // 新增,存放种子列表
663// // const [loading, setLoading] = useState(true);
664// // const [promoIndex, setPromoIndex] = useState(0);
665// // const promoTimerRef = useRef(null);
666
667// // useEffect(() => {
668// // fetchData();
669// // fetchTorrentList(); // 新增,获取种子列表
670// // }, []);
671
672// // useEffect(() => {
673// // if (promotions.length === 0) return;
674// // clearInterval(promoTimerRef.current);
675// // promoTimerRef.current = setInterval(() => {
676// // setPromoIndex(prev => (prev + 1) % promotions.length);
677// // }, 5000);
678// // return () => clearInterval(promoTimerRef.current);
679// // }, [promotions]);
680
681// // // 获取促销数据
682// // const fetchData = async () => {
683// // try {
684// // const response = await fetch('/seeds/promotions');
685// // const json = await response.json();
686// // const promoData = Array.isArray(json?.data) ? json.data : [];
687// // setPromotions(promoData);
688// // } catch (error) {
689// // console.error('获取促销活动失败:', error);
690// // } finally {
691// // setLoading(false);
692// // }
693// // };
694
695// // // 获取种子列表,赋值给torrents
696// // const fetchTorrentList = async () => {
697// // try {
698// // const response = await fetch('/seeds/list');
699// // const json = await response.json();
700// // const torrentList = Array.isArray(json?.data) ? json.data : [];
701// // setTorrents(torrentList);
702// // } catch (error) {
703// // console.error('获取种子列表失败:', error);
704// // }
705// // };
706
707// // // 创建促销时,自动使用当前种子的id列表,而不是写死
708// // const handleCreatePromotion = async () => {
709// // if (torrents.length === 0) {
710// // alert('没有可用的种子,请先上传种子');
711// // return;
712// // }
713
714// // const applicableTorrentIds = torrents.map(t => t.id); // 获取所有种子id数组
715
716// // const newPromo = {
717// // name: '测试促销活动',
718// // startTime: new Date().toISOString(),
719// // endTime: new Date(Date.now() + 7 * 86400000).toISOString(),
720// // discountPercentage: 20,
721// // applicableTorrentIds: applicableTorrentIds, // 动态传入种子ID数组
722// // description: '这是一个测试促销活动'
723// // };
724
725// // try {
726// // const res = await fetch('/seeds/promotions', {
727// // method: 'POST',
728// // headers: {
729// // 'Content-Type': 'application/json'
730// // },
731// // body: JSON.stringify(newPromo)
732// // });
733// // const json = await res.json();
734// // if (json.code === 200) {
735// // alert('促销活动创建成功');
736// // fetchData();
737// // } else {
738// // alert('创建失败: ' + (json.msg || '未知错误'));
739// // }
740// // } catch (err) {
741// // console.error('创建促销失败:', err);
742// // alert('创建促销失败');
743// // }
744// // };
745
746// // const handleDeletePromotion = async (promotionId) => {
747// // if (!window.confirm('确认删除该促销活动吗?')) return;
748
749// // try {
750// // const res = await fetch(`/seeds/promotions/${promotionId}`, {
751// // method: 'DELETE'
752// // });
753// // const json = await res.json();
754// // if (json.success) {
755// // alert('删除成功');
756// // fetchData();
757// // } else {
758// // alert('删除失败: ' + json.message);
759// // }
760// // } catch (err) {
761// // console.error('删除失败:', err);
762// // }
763// // };
764
765// // const isAdmin = user?.role === 'admin';
766// // const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
767// // const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
768// // const currentPromo = promotions[promoIndex];
769
770// // if (loading) {
771// // return <div className="promotion-container">加载中...</div>;
772// // }
773
774// // return (
775// // <div className="promotion-container carousel-container">
776// // <section className="carousel-section">
777// // <h2>当前促销活动</h2>
778
779// // {isAdmin && (
780// // <button className="create-btn" onClick={handleCreatePromotion}>
781// // 创建促销活动
782// // </button>
783// // )}
784
785// // {promotions.length === 0 || !currentPromo ? (
786// // <div className="empty-state">暂无促销活动</div>
787// // ) : (
788// // <div
789// // className="carousel"
790// // onMouseEnter={() => clearInterval(promoTimerRef.current)}
791// // onMouseLeave={() => {
792// // promoTimerRef.current = setInterval(() => {
793// // setPromoIndex(prev => (prev + 1) % promotions.length);
794// // }, 3000);
795// // }}
796// // >
797// // <button className="arrow left" onClick={prevPromo}>&lt;</button>
798// // <div className="slide">
799// // <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
800// // <div><strong>促销时间:</strong>
801// // {currentPromo?.pStartTime && currentPromo?.pEndTime
802// // ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
803// // : '未知'}
804// // </div>
805// // <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
806// // <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
807// // {currentPromo?.description && (
808// // <div><strong>描述:</strong>{currentPromo.description}</div>
809// // )}
810// // {isAdmin && (
811// // <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
812// // 删除该活动
813// // </button>
814// // )}
815// // </div>
816// // <button className="arrow right" onClick={nextPromo}>&gt;</button>
817// // </div>
818// // )}
819// // </section>
820// // </div>
821// // );
822// // };
823
824// // export default Promotion;
825
826
Krishyaf1d0ea82025-05-03 17:01:58 +0800827import React, { useEffect, useState, useRef } from 'react';
828import './Promotion.css';
Krishya6bf199c2025-06-06 21:14:23 +0800829import { useUser } from '../../../context/UserContext';
Krishyaf1d0ea82025-05-03 17:01:58 +0800830
Krishyaf1d0ea82025-05-03 17:01:58 +0800831const Promotion = () => {
Krishya6bf199c2025-06-06 21:14:23 +0800832 const { user } = useUser();
Krishyaf1d0ea82025-05-03 17:01:58 +0800833 const [promotions, setPromotions] = useState([]);
Krishya6bf199c2025-06-06 21:14:23 +0800834 const [torrents, setTorrents] = useState([]);
Krishyaf1d0ea82025-05-03 17:01:58 +0800835 const [loading, setLoading] = useState(true);
Krishyaf1d0ea82025-05-03 17:01:58 +0800836 const [promoIndex, setPromoIndex] = useState(0);
Krishyaf1d0ea82025-05-03 17:01:58 +0800837 const promoTimerRef = useRef(null);
Krishyaf1d0ea82025-05-03 17:01:58 +0800838
Krishya6bf199c2025-06-06 21:14:23 +0800839 // 新增:控制创建对话框显示
840 const [showCreateDialog, setShowCreateDialog] = useState(false);
841
842 // 创建促销活动表单状态
843 const [formData, setFormData] = useState({
844 name: '',
845 startTime: '',
846 endTime: '',
847 discountPercentage: '',
848 uploadCoeff: '',
849 downloadCoeff: '',
850 description: ''
851 });
852
Krishyaf1d0ea82025-05-03 17:01:58 +0800853 useEffect(() => {
854 fetchData();
Krishya6bf199c2025-06-06 21:14:23 +0800855 fetchTorrentList();
Krishyaf1d0ea82025-05-03 17:01:58 +0800856 }, []);
857
Krishyaf1d0ea82025-05-03 17:01:58 +0800858 useEffect(() => {
859 if (promotions.length === 0) return;
Krishyaf1d0ea82025-05-03 17:01:58 +0800860 clearInterval(promoTimerRef.current);
861 promoTimerRef.current = setInterval(() => {
862 setPromoIndex(prev => (prev + 1) % promotions.length);
863 }, 5000);
864 return () => clearInterval(promoTimerRef.current);
865 }, [promotions]);
866
Krishyaf1d0ea82025-05-03 17:01:58 +0800867 const fetchData = async () => {
868 try {
Krishya6bf199c2025-06-06 21:14:23 +0800869 const response = await fetch('/seeds/promotions');
870 const json = await response.json();
871 const promoData = Array.isArray(json?.data) ? json.data : [];
Krishyaf1d0ea82025-05-03 17:01:58 +0800872 setPromotions(promoData);
Krishyaf1d0ea82025-05-03 17:01:58 +0800873 } catch (error) {
Krishya8f2fec82025-06-04 21:54:46 +0800874 console.error('获取促销活动失败:', error);
Krishyaf1d0ea82025-05-03 17:01:58 +0800875 } finally {
876 setLoading(false);
877 }
878 };
879
Krishya6bf199c2025-06-06 21:14:23 +0800880 const fetchTorrentList = async () => {
881 try {
882 const response = await fetch('/seeds/list');
883 const json = await response.json();
884 const torrentList = Array.isArray(json?.data) ? json.data : [];
885 setTorrents(torrentList);
886 } catch (error) {
887 console.error('获取种子列表失败:', error);
888 }
889 };
890
891 // 打开创建促销活动弹窗
892 const openCreateDialog = () => {
893 // 重置表单数据
894 setFormData({
895 name: '',
896 startTime: '',
897 endTime: '',
898 discountPercentage: '',
899 uploadCoeff: '',
900 downloadCoeff: '',
901 description: ''
902 });
903 setShowCreateDialog(true);
904 };
905
906 // 关闭弹窗
907 const closeCreateDialog = () => {
908 setShowCreateDialog(false);
909 };
910
911 // 处理表单输入变化
912 const handleInputChange = (e) => {
913 const { name, value } = e.target;
914 setFormData(prev => ({
915 ...prev,
916 [name]: value
917 }));
918 };
919
920 // 提交创建促销活动
921 const handleCreatePromotion = async () => {
922 if (torrents.length === 0) {
923 alert('没有可用的种子,请先上传种子');
924 return;
925 }
926 if (!formData.name.trim()) {
927 alert('促销名称不能为空');
928 return;
929 }
930 if (!formData.startTime || !formData.endTime) {
931 alert('促销开始时间和结束时间不能为空');
932 return;
933 }
934 if (new Date(formData.startTime) >= new Date(formData.endTime)) {
935 alert('促销结束时间必须晚于开始时间');
936 return;
937 }
938 if (!formData.discountPercentage || isNaN(formData.discountPercentage)) {
939 alert('折扣百分比必须是数字');
940 return;
941 }
942
943 const applicableTorrentIds = torrents.map(t => t.id);
944
945 const newPromo = {
946 name: formData.name,
947 startTime: new Date(formData.startTime).toISOString(),
948 endTime: new Date(formData.endTime).toISOString(),
949 discountPercentage: Number(formData.discountPercentage),
950 uploadCoeff: formData.uploadCoeff ? Number(formData.uploadCoeff) : undefined,
951 downloadCoeff: formData.downloadCoeff ? Number(formData.downloadCoeff) : undefined,
Krishya73cd8822025-06-07 15:48:41 +0800952 applicableTorrentIds: applicableTorrentIds,
Krishya6bf199c2025-06-06 21:14:23 +0800953 description: formData.description
954 };
955
956
957 try {
958 const res = await fetch('/seeds/promotions', {
959 method: 'POST',
960 headers: { 'Content-Type': 'application/json' },
961 body: JSON.stringify(newPromo)
962 });
963 const json = await res.json();
Krishya73cd8822025-06-07 15:48:41 +0800964 if (json.code === 200 || json.code === 0) {
Krishya6bf199c2025-06-06 21:14:23 +0800965 alert('促销活动创建成功');
966 fetchData();
967 setShowCreateDialog(false);
968 } else {
969 alert('创建失败: ' + (json.msg || '未知错误'));
970 }
971 } catch (err) {
972 console.error('创建促销失败:', err);
973 alert('创建促销失败');
974 }
975 };
976
977 const handleDeletePromotion = async (promotionId) => {
978 if (!window.confirm('确认删除该促销活动吗?')) return;
979
980 try {
981 const res = await fetch(`/seeds/promotions/${promotionId}`, { method: 'DELETE' });
982 const json = await res.json();
Krishya73cd8822025-06-07 15:48:41 +0800983 if (json.code === 0 || json.code === 200) {
Krishya6bf199c2025-06-06 21:14:23 +0800984 alert('删除成功');
985 fetchData();
986 } else {
Krishya73cd8822025-06-07 15:48:41 +0800987 alert('删除失败: ' + (json.msg || '未知错误'));
Krishya6bf199c2025-06-06 21:14:23 +0800988 }
989 } catch (err) {
990 console.error('删除失败:', err);
991 }
992 };
993
994 const isAdmin = user?.role === 'admin';
995 const prevPromo = () => setPromoIndex((promoIndex - 1 + promotions.length) % promotions.length);
996 const nextPromo = () => setPromoIndex((promoIndex + 1) % promotions.length);
997 const currentPromo = promotions[promoIndex];
998
Krishyaf1d0ea82025-05-03 17:01:58 +0800999 if (loading) {
1000 return <div className="promotion-container">加载中...</div>;
1001 }
1002
Krishyaf1d0ea82025-05-03 17:01:58 +08001003 return (
1004 <div className="promotion-container carousel-container">
Krishyaf1d0ea82025-05-03 17:01:58 +08001005 <section className="carousel-section">
1006 <h2>当前促销活动</h2>
Krishya6bf199c2025-06-06 21:14:23 +08001007
1008 {isAdmin && (
1009 <button className="create-btn" onClick={openCreateDialog}>
1010 创建促销活动
1011 </button>
1012 )}
1013
Krishya2283d882025-05-27 22:25:19 +08001014 {promotions.length === 0 || !currentPromo ? (
Krishyaf1d0ea82025-05-03 17:01:58 +08001015 <div className="empty-state">暂无促销活动</div>
1016 ) : (
1017 <div
1018 className="carousel"
1019 onMouseEnter={() => clearInterval(promoTimerRef.current)}
1020 onMouseLeave={() => {
1021 promoTimerRef.current = setInterval(() => {
1022 setPromoIndex(prev => (prev + 1) % promotions.length);
1023 }, 3000);
1024 }}
1025 >
1026 <button className="arrow left" onClick={prevPromo}>&lt;</button>
1027 <div className="slide">
Krishya2e0f49a2025-05-29 10:59:01 +08001028 <div><strong>促销名称:</strong>{currentPromo?.name ?? '未知'}</div>
Krishyaf1d0ea82025-05-03 17:01:58 +08001029 <div><strong>促销时间:</strong>
Krishya2e0f49a2025-05-29 10:59:01 +08001030 {currentPromo?.pStartTime && currentPromo?.pEndTime
1031 ? `${new Date(currentPromo.pStartTime).toLocaleString()} ~ ${new Date(currentPromo.pEndTime).toLocaleString()}`
Krishya2283d882025-05-27 22:25:19 +08001032 : '未知'}
Krishyaf1d0ea82025-05-03 17:01:58 +08001033 </div>
Krishya2e0f49a2025-05-29 10:59:01 +08001034 <div><strong>上传奖励系数:</strong>{currentPromo?.uploadCoeff ?? '无'}</div>
1035 <div><strong>下载折扣系数:</strong>{currentPromo?.downloadCoeff ?? '无'}</div>
Krishya2283d882025-05-27 22:25:19 +08001036 {currentPromo?.description && (
Krishya2e0f49a2025-05-29 10:59:01 +08001037 <div><strong>描述:</strong>{currentPromo.description}</div>
Krishyaf1d0ea82025-05-03 17:01:58 +08001038 )}
Krishya6bf199c2025-06-06 21:14:23 +08001039 {isAdmin && (
1040 <button className="delete-btn" onClick={() => handleDeletePromotion(currentPromo.id)}>
1041 删除该活动
1042 </button>
1043 )}
Krishyaf1d0ea82025-05-03 17:01:58 +08001044 </div>
1045 <button className="arrow right" onClick={nextPromo}>&gt;</button>
1046 </div>
1047 )}
1048 </section>
Krishya6bf199c2025-06-06 21:14:23 +08001049
1050 {/* 创建促销活动弹窗 */}
1051 {showCreateDialog && (
1052 <div className="dialog-overlay">
1053 <div className="dialog">
1054 <h3>创建促销活动</h3>
1055 <div className="form-item">
1056 <label>促销名称:</label>
1057 <input
1058 type="text"
1059 name="name"
1060 value={formData.name}
1061 onChange={handleInputChange}
1062 placeholder="请输入促销名称"
1063 />
1064 </div>
1065 <div className="form-item">
1066 <label>开始时间:</label>
1067 <input
1068 type="datetime-local"
1069 name="startTime"
1070 value={formData.startTime}
1071 onChange={handleInputChange}
1072 />
1073 </div>
1074 <div className="form-item">
1075 <label>结束时间:</label>
1076 <input
1077 type="datetime-local"
1078 name="endTime"
1079 value={formData.endTime}
1080 onChange={handleInputChange}
1081 />
1082 </div>
1083 <div className="form-item">
1084 <label>折扣百分比(数字):</label>
1085 <input
1086 type="number"
1087 name="discountPercentage"
1088 value={formData.discountPercentage}
1089 onChange={handleInputChange}
1090 placeholder="例如:20 表示 20% 折扣"
1091 min="0"
1092 max="100"
1093 />
1094 </div>
1095 <div className="form-item">
1096 <label>上传奖励系数(可选):</label>
1097 <input
1098 type="number"
1099 name="uploadCoeff"
1100 value={formData.uploadCoeff}
1101 onChange={handleInputChange}
1102 placeholder="例如:1.5"
1103 step="0.1"
1104 />
1105 </div>
1106 <div className="form-item">
1107 <label>下载折扣系数(可选):</label>
1108 <input
1109 type="number"
1110 name="downloadCoeff"
1111 value={formData.downloadCoeff}
1112 onChange={handleInputChange}
1113 placeholder="例如:0.8"
1114 step="0.1"
1115 />
1116 </div>
1117 <div className="form-item">
1118 <label>描述(可选):</label>
1119 <textarea
1120 name="description"
1121 value={formData.description}
1122 onChange={handleInputChange}
1123 placeholder="促销活动描述"
1124 rows={3}
1125 />
1126 </div>
1127 <div className="dialog-buttons">
1128 <button onClick={handleCreatePromotion}>确定</button>
1129 <button onClick={closeCreateDialog}>取消</button>
1130 </div>
1131 </div>
1132 </div>
1133 )}
Krishyaf1d0ea82025-05-03 17:01:58 +08001134 </div>
1135 );
1136};
1137
1138export default Promotion;