You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Vanilla JS实现聊天机器人的文件附件输入框UI?

聊天机器人附件功能:UI设计方案与Vanilla JS实现

一、UI设计思路

核心交互逻辑

  • 极简布局:在聊天输入框旁放置「回形针图标按钮」,点击触发文件选择;输入框下方预留区域展示已选文件,不占用输入空间。
  • 格式限制提示:按钮hover时显示支持的格式(PDF/CSV等),文件选择时直接过滤不符合的格式,避免无效操作。
  • 已选文件管理:每个已选文件显示为小型卡片,包含文件名、格式化后的文件大小、删除按钮;支持多选,超出数量可横向滚动或折叠。
  • 状态反馈:文件选中后立即显示预览;若格式错误,弹出内联提示(不使用弹窗);发送时若文件未处理完成,禁用发送按钮并显示加载状态。

响应式适配

  • 桌面端:附件按钮放在输入框左侧,发送按钮在右侧,文件预览区横向排列。
  • 移动端:附件按钮和发送按钮合并到输入框右侧的下拉菜单,文件预览区垂直堆叠,节省横向空间。

二、Vanilla JS 完整实现代码

HTML 结构

<div class="chat-input-container">
  <!-- 隐藏原生文件选择框,用自定义按钮触发 -->
  <input type="file" id="fileInput" multiple accept=".pdf,.csv" style="display: none;">
  
  <button id="attachBtn" class="attach-btn" title="添加PDF/CSV文件">📎</button>
  
  <textarea id="messageInput" class="message-input" placeholder="输入消息..."></textarea>
  
  <button id="sendBtn" class="send-btn">发送</button>
</div>

<!-- 已选文件预览区 -->
<div id="filePreviewContainer" class="file-preview-container"></div>

CSS 样式

.chat-input-container {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border: 1px solid #e0e0e0;
  border-radius: 20px;
  max-width: 600px;
  margin: 20px auto;
}

.attach-btn, .send-btn {
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background-color: #f0f0f0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
}

.attach-btn:hover, .send-btn:hover {
  background-color: #e0e0e0;
}

.message-input {
  flex: 1;
  border: none;
  resize: none;
  padding: 8px 12px;
  font-size: 14px;
  min-height: 20px;
}

.message-input:focus {
  outline: none;
}

.file-preview-container {
  max-width: 600px;
  margin: 0 auto 10px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.file-preview-card {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background-color: #f5f5f5;
  border-radius: 12px;
  font-size: 14px;
}

.delete-file-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: #666;
  font-size: 16px;
}

.delete-file-btn:hover {
  color: #ff4444;
}

.send-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

JavaScript 逻辑

// 获取DOM元素
const attachBtn = document.getElementById('attachBtn');
const fileInput = document.getElementById('fileInput');
const filePreviewContainer = document.getElementById('filePreviewContainer');
const sendBtn = document.getElementById('sendBtn');
const messageInput = document.getElementById('messageInput');

// 存储已选文件
let selectedFiles = [];

// 点击附件按钮触发文件选择
attachBtn.addEventListener('click', () => {
  fileInput.click();
});

// 处理文件选择事件
fileInput.addEventListener('change', (e) => {
  const files = Array.from(e.target.files);
  
  // 过滤已存在的文件(避免重复选择)
  const newFiles = files.filter(file => !selectedFiles.some(f => f.name === file.name && f.size === file.size));
  
  if (newFiles.length === 0) return;
  
  // 添加到已选文件列表
  selectedFiles = [...selectedFiles, ...newFiles];
  
  // 渲染预览卡片
  renderFilePreviews();
});

// 渲染已选文件预览
function renderFilePreviews() {
  filePreviewContainer.innerHTML = '';
  
  selectedFiles.forEach((file, index) => {
    const card = document.createElement('div');
    card.className = 'file-preview-card';
    
    // 文件信息:名称 + 格式化大小
    const fileInfo = document.createElement('span');
    const fileSize = formatFileSize(file.size);
    fileInfo.textContent = `${file.name} (${fileSize})`;
    
    // 删除按钮
    const deleteBtn = document.createElement('button');
    deleteBtn.className = 'delete-file-btn';
    deleteBtn.textContent = '×';
    deleteBtn.addEventListener('click', () => {
      selectedFiles.splice(index, 1);
      renderFilePreviews();
    });
    
    card.appendChild(fileInfo);
    card.appendChild(deleteBtn);
    filePreviewContainer.appendChild(card);
  });
  
  // 有文件或文本时启用发送按钮
  sendBtn.disabled = selectedFiles.length === 0 && messageInput.value.trim() === '';
}

// 格式化文件大小(KB/MB)
function formatFileSize(bytes) {
  if (bytes < 1024) return bytes + ' B';
  else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
  else return (bytes / 1048576).toFixed(1) + ' MB';
}

// 发送按钮点击事件
sendBtn.addEventListener('click', () => {
  const message = messageInput.value.trim();
  
  // 处理文件上传逻辑
  if (selectedFiles.length > 0) {
    const formData = new FormData();
    selectedFiles.forEach(file => formData.append('attachments', file));
    if (message) formData.append('message', message);
    
    // 模拟上传状态
    sendBtn.disabled = true;
    sendBtn.textContent = '发送中...';
    
    setTimeout(() => {
      // 上传完成后清空状态
      messageInput.value = '';
      selectedFiles = [];
      renderFilePreviews();
      sendBtn.disabled = false;
      sendBtn.textContent = '发送';
      alert('消息和文件已发送'); // 实际项目替换为聊天消息展示逻辑
    }, 1500);
  } else if (message) {
    // 仅发送文本消息
    alert('文本消息已发送');
    messageInput.value = '';
    sendBtn.disabled = true;
  }
});

// 监听输入框内容变化,控制发送按钮状态
messageInput.addEventListener('input', () => {
  sendBtn.disabled = selectedFiles.length === 0 && messageInput.value.trim() === '';
});

内容的提问来源于stack exchange,提问作者riri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:03:27