blob: 490a13839b4edf6180730d4746ab918cd4a934e6 [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="upload-page">
Xing Jinwenebbccad2025-06-07 21:24:44 +08003 <Navbar />
vulgar5201055346a2025-06-05 14:15:55 +08004 <div class="upload-container">
5 <h2>上传种子</h2>
6
Xing Jinwenff16b1e2025-06-05 00:29:26 +08007 <el-form
8 ref="uploadFormRef"
9 :model="uploadForm"
vulgar5201055346a2025-06-05 14:15:55 +080010 :rules="uploadRules"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080011 label-width="120px"
vulgar5201055346a2025-06-05 14:15:55 +080012 class="upload-form"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080013 >
14 <!-- 种子文件上传 -->
vulgar5201055346a2025-06-05 14:15:55 +080015 <el-form-item label="种子文件" prop="file">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080016 <el-upload
vulgar5201055346a2025-06-05 14:15:55 +080017 ref="uploadRef"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080018 :auto-upload="false"
19 :limit="1"
20 accept=".torrent"
21 :on-change="handleTorrentChange"
22 :on-remove="handleTorrentRemove"
vulgar5201055346a2025-06-05 14:15:55 +080023 :file-list="fileList"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080024 >
vulgar5201055346a2025-06-05 14:15:55 +080025 <template #trigger>
26 <el-button type="primary">选择文件</el-button>
27 </template>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080028 <template #tip>
29 <div class="el-upload__tip">
vulgar5201055346a2025-06-05 14:15:55 +080030 只能上传 .torrent 文件
Xing Jinwenff16b1e2025-06-05 00:29:26 +080031 </div>
32 </template>
33 </el-upload>
34 </el-form-item>
35
36 <!-- 基本信息 -->
vulgar5201055346a2025-06-05 14:15:55 +080037 <el-form-item label="标题" prop="title">
38 <el-input v-model="uploadForm.title" placeholder="请输入种子标题" />
Xing Jinwenff16b1e2025-06-05 00:29:26 +080039 </el-form-item>
40
vulgar5201055346a2025-06-05 14:15:55 +080041 <el-form-item label="副标题" prop="subtitle">
42 <el-input v-model="uploadForm.subtitle" placeholder="请输入副标题(可选)" />
43 </el-form-item>
44
45 <el-form-item label="分类" prop="category">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080046 <el-select v-model="uploadForm.category" placeholder="请选择分类">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080047 <el-option
vulgar5201055346a2025-06-05 14:15:55 +080048 v-for="category in categories"
49 :key="category.id"
50 :label="category.name"
51 :value="category.slug"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080052 />
53 </el-select>
54 </el-form-item>
55
vulgar5201055346a2025-06-05 14:15:55 +080056 <!-- 标签 -->
57 <el-form-item label="标签" prop="tag">
58 <el-select
59 v-model="uploadForm.tag"
60 multiple
61 filterable
62 allow-create
63 placeholder="请选择或输入标签"
64 >
65 <el-option
66 v-for="tag in availableTags"
67 :key="tag.id"
68 :label="tag.name"
69 :value="tag.name"
70 />
71 </el-select>
72 </el-form-item>
73
74 <!-- 描述信息 -->
75 <el-form-item label="描述" prop="description">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080076 <el-input
77 v-model="uploadForm.description"
78 type="textarea"
79 :rows="6"
vulgar5201055346a2025-06-05 14:15:55 +080080 placeholder="请输入种子描述,支持 Markdown 格式"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080081 />
82 </el-form-item>
83
vulgar5201055346a2025-06-05 14:15:55 +080084 <!-- 匿名发布 -->
Xing Jinwenff16b1e2025-06-05 00:29:26 +080085 <el-form-item>
vulgar5201055346a2025-06-05 14:15:55 +080086 <el-checkbox v-model="uploadForm.anonymous">匿名发布</el-checkbox>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080087 </el-form-item>
88
89 <!-- 提交按钮 -->
90 <el-form-item>
vulgar5201055346a2025-06-05 14:15:55 +080091 <el-button type="primary" @click="submitUpload" :loading="uploading">
92 上传种子
93 </el-button>
94 <el-button @click="resetForm">重置表单</el-button>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080095 </el-form-item>
96 </el-form>
97 </div>
98 </div>
99</template>
100
101<script>
vulgar5201055346a2025-06-05 14:15:55 +0800102import { ref, reactive, onMounted } from 'vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800103import { useRouter } from 'vue-router'
104import { ElMessage } from 'element-plus'
vulgar5201055346a2025-06-05 14:15:55 +0800105import { uploadTorrent, getCategories, getTags } from '@/api/torrent'
Xing Jinwenebbccad2025-06-07 21:24:44 +0800106import Navbar from '@/components/Navbar.vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800107export default {
108 name: 'UploadView',
Xing Jinwenebbccad2025-06-07 21:24:44 +0800109 components: {
110 Navbar
111 },
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800112 setup() {
113 const router = useRouter()
114 const uploadFormRef = ref(null)
vulgar5201055346a2025-06-05 14:15:55 +0800115 const uploadRef = ref(null)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800116 const uploading = ref(false)
vulgar5201055346a2025-06-05 14:15:55 +0800117 const fileList = ref([])
118
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800119 const uploadForm = reactive({
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800120 title: '',
vulgar5201055346a2025-06-05 14:15:55 +0800121 subtitle: '',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800122 category: '',
vulgar5201055346a2025-06-05 14:15:55 +0800123 tag: [],
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800124 description: '',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800125 anonymous: false,
vulgar5201055346a2025-06-05 14:15:55 +0800126 file: null
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800127 })
vulgar5201055346a2025-06-05 14:15:55 +0800128
129 const uploadRules = {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800130 title: [
vulgar5201055346a2025-06-05 14:15:55 +0800131 { required: true, message: '请输入种子标题', trigger: 'blur' },
132 { min: 3, max: 100, message: '标题长度应在 3 到 100 个字符之间', trigger: 'blur' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800133 ],
134 category: [
vulgar5201055346a2025-06-05 14:15:55 +0800135 { required: true, message: '请选择分类', trigger: 'change' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800136 ],
137 description: [
vulgar5201055346a2025-06-05 14:15:55 +0800138 { required: true, message: '请输入种子描述', trigger: 'blur' },
139 { min: 10, message: '描述至少需要 10 个字符', trigger: 'blur' }
140 ],
141 file: [
142 { required: true, message: '请上传种子文件', trigger: 'change' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800143 ]
144 }
vulgar5201055346a2025-06-05 14:15:55 +0800145
146 const categories = ref([])
147 const availableTags = ref([])
148
149 // 获取分类列表
150 const loadCategories = async () => {
151 try {
152 console.log('开始加载分类列表...')
vulgar5201c4a15b12025-06-06 13:55:09 +0800153 console.log('当前token(分类列表):', localStorage.getItem('token'))
vulgar5201055346a2025-06-05 14:15:55 +0800154 const response = await getCategories()
155 console.log('分类列表响应:', response)
156
vulgar5201c4a15b12025-06-06 13:55:09 +0800157 const list = Array.isArray(response) ? response : response.data
158
159 if (list && list.length > 0) {
160 categories.value = list
vulgar5201055346a2025-06-05 14:15:55 +0800161 console.log('分类列表加载成功:', categories.value)
162 } else {
163 console.warn('分类列表数据为空')
164 categories.value = []
165 }
166 } catch (error) {
167 console.error('Failed to load categories:', error)
168 console.error('错误详情:', {
169 message: error.message,
170 response: error.response?.data,
171 status: error.response?.status,
172 config: error.config
173 })
174
175 // 根据错误类型显示不同的提示
176 if (error.response?.status === 401) {
177 ElMessage.error('请先登录')
178 router.push('/login')
179 } else if (error.response?.status === 403) {
180 ElMessage.error('没有权限访问分类列表')
181 } else if (error.code === 'ERR_NETWORK') {
182 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
183 } else {
184 ElMessage.error(`获取分类列表失败: ${error.message}`)
185 }
186 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800187 }
vulgar5201055346a2025-06-05 14:15:55 +0800188
189 // 获取标签列表
190 const loadTags = async () => {
191 try {
192 console.log('开始加载标签列表...')
193 const response = await getTags()
194 console.log('标签列表响应:', response)
195
196 if (response && response.data) {
197 availableTags.value = response.data
198 console.log('标签列表加载成功:', availableTags.value)
199 } else {
200 console.warn('标签列表数据为空')
201 availableTags.value = []
202 }
203 } catch (error) {
204 console.error('Failed to load tags:', error)
205 console.error('错误详情:', {
206 message: error.message,
207 response: error.response?.data,
208 status: error.response?.status
209 })
210
211 // 如果是网络错误,提供更详细的提示
212 if (error.code === 'ERR_NETWORK') {
213 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
214 } else {
215 ElMessage.error(`获取标签列表失败: ${error.message}`)
216 }
217 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800218 }
vulgar5201055346a2025-06-05 14:15:55 +0800219
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800220 const handleTorrentChange = (file) => {
vulgar5201055346a2025-06-05 14:15:55 +0800221 if (file) {
222 uploadForm.file = file.raw
223 fileList.value = [file]
224
225 // 自动校验文件字段
226 uploadFormRef.value?.validateField('file')
227 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800228 }
vulgar5201055346a2025-06-05 14:15:55 +0800229
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800230 const handleTorrentRemove = () => {
vulgar5201055346a2025-06-05 14:15:55 +0800231 uploadForm.file = null
232 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800233 }
vulgar5201055346a2025-06-05 14:15:55 +0800234
235 const submitUpload = async () => {
236 if (!uploadForm.file) {
237 ElMessage.warning('请先选择种子文件')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800238 return
239 }
vulgar5201055346a2025-06-05 14:15:55 +0800240
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800241 try {
242 await uploadFormRef.value?.validate()
243
244 uploading.value = true
vulgar5201055346a2025-06-05 14:15:55 +0800245 const formData = new FormData()
246 formData.append('file', uploadForm.file)
247 formData.append('title', uploadForm.title)
248 formData.append('subtitle', uploadForm.subtitle || '')
249 formData.append('category', uploadForm.category)
250 formData.append('description', uploadForm.description)
251 formData.append('anonymous', uploadForm.anonymous)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800252
vulgar5201055346a2025-06-05 14:15:55 +0800253 // 处理标签数组
254 if (uploadForm.tag && uploadForm.tag.length > 0) {
255 uploadForm.tag.forEach(tag => {
256 formData.append('tag', tag)
257 })
258 }
259
260 const response = await uploadTorrent(formData)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800261
vulgar5201055346a2025-06-05 14:15:55 +0800262 ElMessage.success('种子上传成功')
263 // 根据后端返回的数据跳转到种子详情页
264 if (response.data && response.data.infoHash) {
265 router.push(`/torrent/${response.data.infoHash}`)
266 } else {
267 router.push('/torrents') // 或者跳转到种子列表页
268 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800269 } catch (error) {
vulgar5201055346a2025-06-05 14:15:55 +0800270 console.error('Failed to upload torrent:', error)
271
272 // 根据后端返回的错误信息显示不同的提示
273 let errorMessage = '种子上传失败'
274 if (error.response?.data?.message) {
275 errorMessage = error.response.data.message
276 } else if (error.response?.data?.error) {
277 errorMessage = error.response.data.error
278 }
279
280 ElMessage.error(errorMessage)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800281 } finally {
282 uploading.value = false
283 }
284 }
vulgar5201055346a2025-06-05 14:15:55 +0800285
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800286 const resetForm = () => {
287 uploadFormRef.value?.resetFields()
vulgar5201055346a2025-06-05 14:15:55 +0800288 uploadRef.value?.clearFiles()
289 uploadForm.file = null
290 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800291 }
vulgar5201055346a2025-06-05 14:15:55 +0800292
293 // 组件挂载时加载数据
294 onMounted(() => {
295 loadCategories()
296 loadTags()
297 })
298
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800299 return {
300 uploadFormRef,
vulgar5201055346a2025-06-05 14:15:55 +0800301 uploadRef,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800302 uploading,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800303 uploadForm,
vulgar5201055346a2025-06-05 14:15:55 +0800304 uploadRules,
305 categories,
306 availableTags,
307 fileList,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800308 handleTorrentChange,
309 handleTorrentRemove,
vulgar5201055346a2025-06-05 14:15:55 +0800310 submitUpload,
311 resetForm
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800312 }
313 }
314}
315</script>
316
317<style lang="scss" scoped>
318.upload-page {
319 max-width: 800px;
320 margin: 0 auto;
321 padding: 24px;
322}
323
vulgar5201055346a2025-06-05 14:15:55 +0800324.upload-container {
325 background: #fff;
326 border-radius: 8px;
327 padding: 24px;
328 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
329
330 h2 {
331 margin: 0 0 24px;
332 padding-bottom: 16px;
333 border-bottom: 1px solid #eee;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800334 color: #2c3e50;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800335 }
336}
337
338.upload-form {
vulgar5201055346a2025-06-05 14:15:55 +0800339 .el-upload {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800340 width: 100%;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800341 }
vulgar5201055346a2025-06-05 14:15:55 +0800342
343 .el-upload__tip {
344 line-height: 1.2;
345 padding: 8px 0;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800346 color: #909399;
347 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800348}
349
350@media (max-width: 768px) {
351 .upload-page {
352 padding: 16px;
353 }
vulgar5201055346a2025-06-05 14:15:55 +0800354
355 .upload-container {
356 padding: 16px;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800357 }
vulgar5201055346a2025-06-05 14:15:55 +0800358
359 :deep(.el-form-item__label) {
360 float: none;
361 display: block;
362 text-align: left;
363 padding: 0 0 8px;
364 }
365
366 :deep(.el-form-item__content) {
367 margin-left: 0 !important;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800368 }
369}
xingjinwend652cc62025-06-04 19:52:19 +0800370</style>