小程序后台管理系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

6.6 KiB

🔄 SelectFile组件拖拽调整顺序功能演示

🎯 功能概述

我已经成功为SelectFile组件添加了拖拽调整顺序的功能,让用户可以通过拖拽来重新排列文件的顺序。

核心功能

  1. 🔄 拖拽排序

    • 支持鼠标拖拽调整文件顺序
    • 实时视觉反馈和拖拽指示器
    • 顺序指示器显示当前位置
  2. 🎯 智能交互

    • 拖拽时显示拖拽指示器
    • 悬停时显示拖拽提示
    • 拖拽完成后自动更新数据
  3. 📍 视觉反馈

    • 顺序指示器显示文件位置
    • 拖拽时的视觉效果
    • 悬停时的交互提示

🔧 技术实现

1. 组件模板更新

<template>
  <a-image-preview-group>
    <div class="select-file-container">
      <!-- 🔄 可拖拽的文件列表 -->
      <div 
        class="draggable-file-list"
        @dragover.prevent
        @drop="onDrop"
      >
        <template v-for="(item, index) in localData" :key="item.id || index">
          <div 
            class="image-upload-item draggable-item"
            :class="{ 'dragging': dragIndex === index }"
            draggable="true"
            @dragstart="onDragStart(index, $event)"
            @dragend="onDragEnd"
            @dragenter="onDragEnter(index)"
            @dragleave="onDragLeave"
            v-if="isImage(item.url)"
          >
            <!-- 🎯 拖拽指示器 -->
            <div class="drag-indicator">
              <HolderOutlined />
            </div>
            
            <a-image :src="item.url" />
            
            <!-- 📍 顺序指示器 -->
            <div class="order-indicator">{{ index + 1 }}</div>
          </div>
        </template>
      </div>
    </div>
  </a-image-preview-group>
</template>

2. 拖拽逻辑实现

// 🔄 拖拽相关状态
const dragIndex = ref<number | null>(null);
const dragOverIndex = ref<number | null>(null);

// 📝 本地数据副本,用于拖拽操作
const localData = ref<any[]>([]);

// 🔄 监听props.data变化,同步到localData
watch(
  () => props.data,
  (newData) => {
    if (newData) {
      localData.value = [...newData];
    }
  },
  { immediate: true, deep: true }
);

// 🔄 拖拽开始
const onDragStart = (index: number, event: DragEvent) => {
  dragIndex.value = index;
  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = 'move';
    event.dataTransfer.setData('text/html', index.toString());
  }
};

// 🔄 拖拽结束
const onDragEnd = () => {
  dragIndex.value = null;
  dragOverIndex.value = null;
};

// 🔄 拖拽进入
const onDragEnter = (index: number) => {
  dragOverIndex.value = index;
};

// 🔄 拖拽放置
const onDrop = (event: DragEvent) => {
  event.preventDefault();
  
  if (dragIndex.value !== null && dragOverIndex.value !== null && dragIndex.value !== dragOverIndex.value) {
    const newData = [...localData.value];
    const draggedItem = newData[dragIndex.value];
    
    // 移除拖拽的项目
    newData.splice(dragIndex.value, 1);
    
    // 在新位置插入项目
    const insertIndex = dragIndex.value < dragOverIndex.value ? dragOverIndex.value - 1 : dragOverIndex.value;
    newData.splice(insertIndex, 0, draggedItem);
    
    // 更新本地数据
    localData.value = newData;
    
    // 触发重新排序事件
    emit('reorder', newData);
  }
  
  dragIndex.value = null;
  dragOverIndex.value = null;
};

3. 事件定义更新

const emit = defineEmits<{
  (e: 'done', data: FileRecord): void;
  (e: 'del', index: number): void;
  (e: 'clear'): void;
  (e: 'reorder', data: any[]): void; // 新增重新排序事件
}>();

4. 样式设计

// 🔄 可拖拽项目样式
.draggable-item {
  position: relative;
  cursor: move;
  transition: all 0.3s ease;
  border-radius: 8px;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    
    .drag-indicator {
      opacity: 1;
    }
  }
  
  &.dragging {
    opacity: 0.5;
    transform: rotate(5deg) scale(0.95);
    z-index: 1000;
  }
}

// 🎯 拖拽指示器
.drag-indicator {
  position: absolute;
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 12px;
  opacity: 0;
  transition: opacity 0.2s ease;
  z-index: 10;
  pointer-events: none;
}

// 📍 顺序指示器
.order-indicator {
  position: absolute;
  top: -6px;
  right: -6px;
  background: #1890ff;
  color: white;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
  z-index: 5;
  border: 2px solid white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

🔗 父组件集成

在articleEdit.vue中添加对reorder事件的处理:

<SelectFile
  :placeholder="`请选择图片`"
  :limit="6"
  :data="files"
  @done="chooseFile"
  @del="onDeleteFile"
  @reorder="onReorderFiles"
/>
// 🔄 处理文件重新排序
const onReorderFiles = (newData: any[]) => {
  files.value = newData;
  form.files = JSON.stringify(files.value.map((d) => d.url));
  message.success('文件顺序已更新');
};

🎨 用户体验

  1. 直观操作:用户可以直接拖拽文件来调整顺序
  2. 视觉反馈:拖拽时有清晰的视觉指示
  3. 顺序显示:每个文件都有序号显示当前位置
  4. 即时更新:拖拽完成后立即更新数据和UI

📊 功能特点

特性 描述 状态
拖拽排序 支持鼠标拖拽调整顺序
视觉反馈 拖拽时的动画和指示器
顺序指示 显示文件的当前位置
数据同步 拖拽后自动更新数据
事件通知 触发reorder事件通知父组件
响应式设计 适配不同屏幕尺寸

🚀 使用场景

  1. 文章封面图排序:调整封面图的显示顺序,第一张作为主封面
  2. 图片轮播排序:调整轮播图片的播放顺序
  3. 文件优先级:根据重要性调整文件的排列顺序
  4. 展示顺序:调整图片在前端的展示顺序

💡 技术亮点

  • 原生HTML5拖拽API:使用标准的拖拽事件
  • Vue3响应式:利用Vue3的响应式系统
  • 数据双向绑定:保持组件内外数据同步
  • 优雅的动画效果:提供流畅的用户体验
  • 类型安全:完整的TypeScript类型定义

这个功能让用户可以更直观地管理文件顺序,特别是在处理多个封面图时,可以轻松调整哪张图片作为主封面显示!🎉