blob: f58c73d7770f2114644080c24a5c90052fdd6c21 [file] [log] [blame]
22301009afbcf4b2025-04-10 16:08:39 +08001import React, { useEffect, useState } from 'react';
Krishya3dc6b352025-06-07 19:02:25 +08002import axios from 'axios';
22301009ecc1c1c2025-04-09 21:56:23 +08003import './Recommend.css';
Krishya3dc6b352025-06-07 19:02:25 +08004import { useUser } from '../../../context/UserContext';
223010091e2aea72025-06-08 16:35:54 +08005import { useLocation } from 'wouter';
6import toast from 'react-hot-toast';
7import CreatePlaylistModal from './CreatePlaylistModal';
8import { confirmAlert } from 'react-confirm-alert';
9import 'react-confirm-alert/src/react-confirm-alert.css';
22301009ecc1c1c2025-04-09 21:56:23 +080010
22301009ecc1c1c2025-04-09 21:56:23 +080011const Recommend = () => {
Krishya3dc6b352025-06-07 19:02:25 +080012 const { user } = useUser();
223010091e2aea72025-06-08 16:35:54 +080013 const [paidLists, setPaidLists] = useState([]);
Krishya3dc6b352025-06-07 19:02:25 +080014 const [popularSeeds, setPopularSeeds] = useState([]);
223010091e2aea72025-06-08 16:35:54 +080015 const [recommendedSeeds, setRecommendedSeeds] = useState({
16 movie: [],
17 tv: [],
18 anime: []
19 });
20
21 const [showModal, setShowModal] = useState(false);
22 const [, navigate] = useLocation();
22301009afbcf4b2025-04-10 16:08:39 +080023
24 useEffect(() => {
223010091e2aea72025-06-08 16:35:54 +080025 axios
26 .get('/playlist/page', { params: { page: 1, size: 8 } })
27 .then((res) => {
28 if (res.data.code === 0) {
29 setPaidLists(res.data.data);
30 } else {
31 toast.error(`获取片单失败:${res.data.msg}`);
32 }
33 })
34 .catch((err) => {
35 console.error('请求片单失败', err);
36 toast.error('请求片单失败,请稍后重试');
37 });
38
Krishya3dc6b352025-06-07 19:02:25 +080039 axios
40 .get('/echo/recommendation/popular', { params: { limit: 16 } })
41 .then((res) => setPopularSeeds(res.data))
223010091e2aea72025-06-08 16:35:54 +080042 .catch((err) => {
43 console.error('获取热门资源失败', err);
44 toast.error('获取热门资源失败');
45 });
22301009afbcf4b2025-04-10 16:08:39 +080046 }, []);
47
Krishya3dc6b352025-06-07 19:02:25 +080048 useEffect(() => {
223010091e2aea72025-06-08 16:35:54 +080049 if (user?.userId) {
Krishya3dc6b352025-06-07 19:02:25 +080050 axios
51 .get(`/echo/recommendation/seeds/${user.userId}`)
223010091e2aea72025-06-08 16:35:54 +080052 .then((res) => {
53 const categorized = { movie: [], tv: [], anime: [] };
54 res.data.forEach((seed) => {
55 if (seed.category === 'movie') categorized.movie.push(seed);
56 else if (seed.category === 'tv') categorized.tv.push(seed);
57 else if (seed.category === 'anime') categorized.anime.push(seed);
58 });
59 setRecommendedSeeds(categorized);
60 })
61 .catch((err) => {
62 console.error('获取个性化推荐失败', err);
63 toast.error('获取个性化推荐失败');
64 });
Krishya3dc6b352025-06-07 19:02:25 +080065 }
66 }, [user]);
67
223010091e2aea72025-06-08 16:35:54 +080068 const handleDelete = (id) => {
69 confirmAlert({
70 title: '确认删除',
71 message: '确定删除此片单吗?',
72 buttons: [
73 {
74 label: '确定',
75 onClick: async () => {
76 const toastId = toast.loading('正在删除...');
77 try {
78 await axios.delete('/playlist', {
79 params: { ids: id },
80 paramsSerializer: (params) =>
81 `ids=${Array.isArray(params.ids) ? params.ids.join(',') : params.ids}`
82 });
83 setPaidLists(paidLists.filter((list) => list.id !== id));
84 toast.success('删除成功', { id: toastId });
85 } catch (error) {
86 console.error('删除失败', error);
87 toast.error('删除失败,请稍后重试', { id: toastId });
88 }
89 }
90 },
91 { label: '取消' }
92 ]
93 });
94 };
95
96 const handlePurchase = (id) => {
97 confirmAlert({
98 title: '确认购买',
99 message: '确定支付该片单?',
100 buttons: [
101 {
102 label: '确定',
103 onClick: async () => {
104 const toastId = toast.loading('购买中...');
105 try {
106 const res = await axios.post(`/playlist/${id}/pay`);
107 if (res.data.code === 0) {
108 toast.success('购买成功', { id: toastId });
109 navigate(`/playlist/${id}`);
110 } else {
111 toast.error(`购买失败:${res.data.msg}`, { id: toastId });
112 }
113 } catch (err) {
114 console.error('支付失败', err);
115 toast.error('购买失败,请稍后重试', { id: toastId });
116 }
117 }
118 },
119 { label: '取消' }
120 ]
121 });
122 };
123
124const renderSeedCard = (seed) => (
125 <div
126 className="seed-card"
127 key={seed.id}
128 onClick={() => navigate(`/seed/${seed.id}`)}
129 style={{ cursor: 'pointer' }}
130 >
131 <img src={seed.imageUrl || '/default-cover.jpg'} alt={seed.title} />
132 <div className="title">{seed.title}</div>
133 </div>
134);
135
136
137 const renderSection = (title, seeds) => (
138 <>
139 <h2>{title}</h2>
140 <div className="seed-list">{seeds.map(renderSeedCard)}</div>
141 </>
Krishya3dc6b352025-06-07 19:02:25 +0800142 );
143
22301009ecc1c1c2025-04-09 21:56:23 +0800144 return (
Krishya3dc6b352025-06-07 19:02:25 +0800145 <div className="recommendation-page">
223010091e2aea72025-06-08 16:35:54 +0800146 <h2>💰 付费片单</h2>
147 {user && user.role === 'admin' && (
148 <button className="create-button" onClick={() => setShowModal(true)}>
149 创建片单
150 </button>
151 )}
Krishya3dc6b352025-06-07 19:02:25 +0800152
223010091e2aea72025-06-08 16:35:54 +0800153 <div className="recommend-paid-row">
154 {paidLists.map((list) => (
155 <div className="paid-card" key={list.id}>
156 <img
157 className="paid-cover"
158 src={list.coverUrl || '/default-cover.jpg'}
159 alt={list.title}
160 />
161 <div className="paid-title">{list.title}</div>
162
163 {user && user.role === 'admin' ? (
164 <div className="admin-actions">
165 <button onClick={() => handleDelete(list.id)}>删除</button>
166 </div>
167 ) : list.isPaid ? (
168 <button onClick={() => navigate(`/playlist/${list.id}`)}>详情</button>
169 ) : (
170 <button onClick={() => handlePurchase(list.id)}>购买</button>
171 )}
172 </div>
173 ))}
174 </div>
175
176 <h2>🎬 正在热映</h2>
177 <div className="seed-list popular-row">
178 {popularSeeds.slice(0, 8).map(renderSeedCard)}
179 </div>
180
181 <h2>🎯 猜你喜欢</h2>
Krishya3dc6b352025-06-07 19:02:25 +0800182 {user ? (
223010091e2aea72025-06-08 16:35:54 +0800183 <>
184 {recommendedSeeds.movie.length > 0 &&
185 renderSection('🎞️ 电影推荐', recommendedSeeds.movie)}
186 {recommendedSeeds.tv.length > 0 &&
187 renderSection('📺 电视剧推荐', recommendedSeeds.tv)}
188 {recommendedSeeds.anime.length > 0 &&
189 renderSection('🎌 动漫推荐', recommendedSeeds.anime)}
190 </>
22301009afbcf4b2025-04-10 16:08:39 +0800191 ) : (
Krishya3dc6b352025-06-07 19:02:25 +0800192 <div className="login-reminder">请登录以获取个性化推荐</div>
22301009afbcf4b2025-04-10 16:08:39 +0800193 )}
223010091e2aea72025-06-08 16:35:54 +0800194
195 {showModal && (
196 <CreatePlaylistModal
197 onClose={() => setShowModal(false)}
198 onSuccess={(newPlaylist) => {
199 setPaidLists([newPlaylist, ...paidLists]);
200 setShowModal(false);
201 }}
202 />
203 )}
22301009ecc1c1c2025-04-09 21:56:23 +0800204 </div>
205 );
206};
207
208export default Recommend;