从零打造「夯到拉」垂直排名系统网页:设计、实现与优化全记录

在日常工作和学习中,我们经常需要对各类内容进行排名展示,而传统的文字排名不够直观。最近,我接到了一个特殊的需求 —— 制作一个 “夯到拉” 垂直排名系统网页,支持图片上传、拖拽排序,还需要特定的等级样式和交互逻辑。今天就来分享这个网页从需求分析到最终落地的完整过程。

一、需求拆解:明确核心功能与视觉要求

在开始编码前,我首先对用户需求进行了详细拆解,确保每个细节都被覆盖:

1. 排名体系设计

  • 垂直排列 5 个等级,从上到下依次为:夯(红色背景)、顶级(橙色背景)、人上人(黄色背景)、NPC(浅黄色背景)、拉(白色背景)
  • 等级文字需要醒目,字号要足够大,且整个表格需要黑色边框装饰

2. 图片交互功能

  • 支持本地图片上传(点击 / 拖放两种方式)
  • 上传的图片需显示在 “图片库” 区域(位于上传按钮上方)
  • 图片可从图片库拖拽到任意等级行
  • 已放置的图片支持跨等级移动,且移动过程中不能丢失
  • 每个图片需配备删除功能

3. 布局与提示要求

  • 拖拽提示框需放在图片库下方
  • 整体界面需简洁清晰,操作逻辑直观
  • 提供帮助模态框,指导用户使用

二、技术选型:轻量高效的技术栈

考虑到网页的交互需求和开发效率,我选择了以下技术组合:

  • HTML5:提供拖放 API 和文件上传功能,满足核心交互需求
  • Tailwind CSS:快速构建响应式界面,自定义颜色和样式
  • Font Awesome:提供丰富的图标,增强视觉体验
  • 原生 JavaScript:处理拖拽逻辑、文件上传和 DOM 操作,避免引入冗余框架

这种技术栈的优势在于:无需复杂的构建流程,代码轻量,加载速度快,且兼容性良好。

三、核心功能实现:一步步攻克技术难点

1. 表格结构与样式设计

首先,我创建了一个带有黑色边框的表格,用于展示 5 个等级。这里的关键是通过 Tailwind 的自定义颜色功能,为每个等级设置专属背景色,并增大文字字号:


<!-- 排名表格核心结构 -->

<section class="mb-10 border-2 border-black rounded-lg overflow-hidden">

<table class="w-full border-collapse">

<thead>

<tr class="bg-gray-800 text-white border-b-2 border-black">

<th class="py-6 px-8 text-left w-1/5 border-r-2 border-black">等级</th>

<th class="py-6 px-8 text-left w-4/5">内容</th>

</tr>

</thead>

<tbody>

<!-- 夯等级 -->

<tr class="border-b-2 border-black">

<td class="p-6 bg-rank-hang font-bold text-white text-3xl md:text-4xl border-r-2 border-black">

</td>

<td class="p-4 drop-zone" data-rank="1">

<div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-1-container"></div>

</td>

</tr>

<!-- 其他等级结构类似 -->

</tbody>

</table>

</section>

在 Tailwind 配置中,我定义了 5 个等级的专属颜色:


tailwind.config = {

theme: {

extend: {

colors: {

'rank-hang': '#FF4136', // 夯 - 红色

'rank-top': '#FF851B', // 顶级 - 橙色

'rank-elite': '#FFDC00', // 人上人 - 黄色

'rank-npc': '#FFFF9D', // NPC - 浅黄色

'rank-la': '#FFFFFF', // 拉 - 白色

},

},

}

}

2. 图片上传功能实现

图片上传支持两种方式:点击上传和拖放上传。我通过 HTML5 的 File API 和拖放 API 实现这一功能:


// 处理文件选择

fileInput.addEventListener('change', handleFiles, false);

// 处理拖放文件

uploadArea.addEventListener('drop', handleDrop, false);

// 上传图片核心函数

function uploadImage(file) {

// 验证文件类型和大小

if (!file.type.match('image.*')) {

alert('请上传图片文件(JPG、PNG或GIF)');

return;

}

if (file.size > 5 * 1024 * 1024) {

alert('图片大小不能超过5MB');

return;

}

const reader = new FileReader();

const imageId = 'img-' + Date.now() + '-' + Math.floor(Math.random() * 1000);

reader.onload = function(e) {

// 保存图片数据到Map中,便于后续使用

imageData.set(imageId, e.target.result);

// 创建图片元素并添加到图片库

const imgContainer = document.createElement('div');

imgContainer.className = 'bg-gray-50 rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow';

imgContainer.innerHTML = `

<img id="library-${imageId}" src="${e.target.result}"

class="w-full h-40 object-cover draggable"

draggable="true"

alt="可拖拽图片">

`;

imageLibrary.appendChild(imgContainer);

// 为图片添加拖拽事件

addDragEventToImage(`library-${imageId}`, imageId, 'library');

};

reader.readAsDataURL(file);

}

3. 拖拽功能:从图片库到等级行的无缝交互

拖拽功能是这个网页的核心,也是最容易出问题的部分。最初,我遇到了图片移动时丢失的问题,后来通过以下方案解决:

(1)拖拽数据格式设计

为了区分图片来源(图片库或某个等级行),我设计了一种特殊的拖拽数据格式:

  • 图片库来源:imageId|library
  • 等级行来源:imageId|rank|sourceRank

这种格式可以让我在接收拖拽时,清楚地知道图片的来源和原位置。

(2)拖拽事件处理

// 为图片添加拖拽事件

function addDragEventToImage(elementId, imageId, sourceType, sourceRank = '') {

const imgElement = document.getElementById(elementId);

if (!imgElement) return;

imgElement.addEventListener('dragstart', (e) => {

// 设置拖拽数据

const data = sourceRank ? `${imageId}|${sourceType}|${sourceRank}` : `${imageId}|${sourceType}`;

e.dataTransfer.setData('text/plain', data);

imgElement.classList.add('opacity-70');

});

imgElement.addEventListener('dragend', () => {

imgElement.classList.remove('opacity-70');

});

}

// 处理等级行的拖拽接收

dropZones.forEach(zone => {

zone.addEventListener('drop', (e) => {

zone.classList.remove('drop-highlight');

// 解析拖拽数据

const data = e.dataTransfer.getData('text/plain').split('|');

const imageId = data[0];

const sourceType = data[1] || 'library';

const sourceRank = data[2];

// 验证图片是否存在

if (!imageId || !imageData.has(imageId)) return;

const targetRank = zone.dataset.rank;

const targetContainer = document.getElementById(`rank-${targetRank}-container`);

// 如果是从其他等级行拖过来的,先移除原图片

if (sourceType === 'rank' && sourceRank && sourceRank !== targetRank) {

const sourceContainer = document.getElementById(`rank-${sourceRank}-container`);

const sourceElement = sourceContainer.querySelector(`[data-image-id="${imageId}"]`);

if (sourceElement) {

sourceElement.remove();

}

}

// 创建新的图片元素并添加到目标容器

const imgSrc = imageData.get(imageId);

const imgElement = createRankImageElement(imageId, imgSrc, targetRank);

targetContainer.appendChild(imgElement);

});

});

(3)排名区域图片元素创建

// 创建排名区域的图片元素

function createRankImageElement(imageId, imgSrc, rank) {

const container = document.createElement('div');

container.className = 'relative group draggable';

container.dataset.imageId = imageId;

container.innerHTML = `

<img src="${imgSrc}" class="w-24 h-24 object-cover rounded shadow-md"

draggable="true" alt="排名图片">

<button class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6

flex items-center justify-center opacity-0 group-hover:opacity-100

transition-opacity delete-img">

<i class="fa fa-times text-xs"></i>

</button>

`;

// 为图片添加拖拽事件,支持再次移动

const imgElement = container.querySelector('img');

imgElement.id = `rank-${rank}-${imageId}`;

addDragEventToImage(imgElement.id, imageId, 'rank', rank);

// 添加删除功能

container.querySelector('.delete-img').addEventListener('click', () => {

container.remove();

});

return container;

}

4. 布局调整:满足用户的视觉要求

根据用户要求,我调整了图片库和上传区域的位置关系:

  • 图片库(已上传图片)位于上方
  • 上传按钮和提示框位于图片库下方
  • 拖拽提示文本放在最底部,清晰指导用户操作

<!-- 图片上传区域布局 -->

<section class="bg-white rounded-lg shadow-md p-6 border-2 border-black">

<h2 class="text-xl font-semibold mb-6 text-gray-800 flex items-center">

<i class="fa fa-picture-o text-blue-500 mr-2"></i> 图片库

</h2>

<!-- 图片库(上方) -->

<div id="image-library" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 mb-6 min-h-[120px]">

<!-- 已上传图片显示区域 -->

</div>

<!-- 上传区域(下方) -->

<div id="upload-area" class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors">

<!-- 上传按钮和提示 -->

</div>

<!-- 拖拽提示(最下方) -->

<div class="mt-4 text-center text-sm text-gray-600 italic">

<i class="fa fa-info-circle mr-1"></i> 提示:从上方图片库中拖拽图片到等级行,也可在不同等级间移动调整

</div>

</section>

四、问题解决:攻克开发过程中的难点

在开发过程中,我遇到了几个典型问题,通过针对性的解决方案,最终实现了流畅的用户体验:

1. 图片移动时丢失的问题

问题原因:最初的实现中,没有妥善保存图片数据,且拖拽数据格式不清晰,导致无法正确识别图片来源。

解决方案

  • 使用 Map 数据结构保存所有图片的 ID 和源地址
  • 设计清晰的拖拽数据格式,包含图片 ID、来源类型和原位置
  • 在接收拖拽时,先验证图片是否存在,再进行后续操作

2. 拖拽视觉反馈不明显

问题原因:用户在拖拽过程中,无法清晰判断当前拖拽状态和目标位置。

解决方案

  • 为拖拽中的图片添加透明度变化(opacity-70)
  • 为目标等级行添加背景色变化(drop-highlight)
  • 为图片库中的图片添加 hover 阴影效果,提示可拖拽

3. 响应式适配问题

问题原因:在小屏幕设备上,表格和图片库可能显示异常。

解决方案

  • 使用 Tailwind 的响应式类(md:、lg:)调整不同屏幕尺寸下的样式
  • 图片库采用自适应网格布局,根据屏幕宽度自动调整列数
  • 等级文字字号随屏幕尺寸变化,确保在小屏幕上也能清晰显示

五、最终效果与功能总结

经过多轮优化,最终的 “夯到拉” 垂直排名系统网页实现了以下功能:

1. 直观的排名展示

  • 5 个等级垂直排列,每个等级有专属背景色和黑色边框
  • 等级文字醒目(3xl-4xl 字号),视觉层次清晰

2. 灵活的图片交互

  • 支持点击 / 拖放两种上传方式
  • 图片可在图片库和等级行之间自由拖拽
  • 已放置的图片支持跨等级移动和删除
  • 拖拽过程中视觉反馈明显,操作流畅

3. 友好的用户引导

  • 清晰的提示文本,指导用户操作
  • 帮助模态框详细说明使用方法
  • 错误提示(如文件类型错误、大小超限)及时准确

六、后续优化方向

虽然当前版本已经满足了用户的所有需求,但仍有一些可以优化的方向:

  1. 图片预览压缩:对于大尺寸图片,在上传前进行压缩,提高加载速度
  1. 排名数据保存:添加本地存储功能,保存用户的排名结果,刷新页面后不丢失
  1. 批量操作:支持批量选择图片上传,批量调整图片等级
  1. 动画效果:为图片拖拽和删除添加平滑的动画效果,提升视觉体验
  1. 多语言支持:添加中英文切换功能,满足不同用户需求

七、开发心得

通过这个项目,我深刻体会到:

  1. 需求分析是关键:在开始编码前,一定要仔细拆解需求,确保每个细节都被覆盖,避免后期反复修改。
  1. 渐进式开发:先实现核心功能,再逐步优化细节和交互,这样可以更快地看到成果,也便于定位问题。
  1. 用户体验至上:技术最终是为用户服务的,要站在用户的角度思考,确保操作直观、反馈及时、视觉舒适。
  1. 问题记录与总结:在开发过程中遇到的问题和解决方案,要及时记录,便于后续回顾和复用。

如果你也有类似的排名展示需求,或者对这个网页有任何改进建议,欢迎在评论区留言交流!

完整代码自取可商用


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>垂直排名系统 - 夯到拉</title>
  <!-- 引入Tailwind CSS -->
  <script src="https://cdn.tailwindcss.com"></script>
  <!-- 引入Font Awesome -->
  <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">

  <!-- 配置Tailwind自定义颜色 -->
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            'rank-hang': '#FF4136',    // 夯 - 红色
            'rank-top': '#FF851B',     // 顶级 - 橙色
            'rank-elite': '#FFDC00',   // 人上人 - 黄色
            'rank-npc': '#FFFF9D',     // NPC - 浅黄色
            'rank-la': '#FFFFFF',      // 拉 - 白色
          },
        },
      }
    }
  </script>

  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .draggable {
        cursor: grab;
        transition: all 0.2s ease;
      }
      .draggable:active {
        cursor: grabbing;
        transform: scale(0.95);
      }
      .drop-highlight {
        background-color: rgba(59, 130, 246, 0.1);
      }
    }
  </style>
</head>
<body class="bg-gray-100 min-h-screen p-4 md:p-8">
  <div class="container mx-auto max-w-6xl">
    <!-- 标题区域 -->
    <header class="mb-8 text-center">
      <h1 class="text-3xl md:text-4xl font-bold text-gray-800 mb-2">夯到拉排名系统</h1>
      <p class="text-gray-600">拖拽图片到对应等级,支持跨等级调整</p>
    </header>

    <!-- 排名表格区域 -->
    <section class="mb-10 border-2 border-black rounded-lg overflow-hidden">
      <table class="w-full border-collapse">
        <thead>
          <tr class="bg-gray-800 text-white border-b-2 border-black">
            <th class="py-6 px-4 md:px-8 text-left w-1/5 border-r-2 border-black">等级</th>
            <th class="py-6 px-4 md:px-8 text-left w-4/5">内容</th>
          </tr>
        </thead>
        <tbody>
          <!-- 夯 - 红色背景 -->
          <tr class="border-b-2 border-black">
            <td class="p-6 bg-rank-hang font-bold text-white text-3xl md:text-4xl border-r-2 border-black">
              夯
            </td>
            <td class="p-4 drop-zone" data-rank="1">
              <div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-1-container"></div>
            </td>
          </tr>

          <!-- 顶级 - 橙色背景 -->
          <tr class="border-b-2 border-black">
            <td class="p-6 bg-rank-top font-bold text-white text-3xl md:text-4xl border-r-2 border-black">
              顶级
            </td>
            <td class="p-4 drop-zone" data-rank="2">
              <div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-2-container"></div>
            </td>
          </tr>

          <!-- 人上人 - 黄色背景 -->
          <tr class="border-b-2 border-black">
            <td class="p-6 bg-rank-elite font-bold text-gray-800 text-3xl md:text-4xl border-r-2 border-black">
              人上人
            </td>
            <td class="p-4 drop-zone" data-rank="3">
              <div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-3-container"></div>
            </td>
          </tr>

          <!-- NPC - 浅黄色背景 -->
          <tr class="border-b-2 border-black">
            <td class="p-6 bg-rank-npc font-bold text-gray-800 text-3xl md:text-4xl border-r-2 border-black">
              NPC
            </td>
            <td class="p-4 drop-zone" data-rank="4">
              <div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-4-container"></div>
            </td>
          </tr>

          <!-- 拉 - 白色背景 -->
          <tr>
            <td class="p-6 bg-rank-la font-bold text-gray-800 text-3xl md:text-4xl border-r-2 border-black">
              拉
            </td>
            <td class="p-4 drop-zone" data-rank="5">
              <div class="flex flex-wrap gap-4 min-h-[160px] p-2" id="rank-5-container"></div>
            </td>
          </tr>
        </tbody>
      </table>
    </section>

    <!-- 图片上传区域 -->
    <section class="bg-white rounded-lg shadow-md p-6 border-2 border-black">
      <h2 class="text-xl font-semibold mb-6 text-gray-800 flex items-center">
        <i class="fa fa-picture-o text-blue-500 mr-2"></i> 图片库
      </h2>

      <!-- 已上传图片区域 -->
      <div id="image-library" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 mb-6 min-h-[120px]">
        <!-- 上传的图片将显示在这里 -->
      </div>

      <!-- 上传区域和提示框 - 放在图片下方 -->
      <div id="upload-area" class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-blue-500 transition-colors">
        <i class="fa fa-cloud-upload text-4xl text-gray-400 mb-3"></i>
        <p class="text-gray-600 mb-4">拖放图片到这里,或点击选择图片</p>
        <label class="inline-block bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded cursor-pointer transition-colors">
          <i class="fa fa-plus mr-1"></i> 选择图片
          <input type="file" id="file-input" accept="image/*" multiple class="hidden">
        </label>
        <p class="text-xs text-gray-500 mt-3">支持 JPG, PNG, GIF 格式,单张不超过 5MB</p>
      </div>

      <!-- 拖拽提示 -->
      <div class="mt-4 text-center text-sm text-gray-600 italic">
        <i class="fa fa-info-circle mr-1"></i> 提示:从上方图片库中拖拽图片到等级行,也可在不同等级间移动调整
      </div>
    </section>

    <!-- 操作按钮 -->
    <div class="flex justify-center gap-4 mt-8 mb-12">
      <button id="clear-btn" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition-colors flex items-center">
        <i class="fa fa-trash mr-2"></i> 清空所有
      </button>
      <button id="help-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-lg transition-colors flex items-center">
        <i class="fa fa-question-circle mr-2"></i> 使用帮助
      </button>
    </div>
  </div>

  <!-- 帮助模态框 -->
  <div id="help-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
    <div class="bg-white rounded-lg shadow-xl max-w-md w-full border-2 border-black">
      <div class="p-6 border-b border-gray-200">
        <h3 class="text-xl font-bold text-gray-800">使用帮助</h3>
      </div>
      <div class="p-6">
        <ul class="space-y-3 text-gray-700">
          <li class="flex items-start">
            <i class="fa fa-check-circle text-green-500 mt-1 mr-2"></i>
            <span>通过下方上传区域添加图片,图片会显示在上方图片库</span>
          </li>
          <li class="flex items-start">
            <i class="fa fa-check-circle text-green-500 mt-1 mr-2"></i>
            <span>将图片从图片库拖拽到任意等级行中</span>
          </li>
          <li class="flex items-start">
            <i class="fa fa-check-circle text-green-500 mt-1 mr-2"></i>
            <span>已放置的图片可以拖拽到其他等级行调整位置</span>
          </li>
          <li class="flex items-start">
            <i class="fa fa-check-circle text-green-500 mt-1 mr-2"></i>
            <span>点击图片上的删除按钮移除图片</span>
          </li>
        </ul>
      </div>
      <div class="p-4 bg-gray-50 rounded-b-lg text-right">
        <button id="close-help" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors">
          明白了
        </button>
      </div>
    </div>
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      // 获取DOM元素
      const uploadArea = document.getElementById('upload-area');
      const fileInput = document.getElementById('file-input');
      const imageLibrary = document.getElementById('image-library');
      const dropZones = document.querySelectorAll('.drop-zone');
      const clearBtn = document.getElementById('clear-btn');
      const helpBtn = document.getElementById('help-btn');
      const helpModal = document.getElementById('help-modal');
      const closeHelp = document.getElementById('close-help');

      // 存储所有图片数据
      let imageData = new Map(); // 存储图片ID和源地址

      // 初始化图片库提示
      imageLibrary.innerHTML = '<div class="col-span-full text-center text-gray-500 py-12">上传的图片将显示在这里</div>';

      // 阻止拖放默认行为
      ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        uploadArea.addEventListener(eventName, preventDefaults, false);
        dropZones.forEach(zone => {
          zone.addEventListener(eventName, preventDefaults, false);
        });
      });

      // 处理上传区域的拖放视觉效果
      ['dragenter', 'dragover'].forEach(eventName => {
        uploadArea.addEventListener(eventName, () => {
          uploadArea.classList.add('border-blue-500', 'bg-blue-50');
        }, false);
      });

      ['dragleave', 'drop'].forEach(eventName => {
        uploadArea.addEventListener(eventName, () => {
          uploadArea.classList.remove('border-blue-500', 'bg-blue-50');
        }, false);
      });

      // 处理排名区域的拖放视觉效果
      dropZones.forEach(zone => {
        zone.addEventListener('dragover', () => {
          zone.classList.add('drop-highlight');
        });

        zone.addEventListener('dragleave', () => {
          zone.classList.remove('drop-highlight');
        });
      });

      // 处理上传区域的文件拖放
      uploadArea.addEventListener('drop', handleDrop, false);

      // 处理文件选择
      fileInput.addEventListener('change', handleFiles, false);
      uploadArea.addEventListener('click', () => fileInput.click());

      // 处理排名区域的放置事件
      dropZones.forEach(zone => {
        zone.addEventListener('drop', (e) => {
          // 移除高亮效果
          zone.classList.remove('drop-highlight');

          // 解析拖拽数据
          const data = e.dataTransfer.getData('text/plain').split('|');
          const imageId = data[0];
          const sourceType = data[1] || 'library'; // 来源类型
          const sourceRank = data[2]; // 来源等级

          // 验证图片ID
          if (!imageId || !imageData.has(imageId)) return;

          // 获取目标等级和容器
          const targetRank = zone.dataset.rank;
          const targetContainer = document.getElementById(`rank-${targetRank}-container`);

          // 如果是从其他等级拖过来的,先从原等级移除
          if (sourceType === 'rank' && sourceRank && sourceRank !== targetRank) {
            const sourceContainer = document.getElementById(`rank-${sourceRank}-container`);
            const sourceElement = sourceContainer.querySelector(`[data-image-id="${imageId}"]`);
            if (sourceElement) {
              sourceElement.remove();
            }
          }

          // 创建图片元素
          const imgSrc = imageData.get(imageId);
          const imgElement = createRankImageElement(imageId, imgSrc, targetRank);

          // 添加到目标容器
          targetContainer.appendChild(imgElement);
        });
      });

      // 清空按钮事件
      clearBtn.addEventListener('click', () => {
        if (confirm('确定要清空所有内容吗?')) {
          // 清空所有排名容器
          for (let i = 1; i <= 5; i++) {
            document.getElementById(`rank-${i}-container`).innerHTML = '';
          }

          // 清空图片库和数据
          imageLibrary.innerHTML = '<div class="col-span-full text-center text-gray-500 py-12">上传的图片将显示在这里</div>';
          imageData.clear();
        }
      });

      // 帮助按钮事件
      helpBtn.addEventListener('click', () => {
        helpModal.classList.remove('hidden');
        helpModal.classList.add('flex');
      });

      // 关闭帮助模态框
      closeHelp.addEventListener('click', () => {
        helpModal.classList.add('hidden');
        helpModal.classList.remove('flex');
      });

      // 点击模态框外部关闭
      helpModal.addEventListener('click', (e) => {
        if (e.target === helpModal) {
          closeHelp.click();
        }
      });

      // 辅助函数:阻止默认行为
      function preventDefaults(e) {
        e.preventDefault();
        e.stopPropagation();
      }

      // 处理拖放文件
      function handleDrop(e) {
        const dt = e.dataTransfer;
        const files = dt.files;
        handleFiles({ target: { files } });
      }

      // 处理选择的文件
      function handleFiles(e) {
        const files = e.target.files;
        if (!files.length) return;

        // 如果是第一次上传,清除提示
        if (imageLibrary.querySelector('.col-span-full')) {
          imageLibrary.innerHTML = '';
        }

        // 处理每个文件
        Array.from(files).forEach(uploadImage);
      }

      // 上传并显示图片
      function uploadImage(file) {
        // 验证文件类型
        if (!file.type.match('image.*')) {
          alert('请上传图片文件(JPG、PNG或GIF)');
          return;
        }

        // 验证文件大小
        if (file.size > 5 * 1024 * 1024) {
          alert('图片大小不能超过5MB');
          return;
        }

        const reader = new FileReader();
        const imageId = 'img-' + Date.now() + '-' + Math.floor(Math.random() * 1000);

        reader.onload = function(e) {
          // 保存图片数据
          imageData.set(imageId, e.target.result);

          // 创建图片元素
          const imgContainer = document.createElement('div');
          imgContainer.className = 'bg-gray-50 rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow';
          imgContainer.innerHTML = `
            <img id="library-${imageId}" src="${e.target.result}"
                 class="w-full h-40 object-cover draggable"
                 draggable="true"
                 alt="可拖拽图片">
          `;

          imageLibrary.appendChild(imgContainer);

          // 添加拖拽事件
          const imgElement = document.getElementById(`library-${imageId}`);
          imgElement.addEventListener('dragstart', (e) => {
            e.dataTransfer.setData('text/plain', `${imageId}|library`);
            imgElement.classList.add('opacity-70');
          });

          imgElement.addEventListener('dragend', () => {
            imgElement.classList.remove('opacity-70');
          });
        };

        reader.readAsDataURL(file);
      }

      // 创建排名区域的图片元素
      function createRankImageElement(imageId, imgSrc, rank) {
        const container = document.createElement('div');
        container.className = 'relative group draggable';
        container.dataset.imageId = imageId;

        container.innerHTML = `
          <img src="${imgSrc}" class="w-24 h-24 object-cover rounded shadow-md"
               draggable="true" alt="排名图片">
          <button class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full w-6 h-6
                         flex items-center justify-center opacity-0 group-hover:opacity-100
                         transition-opacity delete-img">
            <i class="fa fa-times text-xs"></i>
          </button>
        `;

        // 添加拖拽事件(支持跨等级移动)
        const imgElement = container.querySelector('img');
        imgElement.addEventListener('dragstart', (e) => {
          e.dataTransfer.setData('text/plain', `${imageId}|rank|${rank}`);
          imgElement.classList.add('opacity-70');
        });

        imgElement.addEventListener('dragend', () => {
          imgElement.classList.remove('opacity-70');
        });

        // 添加删除事件
        container.querySelector('.delete-img').addEventListener('click', () => {
          container.remove();
        });

        return container;
      }
    });
  </script>
</body>
</html>

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐