blob: 9807cd1e35dc6903d7b483ca4225d84e75dcaf3e [file] [log] [blame]
Xing Jinwenff16b1e2025-06-05 00:29:26 +08001<template>
2 <div class="upload-page">
vulgar5201055346a2025-06-05 14:15:55 +08003 <div class="upload-container">
4 <h2>上传种子</h2>
5
Xing Jinwenff16b1e2025-06-05 00:29:26 +08006 <el-form
7 ref="uploadFormRef"
8 :model="uploadForm"
vulgar5201055346a2025-06-05 14:15:55 +08009 :rules="uploadRules"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080010 label-width="120px"
vulgar5201055346a2025-06-05 14:15:55 +080011 class="upload-form"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080012 >
13 <!-- 种子文件上传 -->
vulgar5201055346a2025-06-05 14:15:55 +080014 <el-form-item label="种子文件" prop="file">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080015 <el-upload
vulgar5201055346a2025-06-05 14:15:55 +080016 ref="uploadRef"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080017 :auto-upload="false"
18 :limit="1"
19 accept=".torrent"
20 :on-change="handleTorrentChange"
21 :on-remove="handleTorrentRemove"
vulgar5201055346a2025-06-05 14:15:55 +080022 :file-list="fileList"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080023 >
vulgar5201055346a2025-06-05 14:15:55 +080024 <template #trigger>
25 <el-button type="primary">选择文件</el-button>
26 </template>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080027 <template #tip>
28 <div class="el-upload__tip">
vulgar5201055346a2025-06-05 14:15:55 +080029 只能上传 .torrent 文件
Xing Jinwenff16b1e2025-06-05 00:29:26 +080030 </div>
31 </template>
32 </el-upload>
33 </el-form-item>
34
35 <!-- 基本信息 -->
vulgar5201055346a2025-06-05 14:15:55 +080036 <el-form-item label="标题" prop="title">
37 <el-input v-model="uploadForm.title" placeholder="请输入种子标题" />
Xing Jinwenff16b1e2025-06-05 00:29:26 +080038 </el-form-item>
39
vulgar5201055346a2025-06-05 14:15:55 +080040 <el-form-item label="副标题" prop="subtitle">
41 <el-input v-model="uploadForm.subtitle" placeholder="请输入副标题(可选)" />
42 </el-form-item>
43
44 <el-form-item label="分类" prop="category">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080045 <el-select v-model="uploadForm.category" placeholder="请选择分类">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080046 <el-option
vulgar5201055346a2025-06-05 14:15:55 +080047 v-for="category in categories"
48 :key="category.id"
49 :label="category.name"
50 :value="category.slug"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080051 />
52 </el-select>
53 </el-form-item>
54
vulgar5201055346a2025-06-05 14:15:55 +080055 <!-- 标签 -->
56 <el-form-item label="标签" prop="tag">
57 <el-select
58 v-model="uploadForm.tag"
59 multiple
60 filterable
61 allow-create
62 placeholder="请选择或输入标签"
63 >
64 <el-option
65 v-for="tag in availableTags"
66 :key="tag.id"
67 :label="tag.name"
68 :value="tag.name"
69 />
70 </el-select>
71 </el-form-item>
72
73 <!-- 描述信息 -->
74 <el-form-item label="描述" prop="description">
Xing Jinwenff16b1e2025-06-05 00:29:26 +080075 <el-input
76 v-model="uploadForm.description"
77 type="textarea"
78 :rows="6"
vulgar5201055346a2025-06-05 14:15:55 +080079 placeholder="请输入种子描述,支持 Markdown 格式"
Xing Jinwenff16b1e2025-06-05 00:29:26 +080080 />
81 </el-form-item>
82
vulgar5201055346a2025-06-05 14:15:55 +080083 <!-- 匿名发布 -->
Xing Jinwenff16b1e2025-06-05 00:29:26 +080084 <el-form-item>
vulgar5201055346a2025-06-05 14:15:55 +080085 <el-checkbox v-model="uploadForm.anonymous">匿名发布</el-checkbox>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080086 </el-form-item>
87
88 <!-- 提交按钮 -->
89 <el-form-item>
vulgar5201055346a2025-06-05 14:15:55 +080090 <el-button type="primary" @click="submitUpload" :loading="uploading">
91 上传种子
92 </el-button>
93 <el-button @click="resetForm">重置表单</el-button>
Xing Jinwenff16b1e2025-06-05 00:29:26 +080094 </el-form-item>
95 </el-form>
96 </div>
97 </div>
98</template>
99
100<script>
vulgar5201055346a2025-06-05 14:15:55 +0800101import { ref, reactive, onMounted } from 'vue'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800102import { useRouter } from 'vue-router'
103import { ElMessage } from 'element-plus'
vulgar5201055346a2025-06-05 14:15:55 +0800104import { uploadTorrent, getCategories, getTags } from '@/api/torrent'
20815951548db5f2a2025-06-09 23:58:33 +0800105
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800106export default {
107 name: 'UploadView',
108 setup() {
109 const router = useRouter()
110 const uploadFormRef = ref(null)
vulgar5201055346a2025-06-05 14:15:55 +0800111 const uploadRef = ref(null)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800112 const uploading = ref(false)
vulgar5201055346a2025-06-05 14:15:55 +0800113 const fileList = ref([])
114
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800115 const uploadForm = reactive({
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800116 title: '',
vulgar5201055346a2025-06-05 14:15:55 +0800117 subtitle: '',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800118 category: '',
vulgar5201055346a2025-06-05 14:15:55 +0800119 tag: [],
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800120 description: '',
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800121 anonymous: false,
vulgar5201055346a2025-06-05 14:15:55 +0800122 file: null
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800123 })
vulgar5201055346a2025-06-05 14:15:55 +0800124
125 const uploadRules = {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800126 title: [
vulgar5201055346a2025-06-05 14:15:55 +0800127 { required: true, message: '请输入种子标题', trigger: 'blur' },
128 { min: 3, max: 100, message: '标题长度应在 3 到 100 个字符之间', trigger: 'blur' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800129 ],
130 category: [
vulgar5201055346a2025-06-05 14:15:55 +0800131 { required: true, message: '请选择分类', trigger: 'change' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800132 ],
133 description: [
vulgar5201055346a2025-06-05 14:15:55 +0800134 { required: true, message: '请输入种子描述', trigger: 'blur' },
135 { min: 10, message: '描述至少需要 10 个字符', trigger: 'blur' }
136 ],
137 file: [
138 { required: true, message: '请上传种子文件', trigger: 'change' }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800139 ]
140 }
vulgar5201055346a2025-06-05 14:15:55 +0800141
142 const categories = ref([])
143 const availableTags = ref([])
144
145 // 获取分类列表
146 const loadCategories = async () => {
147 try {
148 console.log('开始加载分类列表...')
vulgar5201c4a15b12025-06-06 13:55:09 +0800149 console.log('当前token(分类列表):', localStorage.getItem('token'))
vulgar5201055346a2025-06-05 14:15:55 +0800150 const response = await getCategories()
151 console.log('分类列表响应:', response)
152
vulgar5201c4a15b12025-06-06 13:55:09 +0800153 const list = Array.isArray(response) ? response : response.data
154
155 if (list && list.length > 0) {
156 categories.value = list
vulgar5201055346a2025-06-05 14:15:55 +0800157 console.log('分类列表加载成功:', categories.value)
158 } else {
159 console.warn('分类列表数据为空')
160 categories.value = []
161 }
162 } catch (error) {
163 console.error('Failed to load categories:', error)
164 console.error('错误详情:', {
165 message: error.message,
166 response: error.response?.data,
167 status: error.response?.status,
168 config: error.config
169 })
170
171 // 根据错误类型显示不同的提示
172 if (error.response?.status === 401) {
173 ElMessage.error('请先登录')
174 router.push('/login')
175 } else if (error.response?.status === 403) {
176 ElMessage.error('没有权限访问分类列表')
177 } else if (error.code === 'ERR_NETWORK') {
178 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
179 } else {
180 ElMessage.error(`获取分类列表失败: ${error.message}`)
181 }
182 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800183 }
vulgar5201055346a2025-06-05 14:15:55 +0800184
185 // 获取标签列表
186 const loadTags = async () => {
187 try {
188 console.log('开始加载标签列表...')
189 const response = await getTags()
190 console.log('标签列表响应:', response)
191
192 if (response && response.data) {
193 availableTags.value = response.data
194 console.log('标签列表加载成功:', availableTags.value)
195 } else {
196 console.warn('标签列表数据为空')
197 availableTags.value = []
198 }
199 } catch (error) {
200 console.error('Failed to load tags:', error)
201 console.error('错误详情:', {
202 message: error.message,
203 response: error.response?.data,
204 status: error.response?.status
205 })
206
207 // 如果是网络错误,提供更详细的提示
208 if (error.code === 'ERR_NETWORK') {
209 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
210 } else {
211 ElMessage.error(`获取标签列表失败: ${error.message}`)
212 }
213 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800214 }
vulgar5201055346a2025-06-05 14:15:55 +0800215
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800216 const handleTorrentChange = (file) => {
vulgar5201055346a2025-06-05 14:15:55 +0800217 if (file) {
218 uploadForm.file = file.raw
219 fileList.value = [file]
220
221 // 自动校验文件字段
222 uploadFormRef.value?.validateField('file')
223 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800224 }
vulgar5201055346a2025-06-05 14:15:55 +0800225
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800226 const handleTorrentRemove = () => {
vulgar5201055346a2025-06-05 14:15:55 +0800227 uploadForm.file = null
228 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800229 }
vulgar5201055346a2025-06-05 14:15:55 +0800230
231 const submitUpload = async () => {
232 if (!uploadForm.file) {
233 ElMessage.warning('请先选择种子文件')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800234 return
235 }
vulgar5201055346a2025-06-05 14:15:55 +0800236
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800237 try {
238 await uploadFormRef.value?.validate()
239
240 uploading.value = true
vulgar5201055346a2025-06-05 14:15:55 +0800241 const formData = new FormData()
242 formData.append('file', uploadForm.file)
243 formData.append('title', uploadForm.title)
244 formData.append('subtitle', uploadForm.subtitle || '')
245 formData.append('category', uploadForm.category)
246 formData.append('description', uploadForm.description)
247 formData.append('anonymous', uploadForm.anonymous)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800248
vulgar5201055346a2025-06-05 14:15:55 +0800249 // 处理标签数组
250 if (uploadForm.tag && uploadForm.tag.length > 0) {
251 uploadForm.tag.forEach(tag => {
252 formData.append('tag', tag)
253 })
254 }
255
256 const response = await uploadTorrent(formData)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800257
vulgar5201055346a2025-06-05 14:15:55 +0800258 ElMessage.success('种子上传成功')
259 // 根据后端返回的数据跳转到种子详情页
260 if (response.data && response.data.infoHash) {
261 router.push(`/torrent/${response.data.infoHash}`)
262 } else {
263 router.push('/torrents') // 或者跳转到种子列表页
264 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800265 } catch (error) {
vulgar5201055346a2025-06-05 14:15:55 +0800266 console.error('Failed to upload torrent:', error)
267
268 // 根据后端返回的错误信息显示不同的提示
269 let errorMessage = '种子上传失败'
270 if (error.response?.data?.message) {
271 errorMessage = error.response.data.message
272 } else if (error.response?.data?.error) {
273 errorMessage = error.response.data.error
274 }
275
276 ElMessage.error(errorMessage)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800277 } finally {
278 uploading.value = false
279 }
280 }
vulgar5201055346a2025-06-05 14:15:55 +0800281
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800282 const resetForm = () => {
283 uploadFormRef.value?.resetFields()
vulgar5201055346a2025-06-05 14:15:55 +0800284 uploadRef.value?.clearFiles()
285 uploadForm.file = null
286 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800287 }
vulgar5201055346a2025-06-05 14:15:55 +0800288
289 // 组件挂载时加载数据
290 onMounted(() => {
291 loadCategories()
292 loadTags()
293 })
294
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800295 return {
296 uploadFormRef,
vulgar5201055346a2025-06-05 14:15:55 +0800297 uploadRef,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800298 uploading,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800299 uploadForm,
vulgar5201055346a2025-06-05 14:15:55 +0800300 uploadRules,
301 categories,
302 availableTags,
303 fileList,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800304 handleTorrentChange,
305 handleTorrentRemove,
vulgar5201055346a2025-06-05 14:15:55 +0800306 submitUpload,
307 resetForm
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800308 }
309 }
310}
311</script>
312
313<style lang="scss" scoped>
314.upload-page {
315 max-width: 800px;
316 margin: 0 auto;
317 padding: 24px;
318}
319
vulgar5201055346a2025-06-05 14:15:55 +0800320.upload-container {
321 background: #fff;
322 border-radius: 8px;
323 padding: 24px;
324 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
325
326 h2 {
327 margin: 0 0 24px;
328 padding-bottom: 16px;
329 border-bottom: 1px solid #eee;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800330 color: #2c3e50;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800331 }
332}
333
334.upload-form {
vulgar5201055346a2025-06-05 14:15:55 +0800335 .el-upload {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800336 width: 100%;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800337 }
vulgar5201055346a2025-06-05 14:15:55 +0800338
339 .el-upload__tip {
340 line-height: 1.2;
341 padding: 8px 0;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800342 color: #909399;
343 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800344}
345
346@media (max-width: 768px) {
347 .upload-page {
348 padding: 16px;
349 }
vulgar5201055346a2025-06-05 14:15:55 +0800350
351 .upload-container {
352 padding: 16px;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800353 }
vulgar5201055346a2025-06-05 14:15:55 +0800354
355 :deep(.el-form-item__label) {
356 float: none;
357 display: block;
358 text-align: left;
359 padding: 0 0 8px;
360 }
361
362 :deep(.el-form-item__content) {
363 margin-left: 0 !important;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800364 }
365}
xingjinwend652cc62025-06-04 19:52:19 +0800366</style>