blob: 28b73f18dea4a59c14f427b5beba291745340f24 [file] [log] [blame]
vulgar520152afbcf2025-06-07 02:34:46 +08001<template>
2 <div class="torrents-page">
3 <div class="page-header">
4 <h1>种子资源</h1>
5 <div class="header-actions">
6 <el-button type="primary" :icon="Upload" @click="$router.push('/upload')">
7 上传种子
8 </el-button>
9 </div>
10 </div>
11
12 <!-- 搜索和筛选 -->
13 <div class="search-section">
14 <div class="search-bar">
15 <el-input
16 v-model="searchQuery"
17 placeholder="搜索种子..."
18 :prefix-icon="Search"
19 size="large"
20 @keyup.enter="handleSearch"
21 clearable
22 />
23 <el-button type="primary" size="large" @click="handleSearch">
24 搜索
25 </el-button>
26 </div>
27
28 <div class="filters">
29 <el-select v-model="selectedCategory" placeholder="分类" @change="handleFilter">
30 <el-option label="全部" value="" />
vulgar52015dc93392025-06-07 12:01:09 +080031 <el-option
32 v-for="category in categories"
33 :key="category.id"
34 :label="category.name"
35 :value="category.slug"
36 />
vulgar520152afbcf2025-06-07 02:34:46 +080037 </el-select>
vulgar520152afbcf2025-06-07 02:34:46 +080038 </div>
39 </div>
40
41 <!-- 种子列表 -->
42 <div class="torrents-list">
43 <div class="list-header">
44 <span class="results-count">共找到 {{ totalCount }} 个种子</span>
45 </div>
46
47 <!-- <div style="background: yellow; padding: 10px; margin: 10px 0;">
48 <p>调试信息:</p>
49 <p>torrents长度: {{ torrents.length }}</p>
50 <p>totalCount: {{ totalCount }}</p>
51 <p>loading: {{ loading }}</p>
52 <pre>{{ JSON.stringify(torrents[0], null, 2) }}</pre>
53 </div> -->
54
55 <el-table
56 :data="torrents"
57 v-loading="loading"
58 @row-click="handleRowClick"
59 stripe
60 class="torrents-table"
61 >
62 <el-table-column label="分类" width="100">
63 <template #default="{ row }">
64 <el-tag
65 :type="getCategoryType(row.category)"
66 size="small"
67 >
68 {{ row.category?.name || '未分类' }}
69 </el-tag>
70 </template>
71 </el-table-column>
72
73 <el-table-column label="种子信息" min-width="400">
74 <template #default="{ row }">
75 <div class="torrent-info">
76 <h4 class="torrent-title">{{ row.title }}</h4>
77 <div class="torrent-subtitle" v-if="row.subTitle">
78 {{ row.subTitle }}
79 </div>
80 <div class="torrent-meta">
81 <span class="uploader">
82 <el-icon><User /></el-icon>
83 {{ row.anonymous ? '匿名用户' : row.user?.username }}
84 </span>
85 <span class="upload-time">
86 <el-icon><Clock /></el-icon>
87 {{ formatTime(row.createdAt) }}
88 </span>
89 <span class="file-size">
90 <el-icon><Document /></el-icon>
91 {{ formatSize(row.size) }}
92 </span>
93 </div>
94 <div class="torrent-tags" v-if="row.tag && row.tag.length > 0">
95 <el-tag
96 v-for="tag in row.tag"
97 :key="tag"
98 size="small"
99 type="info"
100 class="tag-item"
101 >
102 {{ tag }}
103 </el-tag>
104 </div>
105 </div>
106 </template>
107 </el-table-column>
108
109 <el-table-column label="做种" width="80" align="center">
110 <template #default="{ row }">
111 <span class="seeders">{{ row.seeders || 0 }}</span>
112 </template>
113 </el-table-column>
114
115 <el-table-column label="下载" width="80" align="center">
116 <template #default="{ row }">
117 <span class="leechers">{{ row.leechers || 0 }}</span>
118 </template>
119 </el-table-column>
120
121 <el-table-column label="完成" width="80" align="center">
122 <template #default="{ row }">
123 <span>{{ row.downloads || 0 }}</span>
124 </template>
125 </el-table-column>
126
127 <el-table-column label="操作" width="120" align="center">
128 <template #default="{ row }">
129 <el-button
130 type="primary"
131 size="small"
132 :icon="Download"
133 @click.stop="handleDownload(row)"
134 >
135 下载
136 </el-button>
137 </template>
138 </el-table-column>
139 </el-table>
140
141 <!-- 分页 -->
142 <div class="pagination-wrapper">
143 <el-pagination
144 v-model:current-page="currentPage"
145 v-model:page-size="pageSize"
146 :page-sizes="[10,20, 50, 100]"
147 :total="totalCount"
148 layout="total, sizes, prev, pager, next, jumper"
149 @size-change="handleSizeChange"
150 @current-change="handleCurrentChange"
151 />
152 </div>
153 </div>
154 </div>
155</template>
156
157<script>
158import { ref, onMounted, watch } from 'vue'
159import { useRouter, useRoute } from 'vue-router'
160import { ElMessage } from 'element-plus'
161import {
162 Search,
163 Upload,
164 Download,
165 User,
166 Clock,
167 Document
168} from '@element-plus/icons-vue'
vulgar52015dc93392025-06-07 12:01:09 +0800169import { searchTorrents, getCategories } from '@/api/torrent'
vulgar520152afbcf2025-06-07 02:34:46 +0800170
171export default {
172 name: 'TorrentsView',
173 setup() {
174 const router = useRouter()
175 const route = useRoute()
176
177 const loading = ref(false)
178 const searchQuery = ref('')
179 const selectedCategory = ref('')
180 const sortBy = ref('upload_time')
181 const sortOrder = ref('desc')
182 const currentPage = ref(1)
183 const pageSize = ref(20)
184 const totalCount = ref(0)
185 const totalPages = ref(0)
186
187 const torrents = ref([])
vulgar52015dc93392025-06-07 12:01:09 +0800188 const categories = ref([])
vulgar520152afbcf2025-06-07 02:34:46 +0800189
190 onMounted(() => {
191 // 从URL参数初始化搜索条件
192 if (route.query.q) {
193 searchQuery.value = route.query.q
194 }
195 if (route.query.category) {
196 selectedCategory.value = route.query.category
197 }
198 if (route.query.page) {
199 currentPage.value = parseInt(route.query.page)
200 }
201
vulgar52015dc93392025-06-07 12:01:09 +0800202 fetchCategories()
vulgar520152afbcf2025-06-07 02:34:46 +0800203 fetchTorrents()
204 })
205
206 const fetchTorrents = async () => {
207 loading.value = true
208 try {
209 const searchParams = {
210 keyword: searchQuery.value || '', // 搜索关键词
vulgar52015dc93392025-06-07 12:01:09 +0800211 //category: selectedCategory.value || '',
vulgar520152afbcf2025-06-07 02:34:46 +0800212 page: currentPage.value - 1, // 后端页码从0开始
213 entriesPerPage: pageSize.value // 每页显示数量
214 }
215
216 console.log('🔍 发送搜索请求,参数:', searchParams)
217
218 const response = await searchTorrents(searchParams)
219
220 console.log('✅ 接收到响应:', response)
221
222 if (response) { // response直接就是数据
223 torrents.value = response.torrents || []
224 totalCount.value = response.totalElements || 0
225 totalPages.value = response.totalPages || 1
226
227 console.log('📊 处理后的数据:', {
228 torrentsCount: torrents.value.length,
229 totalCount: totalCount.value,
230 firstTorrent: torrents.value[0]
231 })
232 }
233 } catch (error) {
234 console.error('获取种子列表失败:', error)
235 console.error('📝 错误详情:', {
236 message: error.message,
237 status: error.response?.status,
238 data: error.response?.data
239 })
240 ElMessage.error(`获取种子列表失败: ${error.response?.data?.message || error.message}`)
241 // 如果请求失败,清空数据
242 torrents.value = []
243 totalCount.value = 0
244 } finally {
245 loading.value = false
246 }
247 }
248
vulgar52015dc93392025-06-07 12:01:09 +0800249 const fetchCategories = async () => {
250 try {
251 const response = await getCategories()
252 if (response && Array.isArray(response)) {
253 categories.value = response
254 } else {
255 console.error('获取分类列表失败: 响应格式错误', response)
256 }
257 } catch (error) {
258 console.error('获取分类列表失败:', error)
259 ElMessage.error('获取分类列表失败')
260 }
261 }
262
vulgar520152afbcf2025-06-07 02:34:46 +0800263 const handleSearch = () => {
264 currentPage.value = 1
265 updateURL()
266 fetchTorrents()
267 }
268
269 const handleFilter = () => {
270 currentPage.value = 1
271 updateURL()
272 fetchTorrents()
273 }
274
275 const updateURL = () => {
276 const query = {}
277 if (searchQuery.value) query.q = searchQuery.value
278 if (selectedCategory.value) query.category = selectedCategory.value
279 if (currentPage.value > 1) query.page = currentPage.value
280
281 router.replace({ query })
282 }
283
284 const handleRowClick = (row) => {
285 router.push(`/torrent/${row.id || row.infoHash}`)
286 }
287
288 const handleDownload = (row) => {
289 ElMessage.success(`开始下载: ${row.title || row.name}`)
290 // 这里实现下载逻辑
291 }
292
293 const handleSizeChange = (size) => {
294 pageSize.value = size
295 currentPage.value = 1
296 fetchTorrents()
297 }
298
299 const handleCurrentChange = (page) => {
300 currentPage.value = page
301 updateURL()
302 fetchTorrents()
303 }
304
305 const formatTime = (timeString) => {
306 if (!timeString) return '-'
307
308 const date = new Date(timeString)
309 const now = new Date()
310 const diff = now - date
311 const hours = Math.floor(diff / (1000 * 60 * 60))
312
313 if (hours < 1) return '刚刚'
314 if (hours < 24) return `${hours}小时前`
315 const days = Math.floor(hours / 24)
316 if (days < 30) return `${days}天前`
317
318 return date.toLocaleDateString('zh-CN')
319 }
320
321 const formatSize = (sizeInBytes) => {
322 if (!sizeInBytes) return '-'
323
324 const units = ['B', 'KB', 'MB', 'GB', 'TB']
325 let size = sizeInBytes
326 let unitIndex = 0
327
328 while (size >= 1024 && unitIndex < units.length - 1) {
329 size /= 1024
330 unitIndex++
331 }
332
333 return `${size.toFixed(1)} ${units[unitIndex]}`
334 }
335
336 const getCategoryType = (category) => {
vulgar52015dc93392025-06-07 12:01:09 +0800337 if (!category) return ''
vulgar520152afbcf2025-06-07 02:34:46 +0800338 const types = {
339 'os': 'primary',
340 'movie': 'success',
vulgar52015dc93392025-06-07 12:01:09 +0800341 'db': 'info',
vulgar520152afbcf2025-06-07 02:34:46 +0800342 'music': 'warning',
343 'software': 'danger'
344 }
vulgar52015dc93392025-06-07 12:01:09 +0800345 return types[category.slug] || ''
vulgar520152afbcf2025-06-07 02:34:46 +0800346 }
347
348 return {
349 loading,
350 searchQuery,
351 selectedCategory,
352 sortBy,
353 sortOrder,
354 currentPage,
355 pageSize,
356 totalCount,
357 torrents,
vulgar52015dc93392025-06-07 12:01:09 +0800358 categories,
vulgar520152afbcf2025-06-07 02:34:46 +0800359 handleSearch,
360 handleFilter,
361 handleRowClick,
362 handleDownload,
363 handleSizeChange,
364 handleCurrentChange,
365 formatTime,
366 formatSize,
367 getCategoryType,
368 Search,
369 Upload,
370 Download,
371 User,
372 Clock,
373 Document
374 }
375 }
376}
377</script>
378
379<style lang="scss" scoped>
380.torrents-page {
381 max-width: 1200px;
382 margin: 0 auto;
383 padding: 24px;
384}
385
386.page-header {
387 display: flex;
388 justify-content: space-between;
389 align-items: center;
390 margin-bottom: 24px;
391
392 h1 {
393 font-size: 28px;
394 font-weight: 600;
395 color: #2c3e50;
396 margin: 0;
397 }
398}
399
400.search-section {
401 background: #fff;
402 border-radius: 12px;
403 padding: 24px;
404 margin-bottom: 24px;
405 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
406
407 .search-bar {
408 display: flex;
409 gap: 12px;
410 margin-bottom: 16px;
411
412 .el-input {
413 flex: 1;
414 }
415 }
416
417 .filters {
418 display: flex;
419 gap: 16px;
420 flex-wrap: wrap;
421 align-items: center;
422
423 .el-select {
424 width: 120px;
425 }
426 }
427}
428
429.torrents-list {
430 background: #fff;
431 border-radius: 12px;
432 padding: 24px;
433 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
434
435 .list-header {
436 margin-bottom: 16px;
437
438 .results-count {
439 font-size: 14px;
440 color: #909399;
441 }
442 }
443
444 .torrents-table {
445 .torrent-info {
446 .torrent-title {
447 font-size: 16px;
448 font-weight: 500;
449 color: #2c3e50;
450 margin: 0 0 8px 0;
451 line-height: 1.4;
452 cursor: pointer;
453
454 &:hover {
455 color: #409eff;
456 }
457 }
458
459 .torrent-meta {
460 display: flex;
461 gap: 16px;
462 font-size: 12px;
463 color: #909399;
464
465 span {
466 display: flex;
467 align-items: center;
468 gap: 4px;
469 }
470 }
471 }
472
473 .seeders {
474 color: #67c23a;
475 font-weight: 600;
476 }
477
478 .leechers {
479 color: #f56c6c;
480 font-weight: 600;
481 }
482 }
483
484 .pagination-wrapper {
485 margin-top: 24px;
486 text-align: center;
487 }
488}
489
490@media (max-width: 768px) {
491 .torrents-page {
492 padding: 16px;
493 }
494
495 .page-header {
496 flex-direction: column;
497 gap: 16px;
498 align-items: flex-start;
499 }
500
501 .filters {
502 flex-direction: column;
503 align-items: flex-start;
504
505 .el-select {
506 width: 100%;
507 }
508 }
509
510 .torrents-table {
511 :deep(.el-table__header),
512 :deep(.el-table__body) {
513 font-size: 12px;
514 }
515 }
516}
xingjinwend652cc62025-06-04 19:52:19 +0800517</style>