| <template> | |
| <div class="upload-page"> | |
| <div class="upload-container"> | |
| <h2>上传种子</h2> | |
| <el-form | |
| ref="uploadFormRef" | |
| :model="uploadForm" | |
| :rules="uploadRules" | |
| label-width="120px" | |
| class="upload-form" | |
| > | |
| <!-- 种子文件上传 --> | |
| <el-form-item label="种子文件" prop="file"> | |
| <el-upload | |
| ref="uploadRef" | |
| :auto-upload="false" | |
| :limit="1" | |
| accept=".torrent" | |
| :on-change="handleTorrentChange" | |
| :on-remove="handleTorrentRemove" | |
| :file-list="fileList" | |
| > | |
| <template #trigger> | |
| <el-button type="primary">选择文件</el-button> | |
| </template> | |
| <template #tip> | |
| <div class="el-upload__tip"> | |
| 只能上传 .torrent 文件 | |
| </div> | |
| </template> | |
| </el-upload> | |
| </el-form-item> | |
| <!-- 基本信息 --> | |
| <el-form-item label="标题" prop="title"> | |
| <el-input v-model="uploadForm.title" placeholder="请输入种子标题" /> | |
| </el-form-item> | |
| <el-form-item label="副标题" prop="subtitle"> | |
| <el-input v-model="uploadForm.subtitle" placeholder="请输入副标题(可选)" /> | |
| </el-form-item> | |
| <el-form-item label="分类" prop="category"> | |
| <el-select v-model="uploadForm.category" placeholder="请选择分类"> | |
| <el-option | |
| v-for="category in categories" | |
| :key="category.id" | |
| :label="category.name" | |
| :value="category.slug" | |
| /> | |
| </el-select> | |
| </el-form-item> | |
| <!-- 标签 --> | |
| <el-form-item label="标签" prop="tag"> | |
| <el-select | |
| v-model="uploadForm.tag" | |
| multiple | |
| filterable | |
| allow-create | |
| placeholder="请选择或输入标签" | |
| > | |
| <el-option | |
| v-for="tag in availableTags" | |
| :key="tag.id" | |
| :label="tag.name" | |
| :value="tag.name" | |
| /> | |
| </el-select> | |
| </el-form-item> | |
| <!-- 描述信息 --> | |
| <el-form-item label="描述" prop="description"> | |
| <el-input | |
| v-model="uploadForm.description" | |
| type="textarea" | |
| :rows="6" | |
| placeholder="请输入种子描述,支持 Markdown 格式" | |
| /> | |
| </el-form-item> | |
| <!-- 匿名发布 --> | |
| <el-form-item> | |
| <el-checkbox v-model="uploadForm.anonymous">匿名发布</el-checkbox> | |
| </el-form-item> | |
| <!-- 提交按钮 --> | |
| <el-form-item> | |
| <el-button type="primary" @click="submitUpload" :loading="uploading"> | |
| 上传种子 | |
| </el-button> | |
| <el-button @click="resetForm">重置表单</el-button> | |
| </el-form-item> | |
| </el-form> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { ref, reactive, onMounted } from 'vue' | |
| import { useRouter } from 'vue-router' | |
| import { ElMessage } from 'element-plus' | |
| import { uploadTorrent, getCategories, getTags } from '@/api/torrent' | |
| export default { | |
| name: 'UploadView', | |
| setup() { | |
| const router = useRouter() | |
| const uploadFormRef = ref(null) | |
| const uploadRef = ref(null) | |
| const uploading = ref(false) | |
| const fileList = ref([]) | |
| const uploadForm = reactive({ | |
| title: '', | |
| subtitle: '', | |
| category: '', | |
| tag: [], | |
| description: '', | |
| anonymous: false, | |
| file: null | |
| }) | |
| const uploadRules = { | |
| title: [ | |
| { required: true, message: '请输入种子标题', trigger: 'blur' }, | |
| { min: 3, max: 100, message: '标题长度应在 3 到 100 个字符之间', trigger: 'blur' } | |
| ], | |
| category: [ | |
| { required: true, message: '请选择分类', trigger: 'change' } | |
| ], | |
| description: [ | |
| { required: true, message: '请输入种子描述', trigger: 'blur' }, | |
| { min: 10, message: '描述至少需要 10 个字符', trigger: 'blur' } | |
| ], | |
| file: [ | |
| { required: true, message: '请上传种子文件', trigger: 'change' } | |
| ] | |
| } | |
| const categories = ref([]) | |
| const availableTags = ref([]) | |
| // 获取分类列表 | |
| const loadCategories = async () => { | |
| try { | |
| console.log('开始加载分类列表...') | |
| console.log('当前token(分类列表):', localStorage.getItem('token')) | |
| const response = await getCategories() | |
| console.log('分类列表响应:', response) | |
| const list = Array.isArray(response) ? response : response.data | |
| if (list && list.length > 0) { | |
| categories.value = list | |
| console.log('分类列表加载成功:', categories.value) | |
| } else { | |
| console.warn('分类列表数据为空') | |
| categories.value = [] | |
| } | |
| } catch (error) { | |
| console.error('Failed to load categories:', error) | |
| console.error('错误详情:', { | |
| message: error.message, | |
| response: error.response?.data, | |
| status: error.response?.status, | |
| config: error.config | |
| }) | |
| // 根据错误类型显示不同的提示 | |
| if (error.response?.status === 401) { | |
| ElMessage.error('请先登录') | |
| router.push('/login') | |
| } else if (error.response?.status === 403) { | |
| ElMessage.error('没有权限访问分类列表') | |
| } else if (error.code === 'ERR_NETWORK') { | |
| ElMessage.error('无法连接到服务器,请检查后端服务是否启动') | |
| } else { | |
| ElMessage.error(`获取分类列表失败: ${error.message}`) | |
| } | |
| } | |
| } | |
| // 获取标签列表 | |
| const loadTags = async () => { | |
| try { | |
| console.log('开始加载标签列表...') | |
| const response = await getTags() | |
| console.log('标签列表响应:', response) | |
| if (response && response.data) { | |
| availableTags.value = response.data | |
| console.log('标签列表加载成功:', availableTags.value) | |
| } else { | |
| console.warn('标签列表数据为空') | |
| availableTags.value = [] | |
| } | |
| } catch (error) { | |
| console.error('Failed to load tags:', error) | |
| console.error('错误详情:', { | |
| message: error.message, | |
| response: error.response?.data, | |
| status: error.response?.status | |
| }) | |
| // 如果是网络错误,提供更详细的提示 | |
| if (error.code === 'ERR_NETWORK') { | |
| ElMessage.error('无法连接到服务器,请检查后端服务是否启动') | |
| } else { | |
| ElMessage.error(`获取标签列表失败: ${error.message}`) | |
| } | |
| } | |
| } | |
| const handleTorrentChange = (file) => { | |
| if (file) { | |
| uploadForm.file = file.raw | |
| fileList.value = [file] | |
| // 自动校验文件字段 | |
| uploadFormRef.value?.validateField('file') | |
| } | |
| } | |
| const handleTorrentRemove = () => { | |
| uploadForm.file = null | |
| fileList.value = [] | |
| } | |
| const submitUpload = async () => { | |
| if (!uploadForm.file) { | |
| ElMessage.warning('请先选择种子文件') | |
| return | |
| } | |
| try { | |
| await uploadFormRef.value?.validate() | |
| uploading.value = true | |
| const formData = new FormData() | |
| formData.append('file', uploadForm.file) | |
| formData.append('title', uploadForm.title) | |
| formData.append('subtitle', uploadForm.subtitle || '') | |
| formData.append('category', uploadForm.category) | |
| formData.append('description', uploadForm.description) | |
| formData.append('anonymous', uploadForm.anonymous) | |
| // 处理标签数组 | |
| if (uploadForm.tag && uploadForm.tag.length > 0) { | |
| uploadForm.tag.forEach(tag => { | |
| formData.append('tag', tag) | |
| }) | |
| } | |
| const response = await uploadTorrent(formData) | |
| ElMessage.success('种子上传成功') | |
| // 根据后端返回的数据跳转到种子详情页 | |
| if (response.data && response.data.infoHash) { | |
| router.push(`/torrent/${response.data.infoHash}`) | |
| } else { | |
| router.push('/torrents') // 或者跳转到种子列表页 | |
| } | |
| } catch (error) { | |
| console.error('Failed to upload torrent:', error) | |
| // 根据后端返回的错误信息显示不同的提示 | |
| let errorMessage = '种子上传失败' | |
| if (error.response?.data?.message) { | |
| errorMessage = error.response.data.message | |
| } else if (error.response?.data?.error) { | |
| errorMessage = error.response.data.error | |
| } | |
| ElMessage.error(errorMessage) | |
| } finally { | |
| uploading.value = false | |
| } | |
| } | |
| const resetForm = () => { | |
| uploadFormRef.value?.resetFields() | |
| uploadRef.value?.clearFiles() | |
| uploadForm.file = null | |
| fileList.value = [] | |
| } | |
| // 组件挂载时加载数据 | |
| onMounted(() => { | |
| loadCategories() | |
| loadTags() | |
| }) | |
| return { | |
| uploadFormRef, | |
| uploadRef, | |
| uploading, | |
| uploadForm, | |
| uploadRules, | |
| categories, | |
| availableTags, | |
| fileList, | |
| handleTorrentChange, | |
| handleTorrentRemove, | |
| submitUpload, | |
| resetForm | |
| } | |
| } | |
| } | |
| </script> | |
| <style lang="scss" scoped> | |
| .upload-page { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 24px; | |
| } | |
| .upload-container { | |
| background: #fff; | |
| border-radius: 8px; | |
| padding: 24px; | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); | |
| h2 { | |
| margin: 0 0 24px; | |
| padding-bottom: 16px; | |
| border-bottom: 1px solid #eee; | |
| color: #2c3e50; | |
| } | |
| } | |
| .upload-form { | |
| .el-upload { | |
| width: 100%; | |
| } | |
| .el-upload__tip { | |
| line-height: 1.2; | |
| padding: 8px 0; | |
| color: #909399; | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .upload-page { | |
| padding: 16px; | |
| } | |
| .upload-container { | |
| padding: 16px; | |
| } | |
| :deep(.el-form-item__label) { | |
| float: none; | |
| display: block; | |
| text-align: left; | |
| padding: 0 0 8px; | |
| } | |
| :deep(.el-form-item__content) { | |
| margin-left: 0 !important; | |
| } | |
| } | |
| </style> |