blob: 34af25d966ab9ae8297107983bf25fbe120f096d [file] [log] [blame]
vulgar520152afbcf2025-06-07 02:34:46 +08001<template>
2 <div class="torrents-page">
Xing Jinwenebbccad2025-06-07 21:24:44 +08003 <Navbar />
vulgar520152afbcf2025-06-07 02:34:46 +08004 <div class="page-header">
5 <h1>种子资源</h1>
6 <div class="header-actions">
7 <el-button type="primary" :icon="Upload" @click="$router.push('/upload')">
8 上传种子
9 </el-button>
10 </div>
11 </div>
12
13 <!-- 搜索和筛选 -->
14 <div class="search-section">
15 <div class="search-bar">
16 <el-input
17 v-model="searchQuery"
18 placeholder="搜索种子..."
19 :prefix-icon="Search"
20 size="large"
21 @keyup.enter="handleSearch"
22 clearable
23 />
24 <el-button type="primary" size="large" @click="handleSearch">
25 搜索
26 </el-button>
27 </div>
28
29 <div class="filters">
30 <el-select v-model="selectedCategory" placeholder="分类" @change="handleFilter">
vulgar52015dc93392025-06-07 12:01:09 +080031 <el-option
vulgar52019bf462d2025-06-07 17:54:04 +080032 v-for="category in categoryOptions"
33 :key="category.slug"
vulgar52015dc93392025-06-07 12:01:09 +080034 :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>
vulgar52019bf462d2025-06-07 17:54:04 +0800158import { ref, onMounted, watch, computed } from 'vue'
vulgar520152afbcf2025-06-07 02:34:46 +0800159import { 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'
Xing Jinwenebbccad2025-06-07 21:24:44 +0800170import Navbar from '@/components/Navbar.vue'
vulgar520152afbcf2025-06-07 02:34:46 +0800171
172export default {
173 name: 'TorrentsView',
Xing Jinwenebbccad2025-06-07 21:24:44 +0800174 components:{
175 Navbar
176 },
vulgar520152afbcf2025-06-07 02:34:46 +0800177 setup() {
178 const router = useRouter()
179 const route = useRoute()
180
181 const loading = ref(false)
182 const searchQuery = ref('')
183 const selectedCategory = ref('')
184 const sortBy = ref('upload_time')
185 const sortOrder = ref('desc')
186 const currentPage = ref(1)
187 const pageSize = ref(20)
188 const totalCount = ref(0)
189 const totalPages = ref(0)
190
191 const torrents = ref([])
vulgar52015dc93392025-06-07 12:01:09 +0800192 const categories = ref([])
vulgar520152afbcf2025-06-07 02:34:46 +0800193
194 onMounted(() => {
195 // 从URL参数初始化搜索条件
196 if (route.query.q) {
197 searchQuery.value = route.query.q
198 }
199 if (route.query.category) {
200 selectedCategory.value = route.query.category
201 }
202 if (route.query.page) {
203 currentPage.value = parseInt(route.query.page)
204 }
205
vulgar52015dc93392025-06-07 12:01:09 +0800206 fetchCategories()
vulgar520152afbcf2025-06-07 02:34:46 +0800207 fetchTorrents()
208 })
209
210 const fetchTorrents = async () => {
211 loading.value = true
212 try {
vulgar52019bf462d2025-06-07 17:54:04 +0800213 if (selectedCategory.value) {
214 // 使用 GET 请求
215 const response = await fetch(`/api/torrent/search?category=${selectedCategory.value}`)
216 .then(res => res.json())
vulgar520152afbcf2025-06-07 02:34:46 +0800217
vulgar520152afbcf2025-06-07 02:34:46 +0800218 torrents.value = response.torrents || []
219 totalCount.value = response.totalElements || 0
220 totalPages.value = response.totalPages || 1
vulgar52019bf462d2025-06-07 17:54:04 +0800221
222 } else {
223 // 使用 POST 请求(搜索)
224 const searchParams = {
225 keyword: searchQuery.value || '',
226 page: currentPage.value - 1,
227 entriesPerPage: pageSize.value
228 }
229
230 const response = await searchTorrents(searchParams)
231
232 torrents.value = response.torrents || []
233 totalCount.value = response.totalElements || 0
234 totalPages.value = response.totalPages || 1
vulgar520152afbcf2025-06-07 02:34:46 +0800235 }
vulgar52019bf462d2025-06-07 17:54:04 +0800236
vulgar520152afbcf2025-06-07 02:34:46 +0800237 } catch (error) {
238 console.error('获取种子列表失败:', error)
vulgar52019bf462d2025-06-07 17:54:04 +0800239 ElMessage.error('获取种子列表失败')
vulgar520152afbcf2025-06-07 02:34:46 +0800240 torrents.value = []
241 totalCount.value = 0
242 } finally {
243 loading.value = false
244 }
245 }
246
vulgar52015dc93392025-06-07 12:01:09 +0800247 const fetchCategories = async () => {
248 try {
249 const response = await getCategories()
vulgar52019bf462d2025-06-07 17:54:04 +0800250 console.log('分类列表响应:', response)
251
vulgar52015dc93392025-06-07 12:01:09 +0800252 if (response && Array.isArray(response)) {
253 categories.value = response
vulgar52019bf462d2025-06-07 17:54:04 +0800254 console.log('分类列表加载成功:', categories.value)
vulgar52015dc93392025-06-07 12:01:09 +0800255 } else {
256 console.error('获取分类列表失败: 响应格式错误', response)
vulgar52019bf462d2025-06-07 17:54:04 +0800257 ElMessage.warning('分类列表格式不正确')
vulgar52015dc93392025-06-07 12:01:09 +0800258 }
259 } catch (error) {
260 console.error('获取分类列表失败:', error)
261 ElMessage.error('获取分类列表失败')
262 }
263 }
264
vulgar520152afbcf2025-06-07 02:34:46 +0800265 const handleSearch = () => {
266 currentPage.value = 1
267 updateURL()
268 fetchTorrents()
269 }
270
271 const handleFilter = () => {
272 currentPage.value = 1
273 updateURL()
274 fetchTorrents()
275 }
276
277 const updateURL = () => {
278 const query = {}
279 if (searchQuery.value) query.q = searchQuery.value
280 if (selectedCategory.value) query.category = selectedCategory.value
281 if (currentPage.value > 1) query.page = currentPage.value
282
283 router.replace({ query })
284 }
285
286 const handleRowClick = (row) => {
vulgar52014958b252025-06-08 03:26:43 +0800287 router.push(`/torrent/${row.infoHash}`)
vulgar520152afbcf2025-06-07 02:34:46 +0800288 }
289
290 const handleDownload = (row) => {
291 ElMessage.success(`开始下载: ${row.title || row.name}`)
292 // 这里实现下载逻辑
293 }
294
295 const handleSizeChange = (size) => {
296 pageSize.value = size
297 currentPage.value = 1
298 fetchTorrents()
299 }
300
301 const handleCurrentChange = (page) => {
302 currentPage.value = page
303 updateURL()
304 fetchTorrents()
305 }
306
307 const formatTime = (timeString) => {
308 if (!timeString) return '-'
309
310 const date = new Date(timeString)
311 const now = new Date()
312 const diff = now - date
313 const hours = Math.floor(diff / (1000 * 60 * 60))
314
315 if (hours < 1) return '刚刚'
316 if (hours < 24) return `${hours}小时前`
317 const days = Math.floor(hours / 24)
318 if (days < 30) return `${days}天前`
319
320 return date.toLocaleDateString('zh-CN')
321 }
322
323 const formatSize = (sizeInBytes) => {
324 if (!sizeInBytes) return '-'
325
326 const units = ['B', 'KB', 'MB', 'GB', 'TB']
327 let size = sizeInBytes
328 let unitIndex = 0
329
330 while (size >= 1024 && unitIndex < units.length - 1) {
331 size /= 1024
332 unitIndex++
333 }
334
335 return `${size.toFixed(1)} ${units[unitIndex]}`
336 }
337
338 const getCategoryType = (category) => {
vulgar52015dc93392025-06-07 12:01:09 +0800339 if (!category) return ''
vulgar520152afbcf2025-06-07 02:34:46 +0800340 const types = {
341 'os': 'primary',
342 'movie': 'success',
vulgar52015dc93392025-06-07 12:01:09 +0800343 'db': 'info',
vulgar520152afbcf2025-06-07 02:34:46 +0800344 'music': 'warning',
345 'software': 'danger'
346 }
vulgar52015dc93392025-06-07 12:01:09 +0800347 return types[category.slug] || ''
vulgar520152afbcf2025-06-07 02:34:46 +0800348 }
349
vulgar52019bf462d2025-06-07 17:54:04 +0800350 const categoryOptions = computed(() => {
351 return [
352 { id: '', name: '全部' },
353 ...categories.value
354 ]
355 })
356
vulgar520152afbcf2025-06-07 02:34:46 +0800357 return {
358 loading,
359 searchQuery,
360 selectedCategory,
361 sortBy,
362 sortOrder,
363 currentPage,
364 pageSize,
365 totalCount,
366 torrents,
vulgar52015dc93392025-06-07 12:01:09 +0800367 categories,
vulgar52019bf462d2025-06-07 17:54:04 +0800368 categoryOptions,
vulgar520152afbcf2025-06-07 02:34:46 +0800369 handleSearch,
370 handleFilter,
371 handleRowClick,
372 handleDownload,
373 handleSizeChange,
374 handleCurrentChange,
375 formatTime,
376 formatSize,
377 getCategoryType,
378 Search,
379 Upload,
380 Download,
381 User,
382 Clock,
383 Document
384 }
385 }
386}
387</script>
388
389<style lang="scss" scoped>
390.torrents-page {
391 max-width: 1200px;
392 margin: 0 auto;
393 padding: 24px;
394}
395
396.page-header {
397 display: flex;
398 justify-content: space-between;
399 align-items: center;
400 margin-bottom: 24px;
401
402 h1 {
403 font-size: 28px;
404 font-weight: 600;
405 color: #2c3e50;
406 margin: 0;
407 }
408}
409
410.search-section {
411 background: #fff;
412 border-radius: 12px;
413 padding: 24px;
414 margin-bottom: 24px;
415 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
416
417 .search-bar {
418 display: flex;
419 gap: 12px;
420 margin-bottom: 16px;
421
422 .el-input {
423 flex: 1;
424 }
425 }
426
427 .filters {
428 display: flex;
429 gap: 16px;
430 flex-wrap: wrap;
431 align-items: center;
432
433 .el-select {
434 width: 120px;
435 }
436 }
437}
438
439.torrents-list {
440 background: #fff;
441 border-radius: 12px;
442 padding: 24px;
443 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
444
445 .list-header {
446 margin-bottom: 16px;
447
448 .results-count {
449 font-size: 14px;
450 color: #909399;
451 }
452 }
453
454 .torrents-table {
455 .torrent-info {
456 .torrent-title {
457 font-size: 16px;
458 font-weight: 500;
459 color: #2c3e50;
460 margin: 0 0 8px 0;
461 line-height: 1.4;
462 cursor: pointer;
463
464 &:hover {
465 color: #409eff;
466 }
467 }
468
469 .torrent-meta {
470 display: flex;
471 gap: 16px;
472 font-size: 12px;
473 color: #909399;
474
475 span {
476 display: flex;
477 align-items: center;
478 gap: 4px;
479 }
480 }
481 }
482
483 .seeders {
484 color: #67c23a;
485 font-weight: 600;
486 }
487
488 .leechers {
489 color: #f56c6c;
490 font-weight: 600;
491 }
492 }
493
494 .pagination-wrapper {
495 margin-top: 24px;
496 text-align: center;
497 }
498}
499
500@media (max-width: 768px) {
501 .torrents-page {
502 padding: 16px;
503 }
504
505 .page-header {
506 flex-direction: column;
507 gap: 16px;
508 align-items: flex-start;
509 }
510
511 .filters {
512 flex-direction: column;
513 align-items: flex-start;
514
515 .el-select {
516 width: 100%;
517 }
518 }
519
520 .torrents-table {
521 :deep(.el-table__header),
522 :deep(.el-table__body) {
523 font-size: 12px;
524 }
525 }
526}
xingjinwend652cc62025-06-04 19:52:19 +0800527</style>