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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 10:35:53