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

如何实现HTML拖拽排序列表并提交至ASP.NET Core后端

入门开发指南:拖拽组卷功能实现

一、前端实现(原生HTML+JS)

1. 基础结构与样式

先搭建两个核心区域:例题列表区和组卷区,搭配基础样式区分视觉层级:

<div class="container">
  <div class="section">
    <h3>课程例题列表</h3>
    <div id="question-list" class="question-container">
      <div class="question-item" draggable="true" data-id="1">例题1:简述HTML语义化的意义</div>
      <div class="question-item" draggable="true" data-id="2">例题2:写出CSS垂直居中的3种方法</div>
      <div class="question-item" draggable="true" data-id="3">例题3:解释JS中闭包的概念及应用场景</div>
    </div>
  </div>

  <div class="section">
    <h3>自选组卷区</h3>
    <div id="selected-list" class="question-container"></div>
    <button id="submit-btn" style="margin-top:1rem;padding:0.5rem 1rem;">提交组卷</button>
  </div>
</div>
.container {
  display: flex;
  gap: 2rem;
  padding: 1rem;
}
.section {
  flex: 1;
}
.question-container {
  border: 2px solid #eee;
  padding: 1rem;
  min-height: 300px;
  border-radius: 4px;
}
.question-item {
  padding: 0.8rem;
  margin: 0.5rem 0;
  background: #f5f5f5;
  border-radius: 4px;
  cursor: grab;
  transition: background 0.2s;
}
.question-item:active {
  cursor: grabbing;
}
#selected-list .question-item {
  background: #e8f4f8;
}

2. 拖拽选卷功能

用原生HTML5 Drag API实现从例题列表到组卷区的拖拽:

const questionItems = document.querySelectorAll('#question-list .question-item');
const selectedList = document.getElementById('selected-list');

// 处理例题项拖拽开始
questionItems.forEach(item => {
  item.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', e.target.dataset.id);
    e.target.style.opacity = '0.5';
  });
  item.addEventListener('dragend', (e) => {
    e.target.style.opacity = '1';
  });
});

// 组卷区允许拖拽
selectedList.addEventListener('dragover', (e) => {
  e.preventDefault();
  selectedList.style.borderColor = '#2196F3';
});
selectedList.addEventListener('dragleave', () => {
  selectedList.style.borderColor = '#eee';
});

// 完成拖拽添加到组卷区
selectedList.addEventListener('drop', (e) => {
  e.preventDefault();
  selectedList.style.borderColor = '#eee';
  const questionId = e.dataTransfer.getData('text/plain');
  const originalItem = document.querySelector(`#question-list .question-item[data-id="${questionId}"]`);
  
  // 复制原元素到组卷区(如需移动可改为移除原元素)
  const clonedItem = originalItem.cloneNode(true);
  clonedItem.draggable = true;
  selectedList.appendChild(clonedItem);
});

3. 已选题目排序

给组卷区的题目添加拖拽排序功能:

// 记录当前拖拽的元素
let draggedItem = null;

selectedList.addEventListener('dragstart', (e) => {
  if (e.target.classList.contains('question-item')) {
    draggedItem = e.target;
    e.target.style.opacity = '0.5';
  }
});

selectedList.addEventListener('dragover', (e) => {
  e.preventDefault();
  const afterElement = getDragTarget(selectedList, e.clientY);
  if (afterElement) {
    selectedList.insertBefore(draggedItem, afterElement);
  } else {
    selectedList.appendChild(draggedItem);
  }
});

selectedList.addEventListener('dragend', (e) => {
  e.target.style.opacity = '1';
  draggedItem = null;
});

// 辅助函数:找到拖拽位置对应的插入目标
function getDragTarget(container, y) {
  const items = [...container.querySelectorAll('.question-item:not(.dragging)')];
  return items.reduce((closest, child) => {
    const box = child.getBoundingClientRect();
    const offset = y - box.top - box.height / 2;
    if (offset < 0 && offset > closest.offset) {
      return { offset, element: child };
    }
    return closest;
  }, { offset: Number.NEGATIVE_INFINITY }).element;
}

4. 提交组卷数据

收集组卷区的题目ID顺序,发送POST请求到后端:

const submitBtn = document.getElementById('submit-btn');

submitBtn.addEventListener('click', () => {
  const selectedItems = selectedList.querySelectorAll('.question-item');
  const questionOrder = Array.from(selectedItems).map(item => item.dataset.id);
  
  if (questionOrder.length === 0) {
    alert('请至少选择一道题目');
    return;
  }

  fetch('/api/paper/submit', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ questionIds: questionOrder })
  })
  .then(res => res.json())
  .then(data => {
    alert(data.Message || '提交成功');
    selectedList.innerHTML = '';
  })
  .catch(err => {
    console.error('提交失败:', err);
    alert('提交失败,请重试');
  });
});

二、ASP.NET Core 后端实现

1. 创建API控制器

新建PaperController处理组卷提交请求:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

[ApiController]
[Route("api/paper")]
public class PaperController : ControllerBase
{
    // 替换为你的数据库上下文
    private readonly AppDbContext _context;

    public PaperController(AppDbContext context)
    {
        _context = context;
    }

    [HttpPost("submit")]
    public async Task<IActionResult> SubmitPaper([FromBody] PaperSubmissionModel model)
    {
        if (model?.QuestionIds == null || model.QuestionIds.Count == 0)
        {
            return BadRequest("请选择至少一道题目");
        }

        // 保存到数据库:创建组卷记录,存储题目顺序
        var paper = new Paper
        {
            QuestionOrder = string.Join(',', model.QuestionIds),
            CreatedAt = DateTime.Now
        };
        _context.Papers.Add(paper);
        await _context.SaveChangesAsync();

        return Ok(new { Success = true, Message = "组卷保存成功" });
    }
}

// 提交数据模型
public class PaperSubmissionModel
{
    public List<string> QuestionIds { get; set; }
}

2. 数据库实体与配置

创建组卷实体类,并配置DbContext:

public class Paper
{
    public int Id { get; set; }
    public string QuestionOrder { get; set; } // 存储逗号分隔的题目ID
    public DateTime CreatedAt { get; set; }
}

// 在你的DbContext中添加DbSet
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    public DbSet<Paper> Papers { get; set; }
}

执行迁移命令生成数据库表:

Add-Migration AddPaperTable
Update-Database

三、入门优化建议

  • 例题列表改为从后端接口动态加载,替换静态HTML
  • 给组卷区的题目添加删除按钮,支持移除操作
  • 后端添加参数验证,过滤非法题目ID
  • 如需兼容旧浏览器,可替换为SortableJS等轻量拖拽库

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 00:42:48