blob: 6b154323d469361bb05df306d6f644db67f558af [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'
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800105
106export 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('开始加载分类列表...')
149 console.log('当前token:', localStorage.getItem('token'))
150 const response = await getCategories()
151 console.log('分类列表响应:', response)
152
153 if (response && response.data) {
154 categories.value = response.data
155 console.log('分类列表加载成功:', categories.value)
156 } else {
157 console.warn('分类列表数据为空')
158 categories.value = []
159 }
160 } catch (error) {
161 console.error('Failed to load categories:', error)
162 console.error('错误详情:', {
163 message: error.message,
164 response: error.response?.data,
165 status: error.response?.status,
166 config: error.config
167 })
168
169 // 根据错误类型显示不同的提示
170 if (error.response?.status === 401) {
171 ElMessage.error('请先登录')
172 router.push('/login')
173 } else if (error.response?.status === 403) {
174 ElMessage.error('没有权限访问分类列表')
175 } else if (error.code === 'ERR_NETWORK') {
176 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
177 } else {
178 ElMessage.error(`获取分类列表失败: ${error.message}`)
179 }
180 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800181 }
vulgar5201055346a2025-06-05 14:15:55 +0800182
183 // 获取标签列表
184 const loadTags = async () => {
185 try {
186 console.log('开始加载标签列表...')
187 const response = await getTags()
188 console.log('标签列表响应:', response)
189
190 if (response && response.data) {
191 availableTags.value = response.data
192 console.log('标签列表加载成功:', availableTags.value)
193 } else {
194 console.warn('标签列表数据为空')
195 availableTags.value = []
196 }
197 } catch (error) {
198 console.error('Failed to load tags:', error)
199 console.error('错误详情:', {
200 message: error.message,
201 response: error.response?.data,
202 status: error.response?.status
203 })
204
205 // 如果是网络错误,提供更详细的提示
206 if (error.code === 'ERR_NETWORK') {
207 ElMessage.error('无法连接到服务器,请检查后端服务是否启动')
208 } else {
209 ElMessage.error(`获取标签列表失败: ${error.message}`)
210 }
211 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800212 }
vulgar5201055346a2025-06-05 14:15:55 +0800213
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800214 const handleTorrentChange = (file) => {
vulgar5201055346a2025-06-05 14:15:55 +0800215 if (file) {
216 uploadForm.file = file.raw
217 fileList.value = [file]
218
219 // 自动校验文件字段
220 uploadFormRef.value?.validateField('file')
221 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800222 }
vulgar5201055346a2025-06-05 14:15:55 +0800223
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800224 const handleTorrentRemove = () => {
vulgar5201055346a2025-06-05 14:15:55 +0800225 uploadForm.file = null
226 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800227 }
vulgar5201055346a2025-06-05 14:15:55 +0800228
229 const submitUpload = async () => {
230 if (!uploadForm.file) {
231 ElMessage.warning('请先选择种子文件')
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800232 return
233 }
vulgar5201055346a2025-06-05 14:15:55 +0800234
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800235 try {
236 await uploadFormRef.value?.validate()
237
238 uploading.value = true
vulgar5201055346a2025-06-05 14:15:55 +0800239 const formData = new FormData()
240 formData.append('file', uploadForm.file)
241 formData.append('title', uploadForm.title)
242 formData.append('subtitle', uploadForm.subtitle || '')
243 formData.append('category', uploadForm.category)
244 formData.append('description', uploadForm.description)
245 formData.append('anonymous', uploadForm.anonymous)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800246
vulgar5201055346a2025-06-05 14:15:55 +0800247 // 处理标签数组
248 if (uploadForm.tag && uploadForm.tag.length > 0) {
249 uploadForm.tag.forEach(tag => {
250 formData.append('tag', tag)
251 })
252 }
253
254 const response = await uploadTorrent(formData)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800255
vulgar5201055346a2025-06-05 14:15:55 +0800256 ElMessage.success('种子上传成功')
257 // 根据后端返回的数据跳转到种子详情页
258 if (response.data && response.data.infoHash) {
259 router.push(`/torrent/${response.data.infoHash}`)
260 } else {
261 router.push('/torrents') // 或者跳转到种子列表页
262 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800263 } catch (error) {
vulgar5201055346a2025-06-05 14:15:55 +0800264 console.error('Failed to upload torrent:', error)
265
266 // 根据后端返回的错误信息显示不同的提示
267 let errorMessage = '种子上传失败'
268 if (error.response?.data?.message) {
269 errorMessage = error.response.data.message
270 } else if (error.response?.data?.error) {
271 errorMessage = error.response.data.error
272 }
273
274 ElMessage.error(errorMessage)
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800275 } finally {
276 uploading.value = false
277 }
278 }
vulgar5201055346a2025-06-05 14:15:55 +0800279
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800280 const resetForm = () => {
281 uploadFormRef.value?.resetFields()
vulgar5201055346a2025-06-05 14:15:55 +0800282 uploadRef.value?.clearFiles()
283 uploadForm.file = null
284 fileList.value = []
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800285 }
vulgar5201055346a2025-06-05 14:15:55 +0800286
287 // 组件挂载时加载数据
288 onMounted(() => {
289 loadCategories()
290 loadTags()
291 })
292
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800293 return {
294 uploadFormRef,
vulgar5201055346a2025-06-05 14:15:55 +0800295 uploadRef,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800296 uploading,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800297 uploadForm,
vulgar5201055346a2025-06-05 14:15:55 +0800298 uploadRules,
299 categories,
300 availableTags,
301 fileList,
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800302 handleTorrentChange,
303 handleTorrentRemove,
vulgar5201055346a2025-06-05 14:15:55 +0800304 submitUpload,
305 resetForm
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800306 }
307 }
308}
309</script>
310
311<style lang="scss" scoped>
312.upload-page {
313 max-width: 800px;
314 margin: 0 auto;
315 padding: 24px;
316}
317
vulgar5201055346a2025-06-05 14:15:55 +0800318.upload-container {
319 background: #fff;
320 border-radius: 8px;
321 padding: 24px;
322 box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
323
324 h2 {
325 margin: 0 0 24px;
326 padding-bottom: 16px;
327 border-bottom: 1px solid #eee;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800328 color: #2c3e50;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800329 }
330}
331
332.upload-form {
vulgar5201055346a2025-06-05 14:15:55 +0800333 .el-upload {
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800334 width: 100%;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800335 }
vulgar5201055346a2025-06-05 14:15:55 +0800336
337 .el-upload__tip {
338 line-height: 1.2;
339 padding: 8px 0;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800340 color: #909399;
341 }
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800342}
343
344@media (max-width: 768px) {
345 .upload-page {
346 padding: 16px;
347 }
vulgar5201055346a2025-06-05 14:15:55 +0800348
349 .upload-container {
350 padding: 16px;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800351 }
vulgar5201055346a2025-06-05 14:15:55 +0800352
353 :deep(.el-form-item__label) {
354 float: none;
355 display: block;
356 text-align: left;
357 padding: 0 0 8px;
358 }
359
360 :deep(.el-form-item__content) {
361 margin-left: 0 !important;
Xing Jinwenff16b1e2025-06-05 00:29:26 +0800362 }
363}
xingjinwend652cc62025-06-04 19:52:19 +0800364</style>