TinyRobot DragOverlay轻松实现AI对话中的拖拽上传

TinyRobot DragOverlay:轻松实现 AI 对话中的拖拽上传

在 AI 对话应用中,文件上传是与大模型交互的重要方式之一。用户经常需要上传文档、图片、代码文件等作为上下文输入,而传统的点击选择文件方式操作路径长、交互不够直观。拖拽上传作为一种更现代化的交互模式,让用户可以直接将文件拖入对话区域,操作自然流畅,大幅提升用户体验。

TinyRobot 的 v-dropzone 指令与 tr-drag-overlay 组件正是为此场景设计,二者协同工作:v-dropzone 负责监听拖拽事件、文件过滤与校验,tr-drag-overlay 负责展示拖拽过程中的视觉反馈。下面我们来详细了解它们的使用方式。

基础用法:v-dropzone + tr-drag-overlay 协作

v-dropzone 是一个自定义指令,绑定到目标容器元素上,使其成为拖拽区域。tr-drag-overlay 则是覆盖在拖拽区域上方的视觉反馈层。二者通过 is-dragging 状态变量联动:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传文件"
      :overlay-description="['将文件拖到此处上传', '支持图片、文档等常见格式']"
    />
    <!-- 对话内容 -->
    <div class="messages">
      <p>对话消息区域...</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)

const dropzoneConfig = {
  onDrop(files) {
    console.log('上传的文件:', files)
    // 处理文件上传逻辑
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

<style scoped>
.chat-area {
  position: relative;
  min-height: 500px;
}
</style>

当用户将文件拖入 chat-area 区域时,onDraggingChange 回调触发,将 isDragging 设为 true,tr-drag-overlay 随即显示覆盖层;文件放下后,onDrop 回调接收文件列表,覆盖层自动消失。

文件类型过滤

通过 accept 属性可以限定可接受的文件类型,格式与 HTML <input type="file"> 的 accept 属性一致:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传图片"
      :overlay-description="['仅支持 JPG、PNG、GIF 格式']"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)

const dropzoneConfig = {
  accept: '.jpg,.jpeg,.png,.gif,image/jpeg,image/png,image/gif',
  onDrop(files) {
    console.log('图片文件:', files)
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

不符合类型要求的文件会在 onError 回调中被捕获,code 为 FILE_TYPE_NOT_ALLOWED。

文件大小与数量限制

在 AI 对话场景中,通常需要对上传文件的大小和数量加以限制,maxSize 和 maxFiles 属性分别控制这两个维度:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传文件"
      :overlay-description="['单个文件不超过 5MB', '每次最多上传 5 个文件']"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)

const dropzoneConfig = {
  maxSize: 5 * 1024 * 1024, // 5MB
  maxFiles: 5,
  onDrop(files) {
    console.log('有效文件:', files)
  },
  onError(rejection) {
    console.warn('文件被拒绝:', rejection.code, rejection.message)
    // rejection.code 可能是 'FILE_SIZE_EXCEEDED' 或 'MAX_FILES_EXCEEDED'
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

当文件超出大小限制时,onError 回调接收 FileRejection 对象,code 为 FILE_SIZE_EXCEEDED;超过数量限制时,code 为 MAX_FILES_EXCEEDED。

自定义覆盖层内容

默认的覆盖层提供了标题和描述文字,如果需要更丰富的视觉效果或自定义提示,可以使用 overlay 插槽:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay :is-dragging="isDragging" :drag-target="dragTarget">
      <template #overlay>
        <div class="custom-overlay">
          <div class="overlay-icon">📎</div>
          <p class="overlay-title">松开即可上传</p>
          <p class="overlay-hint">支持 PDF、Word、Excel 等文档</p>
        </div>
      </template>
    </tr-drag-overlay>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)

const dropzoneConfig = {
  onDrop(files) {
    console.log('上传文件:', files)
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

<style scoped>
.custom-overlay {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.overlay-icon {
  font-size: 48px;
}
.overlay-title {
  font-size: 18px;
  font-weight: 600;
  color: #409eff;
}
.overlay-hint {
  font-size: 14px;
  color: #909399;
}
</style>

禁用状态

在某些场景下(如正在上传、对话已结束等),需要临时禁用拖拽上传功能。通过 disabled 属性即可控制:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传文件"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)
const uploading = ref(false)

const dropzoneConfig = {
  disabled: uploading,
  onDrop(files) {
    uploading.value = true
    // 模拟上传过程
    setTimeout(() => {
      uploading.value = false
      console.log('上传完成')
    }, 2000)
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

当 disabled 为 true 时,拖拽区域不响应拖拽事件,onDraggingChange 不会被触发。

错误处理

onError 回调接收 FileRejection 类型的参数,包含错误码、错误信息和被拒绝的文件列表,便于在 UI 中给出精确提示:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-area">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传文件"
    />
    <div v-if="errorMessage" class="error-toast">{{ errorMessage }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)
const errorMessage = ref('')

const ERROR_MESSAGES = {
  FILE_TYPE_NOT_ALLOWED: '不支持的文件类型',
  FILE_SIZE_EXCEEDED: '文件大小超出限制',
  MAX_FILES_EXCEEDED: '文件数量超出限制'
}

const dropzoneConfig = {
  accept: '.pdf,.doc,.docx',
  maxSize: 10 * 1024 * 1024,
  maxFiles: 3,
  onDrop(files) {
    errorMessage.value = ''
    console.log('有效文件:', files)
  },
  onError(rejection) {
    errorMessage.value = ERROR_MESSAGES[rejection.code] || rejection.message
    console.warn('被拒绝的文件:', rejection.files)
    // 3 秒后自动清除提示
    setTimeout(() => {
      errorMessage.value = ''
    }, 3000)
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

<style scoped>
.error-toast {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  padding: 10px 20px;
  background: #fef0f0;
  color: #f56c6c;
  border-radius: 4px;
  font-size: 14px;
}
</style>

全屏模式

当拖拽区域需要覆盖整个页面时(如全局文件拖入检测),可以启用 fullscreen 模式:

vue 复制代码
<template>
  <div v-dropzone="dropzoneConfig" class="chat-page">
    <tr-drag-overlay
      :is-dragging="isDragging"
      :drag-target="dragTarget"
      overlay-title="上传到当前对话"
      :overlay-description="['文件将添加到当前 AI 对话中']"
      fullscreen
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { TrDragOverlay, vDropzone } from '@opentiny/tiny-robot'

const isDragging = ref(false)
const dragTarget = ref(null)

const dropzoneConfig = {
  onDrop(files) {
    console.log('全局拖入文件:', files)
  },
  onDraggingChange(dragging, target) {
    isDragging.value = dragging
    dragTarget.value = target
  }
}
</script>

fullscreen 为 true 时,覆盖层会以 position: fixed 定位到整个视口,无论用户拖拽到页面哪个位置都能触达。

API 参考

v-dropzone 指令属性

属性 类型 默认值 说明
accept string '' 可接受的文件类型,格式同 HTML accept 属性
multiple boolean true 是否允许多文件上传
maxSize number 10485760 单个文件最大字节数,默认 10MB
maxFiles number 3 最大文件数量
disabled boolean - 是否禁用拖拽功能
onDrop (files: File[]) => void - 文件放下时的回调
onError (rejection: FileRejection) => void - 文件校验不通过时的回调
onDraggingChange `(dragging: boolean, target: Element null) => void` -

tr-drag-overlay Props

属性 类型 默认值 说明
is-dragging boolean - 当前是否处于拖拽状态,与 v-dropzone 的 onDraggingChange 联动
drag-target `Element null` -
overlay-title string - 覆盖层标题文字
overlay-description string[] - 覆盖层描述文字数组
fullscreen boolean false 是否全屏显示覆盖层

tr-drag-overlay Slots

插槽名 说明
overlay 自定义覆盖层内容,替换默认的标题和描述

FileRejection 类型

typescript 复制代码
interface FileRejection {
  code: string       // 错误码:FILE_TYPE_NOT_ALLOWED | FILE_SIZE_EXCEEDED | MAX_FILES_EXCEEDED
  message: string    // 错误描述信息
  files: File[]      // 被拒绝的文件列表
}

OpenTiny NEXT 是一套企业智能前端开发解决方案,以生成式 UI 和 WebMCP 两大核心技术为基础,对现有传统的 TinyVue 组件库、TinyEngine 低代码引擎等产品进行智能化升级,构建出面向 Agent 应用的前端 NEXT-SDKs、AI Extension、TinyRobot智能助手、GenUI等新产品,加速企业应用的智能化改造,实现AI理解用户意图自主完成任务。

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网:opentiny.design/ TinyRobot 代码仓库:github.com/opentiny/ti... (欢迎star ⭐) TinyRobot skill源码:github.com/opentiny/ag... (欢迎 Star ⭐)

相关推荐
brien95 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance5 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M5 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写5 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李5 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易5 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马6 小时前
前沿视界
前端
kyriewen6 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易6 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
IT_陈寒6 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端