在后端api未测试的前提下完成种子上传
Change-Id: Ie7387ec1db864125afef0cbc08e4f57fb8a91b19
diff --git a/src/views/torrent/UploadView.vue b/src/views/torrent/UploadView.vue
index 80c3660..6b15432 100644
--- a/src/views/torrent/UploadView.vue
+++ b/src/views/torrent/UploadView.vue
@@ -1,176 +1,96 @@
<template>
<div class="upload-page">
- <div class="page-header">
- <h1>上传种子</h1>
- <p class="page-description">分享你的资源,为社区做贡献</p>
- </div>
-
- <div class="upload-form">
+ <div class="upload-container">
+ <h2>上传种子</h2>
+
<el-form
ref="uploadFormRef"
:model="uploadForm"
- :rules="formRules"
+ :rules="uploadRules"
label-width="120px"
- size="large"
+ class="upload-form"
>
<!-- 种子文件上传 -->
- <el-form-item label="种子文件" prop="torrentFile" required>
+ <el-form-item label="种子文件" prop="file">
<el-upload
- ref="torrentUploadRef"
+ ref="uploadRef"
:auto-upload="false"
:limit="1"
accept=".torrent"
:on-change="handleTorrentChange"
:on-remove="handleTorrentRemove"
- :before-upload="beforeTorrentUpload"
- drag
- class="torrent-upload"
+ :file-list="fileList"
>
- <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
- <div class="el-upload__text">
- 将 .torrent 文件拖到此处,或<em>点击上传</em>
- </div>
+ <template #trigger>
+ <el-button type="primary">选择文件</el-button>
+ </template>
<template #tip>
<div class="el-upload__tip">
- 只能上传 .torrent 文件,且不超过 10MB
+ 只能上传 .torrent 文件
</div>
</template>
</el-upload>
</el-form-item>
<!-- 基本信息 -->
- <el-form-item label="资源标题" prop="title" required>
- <el-input
- v-model="uploadForm.title"
- placeholder="请输入资源标题"
- maxlength="200"
- show-word-limit
- />
+ <el-form-item label="标题" prop="title">
+ <el-input v-model="uploadForm.title" placeholder="请输入种子标题" />
</el-form-item>
- <el-form-item label="资源分类" prop="category" required>
+ <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 label="电影" value="movie" />
- <el-option label="电视剧" value="tv" />
- <el-option label="音乐" value="music" />
- <el-option label="软件" value="software" />
- <el-option label="游戏" value="game" />
- <el-option label="电子书" value="ebook" />
- <el-option label="其他" value="other" />
- </el-select>
- </el-form-item>
-
- <el-form-item label="子分类" prop="subcategory">
- <el-select v-model="uploadForm.subcategory" placeholder="请选择子分类">
<el-option
- v-for="sub in getSubcategories(uploadForm.category)"
- :key="sub.value"
- :label="sub.label"
- :value="sub.value"
+ v-for="category in categories"
+ :key="category.id"
+ :label="category.name"
+ :value="category.slug"
/>
</el-select>
</el-form-item>
- <!-- 详细描述 -->
- <el-form-item label="资源描述" prop="description">
+ <!-- 标签 -->
+ <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="请详细描述资源内容,包括格式、质量、语言等信息"
- maxlength="2000"
- show-word-limit
+ placeholder="请输入种子描述,支持 Markdown 格式"
/>
</el-form-item>
- <!-- 标签 -->
- <el-form-item label="标签">
- <div class="tags-input">
- <el-tag
- v-for="tag in uploadForm.tags"
- :key="tag"
- closable
- @close="removeTag(tag)"
- class="tag-item"
- >
- {{ tag }}
- </el-tag>
- <el-input
- v-if="tagInputVisible"
- ref="tagInputRef"
- v-model="tagInputValue"
- size="small"
- @keyup.enter="addTag"
- @blur="addTag"
- class="tag-input"
- />
- <el-button
- v-else
- size="small"
- @click="showTagInput"
- class="add-tag-btn"
- >
- + 添加标签
- </el-button>
- </div>
- </el-form-item>
-
- <!-- 封面图片 -->
- <el-form-item label="封面图片">
- <el-upload
- ref="imageUploadRef"
- :auto-upload="false"
- :limit="1"
- accept="image/*"
- :on-change="handleImageChange"
- :on-remove="handleImageRemove"
- list-type="picture-card"
- class="image-upload"
- >
- <el-icon><Plus /></el-icon>
- <template #tip>
- <div class="el-upload__tip">
- 支持 JPG、PNG 格式,建议尺寸 300x400,不超过 5MB
- </div>
- </template>
- </el-upload>
- </el-form-item>
-
- <!-- 高级选项 -->
+ <!-- 匿名发布 -->
<el-form-item>
- <el-collapse>
- <el-collapse-item title="高级选项" name="advanced">
- <el-form-item label="免费时间">
- <el-select v-model="uploadForm.freeTime" placeholder="选择免费时间">
- <el-option label="永久免费" value="forever" />
- <el-option label="24小时" value="24h" />
- <el-option label="48小时" value="48h" />
- <el-option label="7天" value="7d" />
- <el-option label="30天" value="30d" />
- </el-select>
- </el-form-item>
-
- <el-form-item label="匿名上传">
- <el-switch v-model="uploadForm.anonymous" />
- <span class="form-tip">开启后将不显示上传者信息</span>
- </el-form-item>
-
- <el-form-item label="允许HR">
- <el-switch v-model="uploadForm.allowHR" />
- <span class="form-tip">允许此种子参与HR考核</span>
- </el-form-item>
- </el-collapse-item>
- </el-collapse>
+ <el-checkbox v-model="uploadForm.anonymous">匿名发布</el-checkbox>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item>
- <div class="submit-buttons">
- <el-button @click="resetForm">重置</el-button>
- <el-button type="primary" @click="submitForm" :loading="uploading">
- {{ uploading ? '上传中...' : '提交种子' }}
- </el-button>
- </div>
+ <el-button type="primary" @click="submitUpload" :loading="uploading">
+ 上传种子
+ </el-button>
+ <el-button @click="resetForm">重置表单</el-button>
</el-form-item>
</el-form>
</div>
@@ -178,217 +98,211 @@
</template>
<script>
-import { ref, reactive, nextTick } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
-import {
- UploadFilled,
- Plus
-} from '@element-plus/icons-vue'
+import { uploadTorrent, getCategories, getTags } from '@/api/torrent'
export default {
name: 'UploadView',
setup() {
const router = useRouter()
const uploadFormRef = ref(null)
- const torrentUploadRef = ref(null)
- const imageUploadRef = ref(null)
- const tagInputRef = ref(null)
-
+ const uploadRef = ref(null)
const uploading = ref(false)
- const tagInputVisible = ref(false)
- const tagInputValue = ref('')
-
+ const fileList = ref([])
+
const uploadForm = reactive({
- torrentFile: null,
title: '',
+ subtitle: '',
category: '',
- subcategory: '',
+ tag: [],
description: '',
- tags: [],
- coverImage: null,
- freeTime: '',
anonymous: false,
- allowHR: true
+ file: null
})
-
- const formRules = {
+
+ const uploadRules = {
title: [
- { required: true, message: '请输入资源标题', trigger: 'blur' },
- { min: 5, max: 200, message: '标题长度在 5 到 200 个字符', trigger: 'blur' }
+ { required: true, message: '请输入种子标题', trigger: 'blur' },
+ { min: 3, max: 100, message: '标题长度应在 3 到 100 个字符之间', trigger: 'blur' }
],
category: [
- { required: true, message: '请选择资源分类', trigger: 'change' }
+ { required: true, message: '请选择分类', trigger: 'change' }
],
description: [
- { min: 10, max: 2000, message: '描述长度在 10 到 2000 个字符', trigger: 'blur' }
+ { required: true, message: '请输入种子描述', trigger: 'blur' },
+ { min: 10, message: '描述至少需要 10 个字符', trigger: 'blur' }
+ ],
+ file: [
+ { required: true, message: '请上传种子文件', trigger: 'change' }
]
}
-
- const subcategories = {
- movie: [
- { label: '动作片', value: 'action' },
- { label: '喜剧片', value: 'comedy' },
- { label: '科幻片', value: 'scifi' },
- { label: '恐怖片', value: 'horror' },
- { label: '剧情片', value: 'drama' }
- ],
- tv: [
- { label: '美剧', value: 'us' },
- { label: '国产剧', value: 'cn' },
- { label: '日韩剧', value: 'asia' },
- { label: '英剧', value: 'uk' },
- { label: '纪录片', value: 'documentary' }
- ],
- music: [
- { label: '流行音乐', value: 'pop' },
- { label: '古典音乐', value: 'classical' },
- { label: '摇滚音乐', value: 'rock' },
- { label: '电子音乐', value: 'electronic' },
- { label: '其他', value: 'other' }
- ],
- software: [
- { label: '操作系统', value: 'os' },
- { label: '办公软件', value: 'office' },
- { label: '开发工具', value: 'dev' },
- { label: '设计软件', value: 'design' },
- { label: '其他', value: 'other' }
- ],
- game: [
- { label: 'PC游戏', value: 'pc' },
- { label: '主机游戏', value: 'console' },
- { label: '手机游戏', value: 'mobile' },
- { label: '其他', value: 'other' }
- ]
+
+ 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)
+
+ if (response && response.data) {
+ categories.value = response.data
+ 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 getSubcategories = (category) => {
- return subcategories[category] || []
+
+ // 获取标签列表
+ 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) => {
- uploadForm.torrentFile = file.raw
- // 这里可以解析torrent文件获取基本信息
- parseTorrentFile(file.raw)
+ if (file) {
+ uploadForm.file = file.raw
+ fileList.value = [file]
+
+ // 自动校验文件字段
+ uploadFormRef.value?.validateField('file')
+ }
}
-
+
const handleTorrentRemove = () => {
- uploadForm.torrentFile = null
+ uploadForm.file = null
+ fileList.value = []
}
-
- const beforeTorrentUpload = (file) => {
- const isTorrent = file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent')
- const isLt10M = file.size / 1024 / 1024 < 10
-
- if (!isTorrent) {
- ElMessage.error('只能上传 .torrent 文件!')
- return false
- }
- if (!isLt10M) {
- ElMessage.error('种子文件大小不能超过 10MB!')
- return false
- }
- return true
- }
-
- const parseTorrentFile = (file) => {
- // 这里应该实现torrent文件解析
- // 可以使用 parse-torrent 库
- console.log('解析种子文件:', file.name)
-
- // 模拟解析结果自动填入表单
- if (!uploadForm.title) {
- uploadForm.title = file.name.replace('.torrent', '')
- }
- }
-
- const handleImageChange = (file) => {
- uploadForm.coverImage = file.raw
- }
-
- const handleImageRemove = () => {
- uploadForm.coverImage = null
- }
-
- const showTagInput = () => {
- tagInputVisible.value = true
- nextTick(() => {
- tagInputRef.value?.focus()
- })
- }
-
- const addTag = () => {
- const tag = tagInputValue.value.trim()
- if (tag && !uploadForm.tags.includes(tag)) {
- uploadForm.tags.push(tag)
- }
- tagInputVisible.value = false
- tagInputValue.value = ''
- }
-
- const removeTag = (tag) => {
- const index = uploadForm.tags.indexOf(tag)
- if (index > -1) {
- uploadForm.tags.splice(index, 1)
- }
- }
-
- const submitForm = async () => {
- if (!uploadForm.torrentFile) {
- ElMessage.error('请上传种子文件')
+
+ 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)
- // 模拟上传过程
- await new Promise(resolve => setTimeout(resolve, 2000))
+ // 处理标签数组
+ if (uploadForm.tag && uploadForm.tag.length > 0) {
+ uploadForm.tag.forEach(tag => {
+ formData.append('tag', tag)
+ })
+ }
+
+ const response = await uploadTorrent(formData)
- ElMessage.success('种子上传成功!')
- router.push('/torrents')
-
+ ElMessage.success('种子上传成功')
+ // 根据后端返回的数据跳转到种子详情页
+ if (response.data && response.data.infoHash) {
+ router.push(`/torrent/${response.data.infoHash}`)
+ } else {
+ router.push('/torrents') // 或者跳转到种子列表页
+ }
} catch (error) {
- console.error('表单验证失败:', 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()
- uploadForm.torrentFile = null
- uploadForm.coverImage = null
- uploadForm.tags = []
- torrentUploadRef.value?.clearFiles()
- imageUploadRef.value?.clearFiles()
+ uploadRef.value?.clearFiles()
+ uploadForm.file = null
+ fileList.value = []
}
-
+
+ // 组件挂载时加载数据
+ onMounted(() => {
+ loadCategories()
+ loadTags()
+ })
+
return {
uploadFormRef,
- torrentUploadRef,
- imageUploadRef,
- tagInputRef,
+ uploadRef,
uploading,
- tagInputVisible,
- tagInputValue,
uploadForm,
- formRules,
- getSubcategories,
+ uploadRules,
+ categories,
+ availableTags,
+ fileList,
handleTorrentChange,
handleTorrentRemove,
- beforeTorrentUpload,
- handleImageChange,
- handleImageRemove,
- showTagInput,
- addTag,
- removeTag,
- submitForm,
- resetForm,
- UploadFilled,
- Plus
+ submitUpload,
+ resetForm
}
}
}
@@ -401,106 +315,50 @@
padding: 24px;
}
-.page-header {
- text-align: center;
- margin-bottom: 32px;
-
- h1 {
- font-size: 28px;
- font-weight: 600;
+.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;
- margin: 0 0 8px 0;
- }
-
- .page-description {
- font-size: 16px;
- color: #7f8c8d;
- margin: 0;
}
}
.upload-form {
- background: #fff;
- border-radius: 12px;
- padding: 32px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
-
- .torrent-upload {
+ .el-upload {
width: 100%;
-
- :deep(.el-upload-dragger) {
- width: 100%;
- height: 180px;
- border: 2px dashed #d9d9d9;
- border-radius: 8px;
-
- &:hover {
- border-color: #409eff;
- }
- }
}
-
- .tags-input {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
-
- .tag-item {
- margin: 0;
- }
-
- .tag-input {
- width: 100px;
- }
-
- .add-tag-btn {
- border: 1px dashed #d9d9d9;
- color: #999;
-
- &:hover {
- border-color: #409eff;
- color: #409eff;
- }
- }
- }
-
- .image-upload {
- :deep(.el-upload--picture-card) {
- width: 148px;
- height: 148px;
- }
- }
-
- .form-tip {
- margin-left: 8px;
- font-size: 12px;
+
+ .el-upload__tip {
+ line-height: 1.2;
+ padding: 8px 0;
color: #909399;
}
-
- .submit-buttons {
- display: flex;
- gap: 16px;
- justify-content: center;
- margin-top: 24px;
- }
}
@media (max-width: 768px) {
.upload-page {
padding: 16px;
}
-
- .upload-form {
- padding: 24px 16px;
+
+ .upload-container {
+ padding: 16px;
}
-
- .submit-buttons {
- flex-direction: column;
-
- .el-button {
- width: 100%;
- }
+
+ :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>
\ No newline at end of file