从夯到拉排行网页制作
从零打造「夯到拉」垂直排名系统网页:设计、实现与优化全记录
在日常工作和学习中,我们经常需要对各类内容进行排名展示,而传统的文字排名不够直观。最近,我接到了一个特殊的需求 —— 制作一个 “夯到拉” 垂直排名系统网页,支持图片上传、拖拽排序,还需要特定的等级样式和交互逻辑。今天就来分享这个网页从需求分析到最终落地的完整过程。
一、需求拆解:明确核心功能与视觉要求
在开始编码前,我首先对用户需求进行了详细拆解,确保每个细节都被覆盖:
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. 友好的用户引导
- 清晰的提示文本,指导用户操作
- 帮助模态框详细说明使用方法
- 错误提示(如文件类型错误、大小超限)及时准确
六、后续优化方向
虽然当前版本已经满足了用户的所有需求,但仍有一些可以优化的方向:
- 图片预览压缩:对于大尺寸图片,在上传前进行压缩,提高加载速度
- 排名数据保存:添加本地存储功能,保存用户的排名结果,刷新页面后不丢失
- 批量操作:支持批量选择图片上传,批量调整图片等级
- 动画效果:为图片拖拽和删除添加平滑的动画效果,提升视觉体验
- 多语言支持:添加中英文切换功能,满足不同用户需求
七、开发心得
通过这个项目,我深刻体会到:
- 需求分析是关键:在开始编码前,一定要仔细拆解需求,确保每个细节都被覆盖,避免后期反复修改。
- 渐进式开发:先实现核心功能,再逐步优化细节和交互,这样可以更快地看到成果,也便于定位问题。
- 用户体验至上:技术最终是为用户服务的,要站在用户的角度思考,确保操作直观、反馈及时、视觉舒适。
- 问题记录与总结:在开发过程中遇到的问题和解决方案,要及时记录,便于后续回顾和复用。
如果你也有类似的排名展示需求,或者对这个网页有任何改进建议,欢迎在评论区留言交流!
完整代码自取可商用
<!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>
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)