一个基于ASP.NET Core MVC、模仿Windows资源管理器界面的网页应用,使用jstree和表格,但我不知道在选中节点变化时如何更新并显示树和表格

前端开发 2026-07-10

我有一个带产品类别表和产品表的SQL数据库,我正尝试创建一个页面:左侧显示一个从产品类别表填充的树状视图,右侧是一个表。这个表应该使用来自products数据库表的查询,并以树状视图中选中的节点ID作为参数,仅显示所选类别的产品。

我在使用Entity Framework Core,按常规覆盖 DbContext 类来获取数据。ASP.NET Core MVC没有树状控件,所以我使用jQuery的 jsTree插件,这给我带来了所有问题。

这是用于产品类别的 ProductFolder 类:

public class ProductFolder
{
    [Key]
    public int ProductFolderId { get; set; } = 0;

    public int ParentFolderId { get; set; } = 0;
    public int ProductFolderLevel { get; set; }

    [Required]
    public string ProductFolderName { get; set; } = string.Empty;

    public string Description { get; set; } = string.Empty;

    public ProductFolder()
    {
        ProductFolderId = 0;
        ParentFolderId = 0;
        ProductFolderLevel = 0;
        ProductFolderName = string.Empty;
    }
}

这是 Product 类:

public class Product
{
    [Key]
    public int ProductId { get; set; } = 0;

    public int ParentFolderId { get; set; } = 0;
    public int ProductLevel { get; set; }

    [Required]
    [Display(Name = "Product Name")]
    public string ProductName { get; set; } = string.Empty;

    public string Description { get; set; } = string.Empty;

    [Display(Name = "Width [DX]")]
    public decimal DX { get; set; } = 0.0M;

    [Display(Name = "Height [DY]")]
    public decimal DY { get; set; } = 0.0M;

    [Display(Name = "Depth [DZ]")]
    public decimal DZ { get; set; } = 0.0M;

    public int CNCMachineId { get; set; } = 1;

    [Display(Name = "CNC Machine")]
    public string CNCMachineName { get; set; } = string.Empty;

    [NotMapped]
    ProductFaceCollection ProductFaceCollection;

    [NotMapped]
    ProductInteriorCollection ProductInteriorCollection;

    [NotMapped]
    public decimal materialThickness { get; set; } = 18.0M;

    public Product()
    {
        ProductId = 0;
        ParentFolderId = 0;
        ProductLevel = 1;
        ProductName = string.Empty;

        ProductFaceCollection = new ProductFaceCollection();
        ProductFaceCollection.ProductFaceCells.Add(new ProductFaceCell());

        ProductInteriorCollection = new ProductInteriorCollection();
        ProductInteriorCollection.ProductInteriorCells.Add(new ProductInteriorCell());
    }
}

以及用于填充树状视图的 JsTreeView 类:

public class JsTreeModel
{
    public string id { get; set; }
    public string text { get; set; }
    public int level { get; set; }

    public List<JsTreeModel> children { get; set; }

    public JsTreeModel()
    {
        id = "#";
        text = string.Empty;
        level = 0;
        children = new List<JsTreeModel>();
    }

    public JsTreeModel(int id, string text, int level)
    {
        this.id = id.ToString();
        this.text = text;
        this.level = level;
        children = new List<JsTreeModel>();
    }
}

这是控制器:

using System.Data;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Mvc;
using MyIMS_EFCore.Models;

namespace MyIMS_EFCore.Controllers
{
    public class ProductsController : Controller
    {
        private readonly AppDbContext _context;
        private readonly ILogger<ProductsController> _logger;

        public ProductsController(AppDbContext context, ILogger<ProductsController> logger)
        {
            _context = context;
            _logger = logger;
        }

        // GET: Products
        public IActionResult Index(int? id)
        {
            // Create jstree nodes from product categories.
            JsTreeModel ProductTreeModel = new JsTreeModel();
            GetProductFolders(ref ProductTreeModel);

            // Save jstree nodes as Json in ViewBag.
            ViewBag.Json = JsonConvert.SerializeObject(ProductTreeModel);

            // Create product list by product category, represented by jstrees selected node id.
            List<Product> productList = new List<Product>();
            productList = _context.Products.Where(x => x.ParentFolderId == id).ToList();

            // The model for this controllers View is IEnumerable<Product> (to be used to populate the products table), so return the product list.
            return View(productList);
        }

        public void GetProductFolders(ref JsTreeModel treeModel)
        {
            List<ProductFolder> rootList = new List<ProductFolder>();
            rootList = _context.ProductFolders.Where(x => x.ParentFolderId == 0).ToList();

            PopulateTree(rootList[0].ProductFolderId, treeModel);
        }

        private void PopulateTree(int parentid, JsTreeModel treeModel)
        {
            List<ProductFolder> productFolders = new List<ProductFolder>();
            productFolders = _context.ProductFolders.Where(x => x.ParentFolderId == parentid).ToList();

            treeModel.children = new List<JsTreeModel>();

            for (int i = 0; i < productFolders.Count; i++)
            {
                JsTreeModel childNode = new JsTreeModel(productFolders[i].ProductFolderId, productFolders[i].ProductFolderName, productFolders[i].ProductFolderLevel);
                treeModel.children.Add(childNode);

                PopulateTree(productFolders[i].ProductFolderId, childNode);
            }
        }

        private int StringToInt(string value)
        {
            if (String.IsNullOrEmpty(value))
            {
                return -1;
            }

            int result = 0;
            bool b = int.TryParse(value, out result);

            if (b == false)
            {
                return -1;
            }

            return result;
        }

        [HttpGet]
        [Route("Products/Node/{id}")]
        public IActionResult Node(string id)
        {
            int nodeid = StringToInt(id);

            List<Product> productList = new List<Product>();
            productList = _context.Products.Where(x => x.ParentFolderId == nodeid).ToList();

            return View(productList);
        }
    }
}

而视图看起来是这样的:

@model IEnumerable<Product>
@{
    ViewData["Title"] = "Products";
}

<h2>Products</h2>

<div style="height: 363px">
    <div class="panel panel-default" style="width: 300px;height: 320px;float:left">
        <div class="panel-body">
            <div id="divProductTree">
                <div id="jsProductTree"></div>@* Product categories *@
            </div>
        </div>
    </div>
    <div style="width: 600px;height: 320px;float:left;margin-left: 10px">
        <table class="table">@* Products *@
            <thead>
                <tr>
                    <th>ProductName</th>
                    <th>DX</th>
                    <th>DY</th>
                    <th>DZ</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in Model)
                {
                    <tr>
                        <td>@item.ProductName</td>
                        <td>@item.DX</td>
                        <td>@item.DY</td>
                        <td>@item.DZ</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</div>

@section Scripts {
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $('#jsProductTree').jstree({
            'core': {
                'themes': {
                    'icons': false
                },
                'data': @Html.Raw(ViewBag.Json)/* Product categories data created in Controller*/
            },
            'plugins': ["defaults","changed","json_data","ui"],
            })
            $('#jsProductTree').on('changed.jstree', async function(e, data) {
                alert("(" + data.node.id + ") " + data.node.text);
                let url = `@Url.Action("Node", "Products")/${encodeURIComponent(data.node.id)}`;
                let response = await fetch(url);
                if (response.ok) {
                    // parses the JSON response from the server
                    let newTreeData = response.json();

                    // redraw the jstree
                }
                else {
                    console.error('Failed to load jstree');
                }
            });
        });
    </script>
}

我想做什么?

当左侧树状视图选中的节点改变时,我想更新右侧的 Products 表。

什么工作?

一切直到并包括以下内容都有效: 1.正确填充jstree。 2. onchanged.jstree 事件,它调用 Node(string id) 函数。脚本中的alert调用显示了正确的jstree节点ID和文本,且在 Node() 函数中的 int id 变量传递了正确的节点ID。但是,我完全不知道该函数需要返回什么、为什么要返回,以及jstree在返回时如何处理它。

什么不起作用?

我觉得更多的是我不理解的部分。我原本以为可以“只要”获取所选树状节点的ID,然后以某种方式把它传给控制器,使用该ID运行查询,创建我的产品列表,刷新页面,表格就会显示正确的产品。

显然没有那么简单,我也不知道从哪里着手才好。我想也许需要在jstree内部调用 GetProductFolders() 方法来填充jstree,以便把它与 Index() 方法断开连接,这样在刷新视图时就只更新表格。

我的主要问题是:节点改变时,如何刷新页面?

我知道我这要求有点多。我显然在困境中,也许十年前我会花时间学会游泳,但现在已经没有那段时间了,再说,就像荷马·辛普森一样,我担心再往脑子里塞东西,会有更重要的东西被挤出去。

顺便说一句,我看了很多jstree的示例,这里也有大量与jstree相关的问题,但它们都在讨论单独使用jstree;我还没找到关于将jstree与其他控件一起使用的问答或示例,这似乎有点奇怪。我猜你可以只用一个独立的树状视图作为目录编辑工具?

解决方案

请查看 Start here: FileBrowser demo 的处理方式,检查源码和开发者工具中的网络标签。

根据你的帖子,起初加载是正常的,现在你想刷新类别树视图并从该类别刷新产品。

伪代码

//刷新树视图自身

$('#jsProductTree').jstree({
            'core': {
                'themes': {
                    'icons': false
                },
                'data' : {
                            'url' : '?operation=get_node',
                            'data' : function (node) {
                                return { 'id' : node.id };
                            }
        },
            },
            'plugins': ["defaults","changed","json_data","ui"],
});

data.instance.refresh(); 这个调用将执行Ajax并刷新树,请查看 文档

//根据选中的节点刷新右侧视图

.on('changed.jstree', function (e, data) {
//load products for selected category
});

理想情况下,你可以把 php代码 迁移到asp.net

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章