Xing Jinwen | ff16b1e | 2025-06-05 00:29:26 +0800 | [diff] [blame^] | 1 | <template>
|
| 2 | <div class="upload-page">
|
| 3 | <div class="page-header">
|
| 4 | <h1>上传种子</h1>
|
| 5 | <p class="page-description">分享你的资源,为社区做贡献</p>
|
| 6 | </div>
|
| 7 |
|
| 8 | <div class="upload-form">
|
| 9 | <el-form
|
| 10 | ref="uploadFormRef"
|
| 11 | :model="uploadForm"
|
| 12 | :rules="formRules"
|
| 13 | label-width="120px"
|
| 14 | size="large"
|
| 15 | >
|
| 16 | <!-- 种子文件上传 -->
|
| 17 | <el-form-item label="种子文件" prop="torrentFile" required>
|
| 18 | <el-upload
|
| 19 | ref="torrentUploadRef"
|
| 20 | :auto-upload="false"
|
| 21 | :limit="1"
|
| 22 | accept=".torrent"
|
| 23 | :on-change="handleTorrentChange"
|
| 24 | :on-remove="handleTorrentRemove"
|
| 25 | :before-upload="beforeTorrentUpload"
|
| 26 | drag
|
| 27 | class="torrent-upload"
|
| 28 | >
|
| 29 | <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
| 30 | <div class="el-upload__text">
|
| 31 | 将 .torrent 文件拖到此处,或<em>点击上传</em>
|
| 32 | </div>
|
| 33 | <template #tip>
|
| 34 | <div class="el-upload__tip">
|
| 35 | 只能上传 .torrent 文件,且不超过 10MB
|
| 36 | </div>
|
| 37 | </template>
|
| 38 | </el-upload>
|
| 39 | </el-form-item>
|
| 40 |
|
| 41 | <!-- 基本信息 -->
|
| 42 | <el-form-item label="资源标题" prop="title" required>
|
| 43 | <el-input
|
| 44 | v-model="uploadForm.title"
|
| 45 | placeholder="请输入资源标题"
|
| 46 | maxlength="200"
|
| 47 | show-word-limit
|
| 48 | />
|
| 49 | </el-form-item>
|
| 50 |
|
| 51 | <el-form-item label="资源分类" prop="category" required>
|
| 52 | <el-select v-model="uploadForm.category" placeholder="请选择分类">
|
| 53 | <el-option label="电影" value="movie" />
|
| 54 | <el-option label="电视剧" value="tv" />
|
| 55 | <el-option label="音乐" value="music" />
|
| 56 | <el-option label="软件" value="software" />
|
| 57 | <el-option label="游戏" value="game" />
|
| 58 | <el-option label="电子书" value="ebook" />
|
| 59 | <el-option label="其他" value="other" />
|
| 60 | </el-select>
|
| 61 | </el-form-item>
|
| 62 |
|
| 63 | <el-form-item label="子分类" prop="subcategory">
|
| 64 | <el-select v-model="uploadForm.subcategory" placeholder="请选择子分类">
|
| 65 | <el-option
|
| 66 | v-for="sub in getSubcategories(uploadForm.category)"
|
| 67 | :key="sub.value"
|
| 68 | :label="sub.label"
|
| 69 | :value="sub.value"
|
| 70 | />
|
| 71 | </el-select>
|
| 72 | </el-form-item>
|
| 73 |
|
| 74 | <!-- 详细描述 -->
|
| 75 | <el-form-item label="资源描述" prop="description">
|
| 76 | <el-input
|
| 77 | v-model="uploadForm.description"
|
| 78 | type="textarea"
|
| 79 | :rows="6"
|
| 80 | placeholder="请详细描述资源内容,包括格式、质量、语言等信息"
|
| 81 | maxlength="2000"
|
| 82 | show-word-limit
|
| 83 | />
|
| 84 | </el-form-item>
|
| 85 |
|
| 86 | <!-- 标签 -->
|
| 87 | <el-form-item label="标签">
|
| 88 | <div class="tags-input">
|
| 89 | <el-tag
|
| 90 | v-for="tag in uploadForm.tags"
|
| 91 | :key="tag"
|
| 92 | closable
|
| 93 | @close="removeTag(tag)"
|
| 94 | class="tag-item"
|
| 95 | >
|
| 96 | {{ tag }}
|
| 97 | </el-tag>
|
| 98 | <el-input
|
| 99 | v-if="tagInputVisible"
|
| 100 | ref="tagInputRef"
|
| 101 | v-model="tagInputValue"
|
| 102 | size="small"
|
| 103 | @keyup.enter="addTag"
|
| 104 | @blur="addTag"
|
| 105 | class="tag-input"
|
| 106 | />
|
| 107 | <el-button
|
| 108 | v-else
|
| 109 | size="small"
|
| 110 | @click="showTagInput"
|
| 111 | class="add-tag-btn"
|
| 112 | >
|
| 113 | + 添加标签
|
| 114 | </el-button>
|
| 115 | </div>
|
| 116 | </el-form-item>
|
| 117 |
|
| 118 | <!-- 封面图片 -->
|
| 119 | <el-form-item label="封面图片">
|
| 120 | <el-upload
|
| 121 | ref="imageUploadRef"
|
| 122 | :auto-upload="false"
|
| 123 | :limit="1"
|
| 124 | accept="image/*"
|
| 125 | :on-change="handleImageChange"
|
| 126 | :on-remove="handleImageRemove"
|
| 127 | list-type="picture-card"
|
| 128 | class="image-upload"
|
| 129 | >
|
| 130 | <el-icon><Plus /></el-icon>
|
| 131 | <template #tip>
|
| 132 | <div class="el-upload__tip">
|
| 133 | 支持 JPG、PNG 格式,建议尺寸 300x400,不超过 5MB
|
| 134 | </div>
|
| 135 | </template>
|
| 136 | </el-upload>
|
| 137 | </el-form-item>
|
| 138 |
|
| 139 | <!-- 高级选项 -->
|
| 140 | <el-form-item>
|
| 141 | <el-collapse>
|
| 142 | <el-collapse-item title="高级选项" name="advanced">
|
| 143 | <el-form-item label="免费时间">
|
| 144 | <el-select v-model="uploadForm.freeTime" placeholder="选择免费时间">
|
| 145 | <el-option label="永久免费" value="forever" />
|
| 146 | <el-option label="24小时" value="24h" />
|
| 147 | <el-option label="48小时" value="48h" />
|
| 148 | <el-option label="7天" value="7d" />
|
| 149 | <el-option label="30天" value="30d" />
|
| 150 | </el-select>
|
| 151 | </el-form-item>
|
| 152 |
|
| 153 | <el-form-item label="匿名上传">
|
| 154 | <el-switch v-model="uploadForm.anonymous" />
|
| 155 | <span class="form-tip">开启后将不显示上传者信息</span>
|
| 156 | </el-form-item>
|
| 157 |
|
| 158 | <el-form-item label="允许HR">
|
| 159 | <el-switch v-model="uploadForm.allowHR" />
|
| 160 | <span class="form-tip">允许此种子参与HR考核</span>
|
| 161 | </el-form-item>
|
| 162 | </el-collapse-item>
|
| 163 | </el-collapse>
|
| 164 | </el-form-item>
|
| 165 |
|
| 166 | <!-- 提交按钮 -->
|
| 167 | <el-form-item>
|
| 168 | <div class="submit-buttons">
|
| 169 | <el-button @click="resetForm">重置</el-button>
|
| 170 | <el-button type="primary" @click="submitForm" :loading="uploading">
|
| 171 | {{ uploading ? '上传中...' : '提交种子' }}
|
| 172 | </el-button>
|
| 173 | </div>
|
| 174 | </el-form-item>
|
| 175 | </el-form>
|
| 176 | </div>
|
| 177 | </div>
|
| 178 | </template>
|
| 179 |
|
| 180 | <script>
|
| 181 | import { ref, reactive, nextTick } from 'vue'
|
| 182 | import { useRouter } from 'vue-router'
|
| 183 | import { ElMessage } from 'element-plus'
|
| 184 | import {
|
| 185 | UploadFilled,
|
| 186 | Plus
|
| 187 | } from '@element-plus/icons-vue'
|
| 188 |
|
| 189 | export default {
|
| 190 | name: 'UploadView',
|
| 191 | setup() {
|
| 192 | const router = useRouter()
|
| 193 | const uploadFormRef = ref(null)
|
| 194 | const torrentUploadRef = ref(null)
|
| 195 | const imageUploadRef = ref(null)
|
| 196 | const tagInputRef = ref(null)
|
| 197 |
|
| 198 | const uploading = ref(false)
|
| 199 | const tagInputVisible = ref(false)
|
| 200 | const tagInputValue = ref('')
|
| 201 |
|
| 202 | const uploadForm = reactive({
|
| 203 | torrentFile: null,
|
| 204 | title: '',
|
| 205 | category: '',
|
| 206 | subcategory: '',
|
| 207 | description: '',
|
| 208 | tags: [],
|
| 209 | coverImage: null,
|
| 210 | freeTime: '',
|
| 211 | anonymous: false,
|
| 212 | allowHR: true
|
| 213 | })
|
| 214 |
|
| 215 | const formRules = {
|
| 216 | title: [
|
| 217 | { required: true, message: '请输入资源标题', trigger: 'blur' },
|
| 218 | { min: 5, max: 200, message: '标题长度在 5 到 200 个字符', trigger: 'blur' }
|
| 219 | ],
|
| 220 | category: [
|
| 221 | { required: true, message: '请选择资源分类', trigger: 'change' }
|
| 222 | ],
|
| 223 | description: [
|
| 224 | { min: 10, max: 2000, message: '描述长度在 10 到 2000 个字符', trigger: 'blur' }
|
| 225 | ]
|
| 226 | }
|
| 227 |
|
| 228 | const subcategories = {
|
| 229 | movie: [
|
| 230 | { label: '动作片', value: 'action' },
|
| 231 | { label: '喜剧片', value: 'comedy' },
|
| 232 | { label: '科幻片', value: 'scifi' },
|
| 233 | { label: '恐怖片', value: 'horror' },
|
| 234 | { label: '剧情片', value: 'drama' }
|
| 235 | ],
|
| 236 | tv: [
|
| 237 | { label: '美剧', value: 'us' },
|
| 238 | { label: '国产剧', value: 'cn' },
|
| 239 | { label: '日韩剧', value: 'asia' },
|
| 240 | { label: '英剧', value: 'uk' },
|
| 241 | { label: '纪录片', value: 'documentary' }
|
| 242 | ],
|
| 243 | music: [
|
| 244 | { label: '流行音乐', value: 'pop' },
|
| 245 | { label: '古典音乐', value: 'classical' },
|
| 246 | { label: '摇滚音乐', value: 'rock' },
|
| 247 | { label: '电子音乐', value: 'electronic' },
|
| 248 | { label: '其他', value: 'other' }
|
| 249 | ],
|
| 250 | software: [
|
| 251 | { label: '操作系统', value: 'os' },
|
| 252 | { label: '办公软件', value: 'office' },
|
| 253 | { label: '开发工具', value: 'dev' },
|
| 254 | { label: '设计软件', value: 'design' },
|
| 255 | { label: '其他', value: 'other' }
|
| 256 | ],
|
| 257 | game: [
|
| 258 | { label: 'PC游戏', value: 'pc' },
|
| 259 | { label: '主机游戏', value: 'console' },
|
| 260 | { label: '手机游戏', value: 'mobile' },
|
| 261 | { label: '其他', value: 'other' }
|
| 262 | ]
|
| 263 | }
|
| 264 |
|
| 265 | const getSubcategories = (category) => {
|
| 266 | return subcategories[category] || []
|
| 267 | }
|
| 268 |
|
| 269 | const handleTorrentChange = (file) => {
|
| 270 | uploadForm.torrentFile = file.raw
|
| 271 | // 这里可以解析torrent文件获取基本信息
|
| 272 | parseTorrentFile(file.raw)
|
| 273 | }
|
| 274 |
|
| 275 | const handleTorrentRemove = () => {
|
| 276 | uploadForm.torrentFile = null
|
| 277 | }
|
| 278 |
|
| 279 | const beforeTorrentUpload = (file) => {
|
| 280 | const isTorrent = file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent')
|
| 281 | const isLt10M = file.size / 1024 / 1024 < 10
|
| 282 |
|
| 283 | if (!isTorrent) {
|
| 284 | ElMessage.error('只能上传 .torrent 文件!')
|
| 285 | return false
|
| 286 | }
|
| 287 | if (!isLt10M) {
|
| 288 | ElMessage.error('种子文件大小不能超过 10MB!')
|
| 289 | return false
|
| 290 | }
|
| 291 | return true
|
| 292 | }
|
| 293 |
|
| 294 | const parseTorrentFile = (file) => {
|
| 295 | // 这里应该实现torrent文件解析
|
| 296 | // 可以使用 parse-torrent 库
|
| 297 | console.log('解析种子文件:', file.name)
|
| 298 |
|
| 299 | // 模拟解析结果自动填入表单
|
| 300 | if (!uploadForm.title) {
|
| 301 | uploadForm.title = file.name.replace('.torrent', '')
|
| 302 | }
|
| 303 | }
|
| 304 |
|
| 305 | const handleImageChange = (file) => {
|
| 306 | uploadForm.coverImage = file.raw
|
| 307 | }
|
| 308 |
|
| 309 | const handleImageRemove = () => {
|
| 310 | uploadForm.coverImage = null
|
| 311 | }
|
| 312 |
|
| 313 | const showTagInput = () => {
|
| 314 | tagInputVisible.value = true
|
| 315 | nextTick(() => {
|
| 316 | tagInputRef.value?.focus()
|
| 317 | })
|
| 318 | }
|
| 319 |
|
| 320 | const addTag = () => {
|
| 321 | const tag = tagInputValue.value.trim()
|
| 322 | if (tag && !uploadForm.tags.includes(tag)) {
|
| 323 | uploadForm.tags.push(tag)
|
| 324 | }
|
| 325 | tagInputVisible.value = false
|
| 326 | tagInputValue.value = ''
|
| 327 | }
|
| 328 |
|
| 329 | const removeTag = (tag) => {
|
| 330 | const index = uploadForm.tags.indexOf(tag)
|
| 331 | if (index > -1) {
|
| 332 | uploadForm.tags.splice(index, 1)
|
| 333 | }
|
| 334 | }
|
| 335 |
|
| 336 | const submitForm = async () => {
|
| 337 | if (!uploadForm.torrentFile) {
|
| 338 | ElMessage.error('请上传种子文件')
|
| 339 | return
|
| 340 | }
|
| 341 |
|
| 342 | try {
|
| 343 | await uploadFormRef.value?.validate()
|
| 344 |
|
| 345 | uploading.value = true
|
| 346 |
|
| 347 | // 模拟上传过程
|
| 348 | await new Promise(resolve => setTimeout(resolve, 2000))
|
| 349 |
|
| 350 | ElMessage.success('种子上传成功!')
|
| 351 | router.push('/torrents')
|
| 352 |
|
| 353 | } catch (error) {
|
| 354 | console.error('表单验证失败:', error)
|
| 355 | } finally {
|
| 356 | uploading.value = false
|
| 357 | }
|
| 358 | }
|
| 359 |
|
| 360 | const resetForm = () => {
|
| 361 | uploadFormRef.value?.resetFields()
|
| 362 | uploadForm.torrentFile = null
|
| 363 | uploadForm.coverImage = null
|
| 364 | uploadForm.tags = []
|
| 365 | torrentUploadRef.value?.clearFiles()
|
| 366 | imageUploadRef.value?.clearFiles()
|
| 367 | }
|
| 368 |
|
| 369 | return {
|
| 370 | uploadFormRef,
|
| 371 | torrentUploadRef,
|
| 372 | imageUploadRef,
|
| 373 | tagInputRef,
|
| 374 | uploading,
|
| 375 | tagInputVisible,
|
| 376 | tagInputValue,
|
| 377 | uploadForm,
|
| 378 | formRules,
|
| 379 | getSubcategories,
|
| 380 | handleTorrentChange,
|
| 381 | handleTorrentRemove,
|
| 382 | beforeTorrentUpload,
|
| 383 | handleImageChange,
|
| 384 | handleImageRemove,
|
| 385 | showTagInput,
|
| 386 | addTag,
|
| 387 | removeTag,
|
| 388 | submitForm,
|
| 389 | resetForm,
|
| 390 | UploadFilled,
|
| 391 | Plus
|
| 392 | }
|
| 393 | }
|
| 394 | }
|
| 395 | </script>
|
| 396 |
|
| 397 | <style lang="scss" scoped>
|
| 398 | .upload-page {
|
| 399 | max-width: 800px;
|
| 400 | margin: 0 auto;
|
| 401 | padding: 24px;
|
| 402 | }
|
| 403 |
|
| 404 | .page-header {
|
| 405 | text-align: center;
|
| 406 | margin-bottom: 32px;
|
| 407 |
|
| 408 | h1 {
|
| 409 | font-size: 28px;
|
| 410 | font-weight: 600;
|
| 411 | color: #2c3e50;
|
| 412 | margin: 0 0 8px 0;
|
| 413 | }
|
| 414 |
|
| 415 | .page-description {
|
| 416 | font-size: 16px;
|
| 417 | color: #7f8c8d;
|
| 418 | margin: 0;
|
| 419 | }
|
| 420 | }
|
| 421 |
|
| 422 | .upload-form {
|
| 423 | background: #fff;
|
| 424 | border-radius: 12px;
|
| 425 | padding: 32px;
|
| 426 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
| 427 |
|
| 428 | .torrent-upload {
|
| 429 | width: 100%;
|
| 430 |
|
| 431 | :deep(.el-upload-dragger) {
|
| 432 | width: 100%;
|
| 433 | height: 180px;
|
| 434 | border: 2px dashed #d9d9d9;
|
| 435 | border-radius: 8px;
|
| 436 |
|
| 437 | &:hover {
|
| 438 | border-color: #409eff;
|
| 439 | }
|
| 440 | }
|
| 441 | }
|
| 442 |
|
| 443 | .tags-input {
|
| 444 | display: flex;
|
| 445 | flex-wrap: wrap;
|
| 446 | gap: 8px;
|
| 447 | align-items: center;
|
| 448 |
|
| 449 | .tag-item {
|
| 450 | margin: 0;
|
| 451 | }
|
| 452 |
|
| 453 | .tag-input {
|
| 454 | width: 100px;
|
| 455 | }
|
| 456 |
|
| 457 | .add-tag-btn {
|
| 458 | border: 1px dashed #d9d9d9;
|
| 459 | color: #999;
|
| 460 |
|
| 461 | &:hover {
|
| 462 | border-color: #409eff;
|
| 463 | color: #409eff;
|
| 464 | }
|
| 465 | }
|
| 466 | }
|
| 467 |
|
| 468 | .image-upload {
|
| 469 | :deep(.el-upload--picture-card) {
|
| 470 | width: 148px;
|
| 471 | height: 148px;
|
| 472 | }
|
| 473 | }
|
| 474 |
|
| 475 | .form-tip {
|
| 476 | margin-left: 8px;
|
| 477 | font-size: 12px;
|
| 478 | color: #909399;
|
| 479 | }
|
| 480 |
|
| 481 | .submit-buttons {
|
| 482 | display: flex;
|
| 483 | gap: 16px;
|
| 484 | justify-content: center;
|
| 485 | margin-top: 24px;
|
| 486 | }
|
| 487 | }
|
| 488 |
|
| 489 | @media (max-width: 768px) {
|
| 490 | .upload-page {
|
| 491 | padding: 16px;
|
| 492 | }
|
| 493 |
|
| 494 | .upload-form {
|
| 495 | padding: 24px 16px;
|
| 496 | }
|
| 497 |
|
| 498 | .submit-buttons {
|
| 499 | flex-direction: column;
|
| 500 |
|
| 501 | .el-button {
|
| 502 | width: 100%;
|
| 503 | }
|
| 504 | }
|
| 505 | }
|
xingjinwen | d652cc6 | 2025-06-04 19:52:19 +0800 | [diff] [blame] | 506 | </style> |