22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 1 | import React, { useEffect, useState } from 'react'; |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 2 | import axios from 'axios'; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 3 | import './Recommend.css'; |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 4 | import { useUser } from '../../../context/UserContext'; |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 5 | import { useLocation } from 'wouter'; |
| 6 | import toast from 'react-hot-toast'; |
| 7 | import CreatePlaylistModal from './CreatePlaylistModal'; |
| 8 | import { confirmAlert } from 'react-confirm-alert'; |
| 9 | import 'react-confirm-alert/src/react-confirm-alert.css'; |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 10 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 11 | const Recommend = () => { |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 12 | const { user } = useUser(); |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 13 | const [paidLists, setPaidLists] = useState([]); |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 14 | const [popularSeeds, setPopularSeeds] = useState([]); |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 15 | const [recommendedSeeds, setRecommendedSeeds] = useState({ |
| 16 | movie: [], |
| 17 | tv: [], |
| 18 | anime: [] |
| 19 | }); |
| 20 | |
| 21 | const [showModal, setShowModal] = useState(false); |
| 22 | const [, navigate] = useLocation(); |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 23 | |
| 24 | useEffect(() => { |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 25 | 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 | |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 39 | axios |
| 40 | .get('/echo/recommendation/popular', { params: { limit: 16 } }) |
| 41 | .then((res) => setPopularSeeds(res.data)) |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 42 | .catch((err) => { |
| 43 | console.error('获取热门资源失败', err); |
| 44 | toast.error('获取热门资源失败'); |
| 45 | }); |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 46 | }, []); |
| 47 | |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 48 | useEffect(() => { |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 49 | if (user?.userId) { |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 50 | axios |
| 51 | .get(`/echo/recommendation/seeds/${user.userId}`) |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 52 | .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 | }); |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 65 | } |
| 66 | }, [user]); |
| 67 | |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 68 | 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 | |
| 124 | const 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 | </> |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 142 | ); |
| 143 | |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 144 | return ( |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 145 | <div className="recommendation-page"> |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 146 | <h2>💰 付费片单</h2> |
| 147 | {user && user.role === 'admin' && ( |
| 148 | <button className="create-button" onClick={() => setShowModal(true)}> |
| 149 | ➕ 创建片单 |
| 150 | </button> |
| 151 | )} |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 152 | |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 153 | <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> |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 182 | {user ? ( |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 183 | <> |
| 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 | </> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 191 | ) : ( |
Krishya | 3dc6b35 | 2025-06-07 19:02:25 +0800 | [diff] [blame] | 192 | <div className="login-reminder">请登录以获取个性化推荐</div> |
22301009 | afbcf4b | 2025-04-10 16:08:39 +0800 | [diff] [blame] | 193 | )} |
22301009 | 1e2aea7 | 2025-06-08 16:35:54 +0800 | [diff] [blame^] | 194 | |
| 195 | {showModal && ( |
| 196 | <CreatePlaylistModal |
| 197 | onClose={() => setShowModal(false)} |
| 198 | onSuccess={(newPlaylist) => { |
| 199 | setPaidLists([newPlaylist, ...paidLists]); |
| 200 | setShowModal(false); |
| 201 | }} |
| 202 | /> |
| 203 | )} |
22301009 | ecc1c1c | 2025-04-09 21:56:23 +0800 | [diff] [blame] | 204 | </div> |
| 205 | ); |
| 206 | }; |
| 207 | |
| 208 | export default Recommend; |