blob: 2229bb1db589bba3e7fb16d6ed139a35a47e7f68 [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';
22301009207e2db2025-06-09 00:27:28 +080010import AuthButton from '../../../components/AuthButton';
22301009ecc1c1c2025-04-09 21:56:23 +080011
22301009ecc1c1c2025-04-09 21:56:23 +080012const Recommend = () => {
Krishya3dc6b352025-06-07 19:02:25 +080013 const { user } = useUser();
223010091e2aea72025-06-08 16:35:54 +080014 const [paidLists, setPaidLists] = useState([]);
Krishya3dc6b352025-06-07 19:02:25 +080015 const [popularSeeds, setPopularSeeds] = useState([]);
223010091e2aea72025-06-08 16:35:54 +080016 const [recommendedSeeds, setRecommendedSeeds] = useState({
17 movie: [],
18 tv: [],
19 anime: []
20 });
21
22 const [showModal, setShowModal] = useState(false);
23 const [, navigate] = useLocation();
22301009afbcf4b2025-04-10 16:08:39 +080024
25 useEffect(() => {
223010091e2aea72025-06-08 16:35:54 +080026 axios
27 .get('/playlist/page', { params: { page: 1, size: 8 } })
28 .then((res) => {
29 if (res.data.code === 0) {
30 setPaidLists(res.data.data);
31 } else {
32 toast.error(`获取片单失败:${res.data.msg}`);
33 }
34 })
35 .catch((err) => {
36 console.error('请求片单失败', err);
37 toast.error('请求片单失败,请稍后重试');
38 });
39
Krishya3dc6b352025-06-07 19:02:25 +080040 axios
41 .get('/echo/recommendation/popular', { params: { limit: 16 } })
42 .then((res) => setPopularSeeds(res.data))
223010091e2aea72025-06-08 16:35:54 +080043 .catch((err) => {
44 console.error('获取热门资源失败', err);
45 toast.error('获取热门资源失败');
46 });
22301009afbcf4b2025-04-10 16:08:39 +080047 }, []);
48
Krishya3dc6b352025-06-07 19:02:25 +080049 useEffect(() => {
223010091e2aea72025-06-08 16:35:54 +080050 if (user?.userId) {
Krishya3dc6b352025-06-07 19:02:25 +080051 axios
52 .get(`/echo/recommendation/seeds/${user.userId}`)
223010091e2aea72025-06-08 16:35:54 +080053 .then((res) => {
54 const categorized = { movie: [], tv: [], anime: [] };
55 res.data.forEach((seed) => {
56 if (seed.category === 'movie') categorized.movie.push(seed);
57 else if (seed.category === 'tv') categorized.tv.push(seed);
58 else if (seed.category === 'anime') categorized.anime.push(seed);
59 });
60 setRecommendedSeeds(categorized);
61 })
62 .catch((err) => {
63 console.error('获取个性化推荐失败', err);
64 toast.error('获取个性化推荐失败');
65 });
Krishya3dc6b352025-06-07 19:02:25 +080066 }
67 }, [user]);
68
223010091e2aea72025-06-08 16:35:54 +080069 const handleDelete = (id) => {
70 confirmAlert({
71 title: '确认删除',
72 message: '确定删除此片单吗?',
73 buttons: [
74 {
75 label: '确定',
76 onClick: async () => {
77 const toastId = toast.loading('正在删除...');
78 try {
79 await axios.delete('/playlist', {
80 params: { ids: id },
81 paramsSerializer: (params) =>
82 `ids=${Array.isArray(params.ids) ? params.ids.join(',') : params.ids}`
83 });
84 setPaidLists(paidLists.filter((list) => list.id !== id));
85 toast.success('删除成功', { id: toastId });
86 } catch (error) {
87 console.error('删除失败', error);
88 toast.error('删除失败,请稍后重试', { id: toastId });
89 }
90 }
91 },
92 { label: '取消' }
93 ]
94 });
95 };
96
97 const handlePurchase = (id) => {
98 confirmAlert({
99 title: '确认购买',
100 message: '确定支付该片单?',
101 buttons: [
102 {
103 label: '确定',
104 onClick: async () => {
105 const toastId = toast.loading('购买中...');
106 try {
107 const res = await axios.post(`/playlist/${id}/pay`);
108 if (res.data.code === 0) {
109 toast.success('购买成功', { id: toastId });
110 navigate(`/playlist/${id}`);
111 } else {
112 toast.error(`购买失败:${res.data.msg}`, { id: toastId });
113 }
114 } catch (err) {
115 console.error('支付失败', err);
116 toast.error('购买失败,请稍后重试', { id: toastId });
117 }
118 }
119 },
120 { label: '取消' }
121 ]
122 });
123 };
124
125const renderSeedCard = (seed) => (
126 <div
127 className="seed-card"
128 key={seed.id}
129 onClick={() => navigate(`/seed/${seed.id}`)}
130 style={{ cursor: 'pointer' }}
131 >
132 <img src={seed.imageUrl || '/default-cover.jpg'} alt={seed.title} />
133 <div className="title">{seed.title}</div>
134 </div>
135);
136
137
138 const renderSection = (title, seeds) => (
139 <>
140 <h2>{title}</h2>
141 <div className="seed-list">{seeds.map(renderSeedCard)}</div>
142 </>
Krishya3dc6b352025-06-07 19:02:25 +0800143 );
144
22301009ecc1c1c2025-04-09 21:56:23 +0800145 return (
Krishya3dc6b352025-06-07 19:02:25 +0800146 <div className="recommendation-page">
223010091e2aea72025-06-08 16:35:54 +0800147 <h2>💰 付费片单</h2>
148 {user && user.role === 'admin' && (
149 <button className="create-button" onClick={() => setShowModal(true)}>
150 创建片单
151 </button>
152 )}
Krishya3dc6b352025-06-07 19:02:25 +0800153
223010091e2aea72025-06-08 16:35:54 +0800154 <div className="recommend-paid-row">
155 {paidLists.map((list) => (
156 <div className="paid-card" key={list.id}>
157 <img
158 className="paid-cover"
159 src={list.coverUrl || '/default-cover.jpg'}
160 alt={list.title}
161 />
162 <div className="paid-title">{list.title}</div>
163
22301009207e2db2025-06-09 00:27:28 +0800164 {user && user.role === 'admin' ? (
165 <div className="admin-actions">
166 <button onClick={() => handleDelete(list.id)}>删除</button>
167 </div>
168 ) : list.isPaid ? (
169 <button onClick={() => navigate(`/playlist/${list.id}`)}>详情</button>
170 ) : (
171 <AuthButton roles={['chocolate', 'ice-cream']} onClick={() => handlePurchase(list.id)}>
172 购买
173 </AuthButton>
174 )}
175
223010091e2aea72025-06-08 16:35:54 +0800176 </div>
177 ))}
178 </div>
179
180 <h2>🎬 正在热映</h2>
181 <div className="seed-list popular-row">
182 {popularSeeds.slice(0, 8).map(renderSeedCard)}
183 </div>
184
185 <h2>🎯 猜你喜欢</h2>
Krishya3dc6b352025-06-07 19:02:25 +0800186 {user ? (
223010091e2aea72025-06-08 16:35:54 +0800187 <>
188 {recommendedSeeds.movie.length > 0 &&
189 renderSection('🎞️ 电影推荐', recommendedSeeds.movie)}
190 {recommendedSeeds.tv.length > 0 &&
191 renderSection('📺 电视剧推荐', recommendedSeeds.tv)}
192 {recommendedSeeds.anime.length > 0 &&
193 renderSection('🎌 动漫推荐', recommendedSeeds.anime)}
194 </>
22301009afbcf4b2025-04-10 16:08:39 +0800195 ) : (
Krishya3dc6b352025-06-07 19:02:25 +0800196 <div className="login-reminder">请登录以获取个性化推荐</div>
22301009afbcf4b2025-04-10 16:08:39 +0800197 )}
223010091e2aea72025-06-08 16:35:54 +0800198
199 {showModal && (
200 <CreatePlaylistModal
201 onClose={() => setShowModal(false)}
202 onSuccess={(newPlaylist) => {
203 setPaidLists([newPlaylist, ...paidLists]);
204 setShowModal(false);
205 }}
206 />
207 )}
22301009ecc1c1c2025-04-09 21:56:23 +0800208 </div>
209 );
210};
211
212export default Recommend;