Skip to content

Upload 文件上传

文件上传组件,支持单文件、多文件上传,文件类型限制等功能。

基础用法

单文件上传。

html
<div id="demo-upload"></div>
<div id="output-basic" style="margin-top: 12px; font-size: 13px; color: #6b7280;">未选择文件</div>
<div style="margin-top: 12px; display: flex; gap: 8px;">
  <button class="sa-button" id="btn-open">打开文件选择</button>
  <button class="sa-button" id="btn-reset">重置</button>
</div>

<script>
const upload = new SaUpload('#demo-upload', {
  onSelect: (files) => {
    const output = document.getElementById('output-basic');
    if (files && files.length > 0) {
      output.textContent = `已选择 ${files.length} 个文件: ${Array.from(files).map(f => f.name).join(', ')}`;
    } else {
      output.textContent = '未选择文件';
    }
    console.log('选择的文件:', files);
  }
});

document.getElementById('btn-open').addEventListener('click', () => {
  upload.open();
});

document.getElementById('btn-reset').addEventListener('click', () => {
  upload.reset();
  document.getElementById('output-basic').textContent = '未选择文件';
});

SA.init('body');
</script>
加载 SanoUI 组件中...
            
          

多文件上传

通过 multiple: true 启用多文件选择功能。

html
<div id="demo-upload-multiple"></div>
<div id="output-multiple" style="margin-top: 12px; font-size: 13px; color: #6b7280;">未选择文件</div>

<script>
const upload = new SaUpload('#demo-upload-multiple', {
  multiple: true,
  onSelect: (files) => {
    const output = document.getElementById('output-multiple');
    if (files && files.length > 0) {
      output.textContent = `已选择 ${files.length} 个文件: ${Array.from(files).map(f => f.name).join(', ')}`;
    } else {
      output.textContent = '未选择文件';
    }
    console.log('选择的文件:', files);
  }
});

SA.init('body');
</script>
加载 SanoUI 组件中...
            
          

文件类型限制

通过 accept 参数限制可选择的文件类型,通过 hint 显示提示信息。

html
<div id="demo-upload-accept"></div>
<div id="output-accept" style="margin-top: 12px; font-size: 13px; color: #6b7280;">未选择文件</div>

<script>
const upload = new SaUpload('#demo-upload-accept', {
  accept: '.jpg,.jpeg,.png,.gif',
  hint: '只能上传 JPG、PNG、GIF 格式的图片',
  onSelect: (files) => {
    const output = document.getElementById('output-accept');
    if (files && files.length > 0) {
      output.textContent = `已选择 ${files.length} 个文件: ${Array.from(files).map(f => f.name).join(', ')}`;
    } else {
      output.textContent = '未选择文件';
    }
    console.log('选择的文件:', files);
  }
});

SA.init('body');
</script>
加载 SanoUI 组件中...
            
          

自动初始化

使用 SA.init() 可以自动初始化所有带有 data-upload 属性的文件上传组件。

html
<div id="demo-auto-init" 
     data-upload="true"
     data-text="自动初始化上传"
     data-hint="支持所有文件类型"
     data-multiple="false"></div>
<div style="margin-top: 12px; font-size: 14px; color: #606266;">
  提示:文件上传组件已通过 SA.init() 自动初始化。
</div>

<script>
// 使用 SA.init() 自动初始化所有组件
const page = SA.init('body');

// 文件上传组件已自动初始化,所有带有 data-upload 属性的元素都会被处理

// 文件上传组件通常不需要手动初始化,直接使用 HTML 属性即可
</script>
加载 SanoUI 组件中...
            
          

API

构造函数

javascript
new SaUpload(container, config)

参数

参数说明类型默认值
container容器选择器或元素string | HTMLElement-
config配置选项SaUploadOptions{}

配置选项

参数说明类型默认值
multiple是否支持多文件选择booleanfalse
text按钮显示文本string'上传文件'
hint提示信息string''
accept接受的文件类型(MIME 类型或扩展名)string''
onSelect文件选择回调function(files, helpers): voidnull

Data 属性

属性说明类型默认值
data-upload启用自动初始化boolean-
data-multiple是否支持多文件booleanfalse
data-text按钮显示文本string'上传文件'
data-hint提示信息string''
data-accept接受的文件类型string''

方法

方法名说明参数返回值
open()打开文件选择对话框-void
reset()重置文件选择(清空已选择的文件)-void
destroy()销毁实例-void

事件回调

事件名说明回调参数
onSelect文件选择时触发files: File[] - 选择的文件数组
helpers: { reset: Function } - 辅助方法对象

静态方法

方法名说明参数返回值
initAll(container?)初始化容器内所有带 data-upload 的元素container?: Element | string - 容器元素或选择器Array<SaUpload>
init(selector)初始化指定容器内的所有标记元素selector: Element | string - 容器选择器或元素Array<SaUpload>

实际使用场景

场景 1:图片上传预览

上传图片后显示预览图。

html
<div id="image-upload"></div>
<div id="image-preview" style="margin-top: 1rem; display: none;">
  <img id="preview-img" style="max-width: 300px; max-height: 300px; border: 1px solid #ddd; border-radius: 4px;">
</div>

<script>
  SA.init('body');
  
  const upload = new SaUpload('#image-upload', {
    accept: '.jpg,.jpeg,.png,.gif',
    hint: '支持 JPG、PNG、GIF 格式,最大 5MB',
    text: '选择图片',
    onSelect: (files) => {
      if (files.length > 0) {
        const file = files[0];
        
        // 检查文件大小(5MB)
        if (file.size > 5 * 1024 * 1024) {
          alert('文件大小不能超过 5MB');
          return;
        }
        
        // 创建预览
        const reader = new FileReader();
        reader.onload = (e) => {
          const previewImg = document.getElementById('preview-img');
          const previewDiv = document.getElementById('image-preview');
          previewImg.src = e.target.result;
          previewDiv.style.display = 'block';
        };
        reader.readAsDataURL(file);
      }
    }
  });
</script>
加载 SanoUI 组件中...
            
          

场景 2:多文件上传列表

显示已选择文件的列表,支持删除。

html
<div id="multi-upload"></div>
<div id="file-list" style="margin-top: 1rem;"></div>

<script>
  SA.init('body');
  
  let selectedFiles = [];
  
  const upload = new SaUpload('#multi-upload', {
    multiple: true,
    text: '选择文件',
    hint: '支持多文件选择',
    onSelect: (files) => {
      selectedFiles = Array.from(files);
      renderFileList();
    }
  });
  
  function renderFileList() {
    const fileList = document.getElementById('file-list');
    
    if (selectedFiles.length === 0) {
      fileList.innerHTML = '<p style="color: #999;">未选择文件</p>';
      return;
    }
    
    fileList.innerHTML = selectedFiles.map((file, index) => {
      const size = (file.size / 1024).toFixed(2) + ' KB';
      return `
        <div style="display: flex; align-items: center; justify-content: space-between; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 0.5rem;">
          <div>
            <strong>${file.name}</strong>
            <span style="color: #999; margin-left: 0.5rem;">${size}</span>
          </div>
          <button class="sa-button sa-button--text sa-button--danger" onclick="removeFile(${index})">删除</button>
        </div>
      `;
    }).join('');
  }
  
  function removeFile(index) {
    selectedFiles.splice(index, 1);
    renderFileList();
  }
  
  // 初始渲染
  renderFileList();
</script>
加载 SanoUI 组件中...
            
          

场景 3:文件上传到服务器

选择文件后上传到服务器。

html
<div id="server-upload"></div>
<div id="upload-status" style="margin-top: 1rem; padding: 1rem; background: #f5f5f5; border-radius: 4px; display: none;">
  <div id="upload-progress" style="margin-bottom: 0.5rem;">上传进度: 0%</div>
  <div id="upload-result"></div>
</div>

<script>
  SA.init('body');
  
  const upload = new SaUpload('#server-upload', {
    text: '选择文件上传',
    hint: '支持所有文件类型',
    onSelect: async (files) => {
      if (files.length === 0) return;
      
      const statusDiv = document.getElementById('upload-status');
      const progressDiv = document.getElementById('upload-progress');
      const resultDiv = document.getElementById('upload-result');
      
      statusDiv.style.display = 'block';
      progressDiv.textContent = '上传中...';
      resultDiv.textContent = '';
      
      const file = files[0];
      const formData = new FormData();
      formData.append('file', file);
      
      try {
        // 模拟上传(实际使用时替换为真实 API)
        const xhr = new XMLHttpRequest();
        
        xhr.upload.onprogress = (e) => {
          if (e.lengthComputable) {
            const percent = Math.round((e.loaded / e.total) * 100);
            progressDiv.textContent = `上传进度: ${percent}%`;
          }
        };
        
        xhr.onload = () => {
          if (xhr.status === 200) {
            progressDiv.textContent = '上传完成';
            resultDiv.innerHTML = `<span style="color: #67c23a;">✓ 文件上传成功: ${file.name}</span>`;
          } else {
            progressDiv.textContent = '上传失败';
            resultDiv.innerHTML = `<span style="color: #f56c6c;">✗ 上传失败,请重试</span>`;
          }
        };
        
        xhr.onerror = () => {
          progressDiv.textContent = '上传失败';
          resultDiv.innerHTML = `<span style="color: #f56c6c;">✗ 网络错误,请检查连接</span>`;
        };
        
        // 模拟上传(实际使用时取消注释)
        // xhr.open('POST', '/api/upload');
        // xhr.send(formData);
        
        // 模拟上传成功
        setTimeout(() => {
          progressDiv.textContent = '上传完成';
          resultDiv.innerHTML = `<span style="color: #67c23a;">✓ 文件上传成功: ${file.name}</span>`;
        }, 2000);
        
      } catch (error) {
        progressDiv.textContent = '上传失败';
        resultDiv.innerHTML = `<span style="color: #f56c6c;">✗ ${error.message}</span>`;
      }
    }
  });
</script>
加载 SanoUI 组件中...
            
          

场景 4:文件类型和大小验证

在上传前验证文件类型和大小。

html
<div id="validated-upload"></div>
<div id="validation-result" style="margin-top: 1rem; padding: 1rem; background: #f5f5f5; border-radius: 4px; display: none;"></div>

<script>
  SA.init('body');
  
  const upload = new SaUpload('#validated-upload', {
    accept: '.pdf,.doc,.docx',
    hint: '仅支持 PDF、Word 文档,最大 10MB',
    text: '选择文档',
    onSelect: (files) => {
      const resultDiv = document.getElementById('validation-result');
      
      if (files.length === 0) {
        resultDiv.style.display = 'none';
        return;
      }
      
      const file = files[0];
      const errors = [];
      
      // 验证文件类型
      const allowedTypes = ['.pdf', '.doc', '.docx'];
      const fileExt = '.' + file.name.split('.').pop().toLowerCase();
      if (!allowedTypes.includes(fileExt)) {
        errors.push(`不支持的文件类型: ${fileExt}`);
      }
      
      // 验证文件大小(10MB)
      const maxSize = 10 * 1024 * 1024;
      if (file.size > maxSize) {
        errors.push(`文件大小超过限制: ${(file.size / 1024 / 1024).toFixed(2)}MB > 10MB`);
      }
      
      if (errors.length > 0) {
        resultDiv.style.display = 'block';
        resultDiv.innerHTML = `
          <div style="color: #f56c6c;">
            <strong>验证失败:</strong>
            <ul style="margin: 0.5rem 0; padding-left: 1.5rem;">
              ${errors.map(e => `<li>${e}</li>`).join('')}
            </ul>
          </div>
        `;
        upload.reset();
      } else {
        resultDiv.style.display = 'block';
        resultDiv.innerHTML = `
          <div style="color: #67c23a;">
            <strong>✓ 验证通过</strong><br>
            文件名: ${file.name}<br>
            大小: ${(file.size / 1024).toFixed(2)} KB
          </div>
        `;
      }
    }
  });
</script>
加载 SanoUI 组件中...
            
          

场景 5:拖拽上传

支持拖拽文件到指定区域上传。

html
<div id="drag-upload-area" style="border: 2px dashed #ddd; border-radius: 4px; padding: 2rem; text-align: center; cursor: pointer; background: #fafafa;">
  <div style="margin-bottom: 0.5rem;">📁 拖拽文件到此处或点击选择</div>
  <div style="font-size: 0.875rem; color: #999;">支持多文件,最大 50MB</div>
</div>
<div id="drag-upload" style="display: none;"></div>
<div id="drag-file-list" style="margin-top: 1rem;"></div>

<script>
  SA.init('body');
  
  const upload = new SaUpload('#drag-upload', {
    multiple: true,
    onSelect: (files) => {
      renderDragFileList(Array.from(files));
    }
  });
  
  const dragArea = document.getElementById('drag-upload-area');
  const fileList = document.getElementById('drag-file-list');
  
  // 点击区域打开文件选择
  dragArea.addEventListener('click', () => {
    upload.open();
  });
  
  // 拖拽事件
  dragArea.addEventListener('dragover', (e) => {
    e.preventDefault();
    dragArea.style.borderColor = '#409eff';
    dragArea.style.background = '#ecf5ff';
  });
  
  dragArea.addEventListener('dragleave', () => {
    dragArea.style.borderColor = '#ddd';
    dragArea.style.background = '#fafafa';
  });
  
  dragArea.addEventListener('drop', (e) => {
    e.preventDefault();
    dragArea.style.borderColor = '#ddd';
    dragArea.style.background = '#fafafa';
    
    const files = Array.from(e.dataTransfer.files);
    if (files.length > 0) {
      renderDragFileList(files);
    }
  });
  
  function renderDragFileList(files) {
    if (files.length === 0) {
      fileList.innerHTML = '';
      return;
    }
    
    fileList.innerHTML = files.map((file, index) => {
      const size = (file.size / 1024 / 1024).toFixed(2) + ' MB';
      return `
        <div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 0.5rem; background: white;">
          <div>
            <strong>${file.name}</strong>
            <span style="color: #999; margin-left: 0.5rem;">${size}</span>
          </div>
          <span style="color: #67c23a;">✓ 已选择</span>
        </div>
      `;
    }).join('');
  }
</script>
加载 SanoUI 组件中...
            
          

注意事项

  1. 文件类型限制accept 属性支持 MIME 类型(如 image/*)或文件扩展名(如 .jpg,.png
  2. 文件大小:浏览器不提供文件大小限制,需要在 onSelect 回调中手动验证
  3. 多文件选择:设置 multiple: true 后,用户可以选择多个文件
  4. 自动初始化:使用 SA.init() 会自动初始化所有带 data-upload 属性的元素
  5. 文件对象onSelect 回调接收的是 File 对象数组,可以使用 FileReader API 读取文件内容

常见问题

Q: 如何获取选择的文件?

A: 在 onSelect 回调中获取:

javascript
const upload = new SaUpload('#upload', {
  onSelect: (files) => {
    console.log('选择的文件:', files);
    files.forEach(file => {
      console.log('文件名:', file.name);
      console.log('文件大小:', file.size);
      console.log('文件类型:', file.type);
    });
  }
});

Q: 如何限制文件大小?

A: 在 onSelect 回调中验证:

javascript
onSelect: (files) => {
  const maxSize = 5 * 1024 * 1024; // 5MB
  const invalidFiles = files.filter(file => file.size > maxSize);
  if (invalidFiles.length > 0) {
    alert('文件大小不能超过 5MB');
    upload.reset();
    return;
  }
}

Q: 如何实现图片预览?

A: 使用 FileReader API:

javascript
onSelect: (files) => {
  if (files.length > 0) {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = document.createElement('img');
      img.src = e.target.result;
      document.body.appendChild(img);
    };
    reader.readAsDataURL(files[0]);
  }
}

Q: 如何上传文件到服务器?

A: 使用 FormDataXMLHttpRequestfetch

javascript
onSelect: async (files) => {
  const formData = new FormData();
  formData.append('file', files[0]);
  
  const response = await fetch('/api/upload', {
    method: 'POST',
    body: formData
  });
  
  const result = await response.json();
  console.log('上传结果:', result);
}

Q: 如何清空已选择的文件?

A: 使用 reset() 方法:

javascript
upload.reset();

Q: 如何支持拖拽上传?

A: 监听容器的拖拽事件:

javascript
const container = document.getElementById('upload-container');
container.addEventListener('drop', (e) => {
  e.preventDefault();
  const files = Array.from(e.dataTransfer.files);
  // 处理文件
});

Released under the MIT License.