Merge "rong qi hua qian duan" into main
diff --git a/.env.development b/.env.development
index bbed15d..8ecc252 100644
--- a/.env.development
+++ b/.env.development
@@ -1 +1,7 @@
-VUE_APP_API_BASE_URL=http://localhost:8081
\ No newline at end of file
+# .env.development  ——  只在开发环境生效
+
+# 给 axios 用:全部请求走 /api,相对路径即可触发 devServer 代理
+VUE_APP_BASE_API=/api
+
+# 给 devServer 代理用:本机后端地址(需要时可改成 Docker / VM IP)
+VUE_APP_BACKEND=http://localhost:8081
diff --git a/src/api/auth.js b/src/api/auth.js
index a3c672a..b0f11aa 100644
--- a/src/api/auth.js
+++ b/src/api/auth.js
@@ -13,7 +13,7 @@
     }

     

     return request({

-      url: '/api/auth/login',  // 需要 /api 前缀

+      url: '/auth/login',  // 需要 /api 前缀

       method: 'post',

       data: requestData

     })

@@ -32,7 +32,7 @@
     }

     

     return request({

-      url: '/api/auth/register',  // 需要 /api 前缀

+      url: '/auth/register',  // 需要 /api 前缀

       method: 'post',

       data: requestData

     })

@@ -43,7 +43,7 @@
    */

   logout() {

     return request({

-      url: '/api/auth/logout',  // 需要 /api 前缀

+      url: '/auth/logout',  // 需要 /api 前缀

       method: 'post'

     })

   },

@@ -53,7 +53,7 @@
    */

   getStatus() {

     return request({

-      url: '/api/auth/status',  // 需要 /api 前缀

+      url: '/auth/status',  // 需要 /api 前缀

       method: 'get'

     })

   }

diff --git a/src/api/request.js b/src/api/request.js
index dd5aade..0e06f0a 100644
--- a/src/api/request.js
+++ b/src/api/request.js
@@ -1,26 +1,29 @@
 import axios from 'axios'

 import { ElMessage } from 'element-plus'

 import router from '@/router'

-

+//test

 const request = axios.create({

   // 关键:不要设置baseURL,或者设置为空字符串

   // 这样请求会发送到当前域名(8080),然后被代理转发到8081

-  baseURL: '',

+  baseURL: process.env.VUE_APP_BASE_API || '/api',

   timeout: 10000,

   headers: {

-    'Content-Type': 'application/json'

+    // 'Content-Type': 'application/json'

   }

 })

 

 request.interceptors.request.use(

   config => {

     const token = localStorage.getItem('token')

+    console.log('📤 添加 token 到请求头:', token)

     if (token) {

       config.headers.Authorization = `Bearer ${token}`

     }

     

     console.log('🚀 发送请求:', config.method?.toUpperCase(), config.url)

     console.log('📤 请求数据:', config.data)

+    console.log('🔍 VUE_APP_BASE_API:', process.env.VUE_APP_BASE_API)

+    console.log('🔍 VUE_APP_BACKEND:', process.env.VUE_APP_BACKEND)

     

     return config

   },

diff --git a/src/api/torrent.js b/src/api/torrent.js
new file mode 100644
index 0000000..bccf99c
--- /dev/null
+++ b/src/api/torrent.js
@@ -0,0 +1,74 @@
+import request from './request'

+

+/**

+ * 上传种子

+ * @param {FormData} formData - 包含种子文件和相关信息的表单数据

+ * @returns {Promise}

+ */

+export function uploadTorrent(formData) {

+  return request({

+    url: '/torrent/upload',

+    method: 'post',

+    data: formData,

+    headers: {

+      'Content-Type': 'multipart/form-data'

+    }

+  })

+}

+

+/**

+ * 获取分类列表

+ * @returns {Promise}

+ */

+export function getCategories() {

+  return request({

+    url: '/category/list', // 注意这里不需要加 /api,已经在代理中配置了

+    method: 'get'

+  })

+}

+

+/**

+ * 获取标签列表

+ * @returns {Promise}

+ * 暂时还没有获取标签的列表

+ */

+export function getTags() {

+    console.log('调用获取标签列表API...')

+    // 由于后端没有标签的Controller,返回模拟数据

+    return new Promise((resolve) => {

+      setTimeout(() => {

+        resolve({

+          data: [

+            { id: 1, name: 'linux' },

+            { id: 2, name: 'ios' },

+            { id: 3, name: 'ubuntu' }

+          ]

+        })

+      }, 100)

+    })

+  }

+

+/**

+ * 获取种子详情

+ * @param {string} infoHash - 种子的info hash

+ * @returns {Promise}

+ */

+export function getTorrentDetail(infoHash) {

+  return request({

+    url: `/torrent/${infoHash}`,

+    method: 'get'

+  })

+}

+

+/**

+ * 获取种子列表

+ * @param {Object} params - 查询参数

+ * @returns {Promise}

+ */

+export function getTorrents(params) {

+  return request({

+    url: '/torrents',

+    method: 'get',

+    params

+  })

+}
\ No newline at end of file
diff --git a/src/views/torrent/UploadView.vue b/src/views/torrent/UploadView.vue
index 80c3660..9807cd1 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,213 @@
 </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)

+        

+        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 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 +317,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
diff --git a/vue.config.js b/vue.config.js
index a0ee328..ae9a27f 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,15 +1,49 @@
+// vue.config.js

 const { defineConfig } = require('@vue/cli-service')

 

 module.exports = defineConfig({

   transpileDependencies: true,

+

+  /*  dev 服务只跑在 8080;真正的后端地址改用

+      环境变量 VUE_APP_BACKEND,默认仍指向 8081  */

   devServer: {

     port: 8080,

     proxy: {

+      // ❶ 统一以 /api 开头的请求全部反向代理

       '/api': {

-        target: 'http://localhost:8081',

+        target: process.env.VUE_APP_BACKEND || 'http://localhost:8081',

         changeOrigin: true,

         ws: true,

-        logLevel: 'debug'

+        secure: false,

+        logLevel: 'debug',

+        onProxyReq: (proxyReq, req, res) => {

+          console.log('🔄 代理请求:', req.method, req.url, '→', 'http://localhost:8081' + req.url)

+        },

+        onProxyRes: (proxyRes, req, res) => {

+          console.log('📨 代理响应:', proxyRes.statusCode, req.url)

+        },

+        onError: (err, req, res) => {

+          console.error('❌ 代理错误:', err.message)

+        }

+      },// 这里需要闭合 /api 的配置

+

+      /* ❷ 可选:如果你项目里还有没改完的

+            /category、/torrent、/auth 旧写法,保留兼容性。

+            改完之后,把这一段删掉也行。 */

+      '/category': {

+        target: process.env.VUE_APP_BACKEND || 'http://localhost:8081',

+        changeOrigin: true,

+        pathRewrite: { '^/category': '/api/category' }

+      },

+      '/torrent': {

+        target: process.env.VUE_APP_BACKEND || 'http://localhost:8081',

+        changeOrigin: true,

+        pathRewrite: { '^/torrent': '/api/torrent' }

+      },

+      '/auth': {

+        target: process.env.VUE_APP_BACKEND || 'http://localhost:8081',

+        changeOrigin: true,

+        pathRewrite: { '^/auth': '/api/auth' }

       }

     }

   }