基于MudBlazor/原生Blazor实现树形视图拖拽功能求启始代码
Blazor WASM + MudBlazor 树形视图拖拽实现
核心实现代码
1. 节点模型定义
首先创建树形节点的基础模型,包含拖拽位置标记:
public class TreeNode { public Guid Id { get; set; } = Guid.NewGuid(); public string Text { get; set; } = string.Empty; public List<TreeNode> Children { get; set; } = new(); public TreeNode? Parent { get; set; } public DropPosition DropPosition { get; set; } = DropPosition.None; } public enum DropPosition { None, Before, After, Inside }
2. 页面逻辑与UI(Razor组件)
@page "/tree-drag-drop" @using System.Linq <MudGrid> <MudItem xs="12" md="4"> <MudCard> <MudCardContent> <MudTypography Variant="Variant.h6">可拖拽项</MudTypography> <MudList> @foreach (var item in DraggableItems) { <MudListItem @key="item" Draggable="true" @ondragstart="() => HandleItemDragStart(item)" Style="cursor: grab;"> @item </MudListItem> } </MudList> </MudCardContent> </MudCard> </MudItem> <MudItem xs="12" md="8"> <MudCard> <MudCardContent> <MudTypography Variant="Variant.h6">树形视图</MudTypography> <div @ondragover="HandleTreeDragOver" @ondrop="HandleTreeDrop" Style="min-height: 200px; padding: 10px;"> <MudTreeView Items="@TreeNodes"> <ItemTemplate> @{ var node = context as TreeNode; } <MudTreeViewItem @key="node.Id" Text="@node.Text" Items="@node.Children" Draggable="true" @ondragstart="() => HandleNodeDragStart(node)" @ondragover="(e) => HandleNodeDragOver(e, node)" Class="@GetNodeClass(node)"> </MudTreeViewItem> </ItemTemplate> </MudTreeView> @if (!TreeNodes.Any()) { <MudText Color="Color.Info">拖拽项到此处添加根节点</MudText> } </div> </MudCardContent> </MudCard> </MudItem> </MudGrid> @code { private List<string> DraggableItems = new() { "A", "B", "C", "D", "E" }; private List<TreeNode> TreeNodes = new(); private object? DraggedItem; private void HandleItemDragStart(string item) { DraggedItem = item; DragDrop.SetData("text/plain", item); } private void HandleNodeDragStart(TreeNode node) { DraggedItem = node; DragDrop.SetData("tree-node", node.Id.ToString()); } private void HandleTreeDragOver(DragEventArgs e) { e.PreventDefault(); if (!TreeNodes.Any()) ResetAllDropPositions(); } private void HandleNodeDragOver(DragEventArgs e, TreeNode targetNode) { e.PreventDefault(); ResetAllDropPositions(); var rect = e.Target.GetBoundingClientRect(); var yPos = e.ClientY - rect.Top; var height = rect.Height; if (yPos < height * 0.25) targetNode.DropPosition = DropPosition.Before; else if (yPos > height * 0.75) targetNode.DropPosition = DropPosition.After; else targetNode.DropPosition = DropPosition.Inside; StateHasChanged(); } private void HandleTreeDrop(DragEventArgs e) { e.PreventDefault(); ResetAllDropPositions(); if (DraggedItem == null) return; // 处理从列表拖拽新增节点 if (DraggedItem is string textItem) { var newNode = new TreeNode { Text = textItem }; var targetNode = TreeNodes.FirstOrDefault(n => n.DropPosition != DropPosition.None); if (targetNode == null) TreeNodes.Add(newNode); else InsertOrAppendNode(newNode, targetNode); } // 处理树形节点内部移动 else if (DraggedItem is TreeNode draggedNode) { var targetNode = TreeNodes.FirstOrDefault(n => n.DropPosition != DropPosition.None); if (targetNode == null || draggedNode == targetNode) return; // 移除原位置节点 if (draggedNode.Parent == null) TreeNodes.Remove(draggedNode); else draggedNode.Parent.Children.Remove(draggedNode); InsertOrAppendNode(draggedNode, targetNode); } StateHasChanged(); } private void InsertOrAppendNode(TreeNode node, TreeNode targetNode) { switch (targetNode.DropPosition) { case DropPosition.Before: if (targetNode.Parent == null) { var index = TreeNodes.IndexOf(targetNode); TreeNodes.Insert(index, node); } else { var index = targetNode.Parent.Children.IndexOf(targetNode); targetNode.Parent.Children.Insert(index, node); node.Parent = targetNode.Parent; } break; case DropPosition.After: if (targetNode.Parent == null) { var index = TreeNodes.IndexOf(targetNode); TreeNodes.Insert(index + 1, node); } else { var index = targetNode.Parent.Children.IndexOf(targetNode); targetNode.Parent.Children.Insert(index + 1, node); node.Parent = targetNode.Parent; } break; case DropPosition.Inside: targetNode.Children.Add(node); node.Parent = targetNode; break; } } private void ResetAllDropPositions() { void ResetRecursively(TreeNode node) { node.DropPosition = DropPosition.None; foreach (var child in node.Children) ResetRecursively(child); } foreach (var node in TreeNodes) ResetRecursively(node); } private string GetNodeClass(TreeNode node) { return node.DropPosition switch { DropPosition.Before => "border-t-2 border-primary", DropPosition.After => "border-b-2 border-primary", DropPosition.Inside => "border-2 border-primary rounded", _ => "" }; } }
3. 样式优化(可选)
在wwwroot/css/app.css中添加:
.border-t-2 { border-top-width: 2px !important; } .border-b-2 { border-bottom-width: 2px !important; } .border-2 { border-width: 2px !important; }
关键功能说明
- 双来源拖拽:支持从左侧文本列表拖拽生成新节点,也支持树形节点内部移动
- 位置检测:通过鼠标在节点内的垂直位置(25%/75%高度分界)判断插入位置
- 视觉反馈:动态添加边框样式,直观提示拖拽目标位置
- 层级处理:自动处理根节点和子节点的插入、移动逻辑
内容的提问来源于stack exchange,提问作者Fredrik Gunne
相关产品推荐
相关产品推荐

