拖拽和点击上传图片功能

<template>
  <div class="container">
    <button @click="addUploadArea" class="add-button">+ 添加上传区域</button>

    <div v-for="(area, index) in areas" :key="area.id" class="upload-area">
      <div class="header">
        <h3>上传区域 {{ index + 1 }}</h3>
        <button @click="removeUploadArea(index)" class="remove-button">×</button>
      </div>

      <!-- 点击上传区域 -->
      <div class="upload-wrapper">
        <!-- 隐藏的 input 元素 -->
        <input
          type="file"
          ref="fileInputs"
          :id="'file-input-' + index"
          class="hidden-input"
          accept="image/*"
          @change="(e) => handleFileSelect(e, index)"
        />

        <!-- 可视化的上传区域 -->
        <label 
          :for="'file-input-' + index" 
          class="upload-box"
          @dragover.prevent
          @drop.prevent="(e) => handleDrop(e, index)"
        >
          <div class="upload-content">
            <div class="upload-icon">📁</div>
            <div class="upload-text">
              <p>点击选择文件 或 拖拽文件到这里</p>
              <p>支持格式: JPEG, PNG (最大5MB)</p>
            </div>
          </div>
        </label>
      </div>

      <!-- 上传状态和预览 -->
      <div v-if="area.uploading" class="status">上传中...</div>
      <div v-if="area.error" class="error">{{ area.error }}</div>
      <img v-if="area.previewUrl" :src="area.previewUrl" class="preview-image" />
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import axios from 'axios'

// 生成唯一ID
let idCounter = 0
const generateId = () => idCounter++

// 上传区域数据
const areas = ref([])
const fileInputs = ref([]) // 用于操作隐藏的 input 元素

// 默认区域配置
const defaultArea = () => ({
  id: generateId(),
  uploading: false,
  previewUrl: '',
  error: ''
})

// 初始化一个默认区域
areas.value.push(defaultArea())

// 添加新区域
const addUploadArea = () => {
  areas.value.push(defaultArea())
}

// 删除区域
const removeUploadArea = (index) => {
  areas.value.splice(index, 1)
}

// 处理文件选择(点击上传)
const handleFileSelect = (event, areaIndex) => {
  const files = event.target.files
  if (files.length > 0) {
    processFiles(files, areaIndex)
    event.target.value = '' // 清空 input 以便重复上传
  }
}

// 处理拖放事件
const handleDrop = (event, areaIndex) => {
  const dt = event.dataTransfer
  processFiles(dt.files, areaIndex)
}

// 统一处理文件(兼容点击、拖拽、粘贴)
const processFiles = async (files, areaIndex) => {
  const area = areas.value[areaIndex]
  
  // 重置状态
  area.error = ''
  area.previewUrl = ''

  // 验证文件
  const file = files[0]
  if (!file) {
    area.error = '未选择文件'
    return
  }

  if (!file.type.startsWith('image/')) {
    area.error = '仅支持图片文件 (JPEG, PNG)'
    return
  }

  if (file.size > 5 * 1024 * 1024) {
    area.error = '文件大小不能超过5MB'
    return
  }

  // 显示预览
  area.previewUrl = URL.createObjectURL(file)
  
  // 开始上传
  try {
    area.uploading = true
    const formData = new FormData()
    formData.append('file', file)

    const response = await axios.post('https://your-api.com/upload', formData, {
      headers: { 'Content-Type': 'multipart/form-data' }
    })

    console.log('上传成功:', response.data)
    // 这里可以处理服务器返回的URL
  } catch (error) {
    area.error = '上传失败: ' + (error.response?.data?.message || error.message)
  } finally {
    area.uploading = false
  }
}
</script>

<style scoped>
.container {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
}

.add-button {
  background: #4CAF50;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  margin-bottom: 20px;
}

.upload-area {
  border: 2px dashed #ccc;
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 20px;
  position: relative;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}

.remove-button {
  background: #ff4444;
  color: white;
  border: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  cursor: pointer;
}

/* 点击上传区域样式 */
.upload-wrapper {
  position: relative;
}

.hidden-input {
  display: none;
}

.upload-box {
  display: block;
  min-height: 150px;
  padding: 20px;
  background: #f8f8f8;
  border-radius: 4px;
  border: 2px dashed #ccc;
  cursor: pointer;
  transition: all 0.3s;
}

.upload-box:hover {
  border-color: #4CAF50;
  background: #f0fff4;
}

.upload-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #666;
}

.upload-icon {
  font-size: 40px;
  margin-bottom: 10px;
}

.upload-text p {
  margin: 5px 0;
  text-align: center;
}

.preview-image {
  max-width: 100%;
  margin-top: 15px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.status {
  color: #2196F3;
  margin-top: 10px;
}

.error {
  color: #ff4444;
  margin-top: 10px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值