基于 jstree 实现工序的主子分节点层级展示
一、项目整体结构ASP.NET Core Razor PagesplaintextProcessKanban/ ├── Pages/ │ ├── ProcessKanban/ │ │ ├── Index.cshtml // 主页面工序看板树状图拖拽 │ │ ├── Index.cshtml.cs // 页面后台逻辑 │ ├── Shared/ │ │ ├── _Layout.cshtml // 布局页引入样式/脚本 ├── Data/ │ ├── ProcessDb.json // 本地JSON文件数据库 ├── wwwroot/ │ ├── css/ │ │ ├── process-kanban.css // 自定义样式 │ ├── js/ │ │ ├── process-kanban.js // 拖拽树状图逻辑 ├── Program.cs ├── appsettings.json二、核心文件实现1. 数据模型 本地 JSON 数据库Data/ProcessDb.json10 组工序数据含主子分节点json[ { Id: 1, Name: 总装工序, ParentId: 0, Level: 1, Sort: 1, Children: [ { Id: 11, Name: 机械装配, ParentId: 1, Level: 2, Sort: 1, Children: [ { Id: 111, Name: 螺栓紧固, ParentId: 11, Level: 3, Sort: 1 }, { Id: 112, Name: 轴承安装, ParentId: 11, Level: 3, Sort: 2 } ] }, { Id: 12, Name: 电气装配, ParentId: 1, Level: 2, Sort: 2, Children: [ { Id: 121, Name: 接线端子压接, ParentId: 12, Level: 3, Sort: 1 }, { Id: 122, Name: 线缆布线, ParentId: 12, Level: 3, Sort: 2 } ] } ] }, { Id: 2, Name: 涂装工序, ParentId: 0, Level: 1, Sort: 2, Children: [ { Id: 21, Name: 表面处理, ParentId: 2, Level: 2, Sort: 1, Children: [ { Id: 211, Name: 喷砂除锈, ParentId: 21, Level: 3, Sort: 1 }, { Id: 212, Name: 脱脂清洗, ParentId: 21, Level: 3, Sort: 2 } ] }, { Id: 22, Name: 喷漆固化, ParentId: 2, Level: 2, Sort: 2, Children: [ { Id: 221, Name: 底漆喷涂, ParentId: 22, Level: 3, Sort: 1 }, { Id: 222, Name: 高温固化, ParentId: 22, Level: 3, Sort: 2 } ] } ] }, { Id: 3, Name: 焊接工序, ParentId: 0, Level: 1, Sort: 3, Children: [{ Id: 31, Name: 氩弧焊, ParentId: 3, Level: 2, Sort: 1, Children: [] }] }, { Id: 4, Name: 机加工工序, ParentId: 0, Level: 1, Sort: 4, Children: [{ Id: 41, Name: 车削, ParentId: 4, Level: 2, Sort: 1, Children: [{ Id: 411, Name: 外圆车削, ParentId: 41, Level: 3, Sort: 1 }] }] }, { Id: 5, Name: 检验工序, ParentId: 0, Level: 1, Sort: 5, Children: [{ Id: 51, Name: 尺寸检测, ParentId: 5, Level: 2, Sort: 1, Children: [{ Id: 511, Name: 三坐标测量, ParentId: 51, Level: 3, Sort: 1 }] }] }, { Id: 6, Name: 热处理工序, ParentId: 0, Level: 1, Sort: 6, Children: [{ Id: 61, Name: 淬火, ParentId: 6, Level: 2, Sort: 1, Children: [] }] }, { Id: 7, Name: 装配前准备, ParentId: 0, Level: 1, Sort: 7, Children: [{ Id: 71, Name: 物料清点, ParentId: 7, Level: 2, Sort: 1, Children: [] }] }, { Id: 8, Name: 包装工序, ParentId: 0, Level: 1, Sort: 8, Children: [{ Id: 81, Name: 缓冲包装, ParentId: 8, Level: 2, Sort: 1, Children: [] }] }, { Id: 9, Name: 仓储工序, ParentId: 0, Level: 1, Sort: 9, Children: [{ Id: 91, Name: 成品入库, ParentId: 9, Level: 2, Sort: 1, Children: [] }] }, { Id: 10, Name: 物流工序, ParentId: 0, Level: 1, Sort: 10, Children: [{ Id: 101, Name: 厂区转运, ParentId: 10, Level: 2, Sort: 1, Children: [] }] } ]ProcessModel.cs页面模型文件夹下新增 Models/ProcessModel.cscsharp运行namespace ProcessKanban.Models { public class ProcessNode { public int Id { get; set; } public string Name { get; set; } string.Empty; public int ParentId { get; set; } public int Level { get; set; } public int Sort { get; set; } public ListProcessNode Children { get; set; } new ListProcessNode(); } }2. 页面后台逻辑Index.cshtml.cscsharp运行using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using ProcessKanban.Models; using System.Text.Json; namespace ProcessKanban.Pages.ProcessKanban { public class IndexModel : PageModel { private readonly IWebHostEnvironment _webHostEnv; public ListProcessNode ProcessNodes { get; set; } new ListProcessNode(); public IndexModel(IWebHostEnvironment webHostEnv) { _webHostEnv webHostEnv; } public IActionResult OnGet() { // 读取本地JSON数据库 var jsonPath Path.Combine(_webHostEnv.ContentRootPath, Data, ProcessDb.json); if (System.IO.File.Exists(jsonPath)) { var jsonContent System.IO.File.ReadAllText(jsonPath); ProcessNodes JsonSerializer.DeserializeListProcessNode(jsonContent) ?? new ListProcessNode(); } return Page(); } // 处理拖拽后的排序更新 public IActionResult OnPostUpdateSort(ListProcessNode updatedNodes) { try { var jsonPath Path.Combine(_webHostEnv.ContentRootPath, Data, ProcessDb.json); var jsonContent JsonSerializer.Serialize(updatedNodes, new JsonSerializerOptions { WriteIndented true }); System.IO.File.WriteAllText(jsonPath, jsonContent); return new JsonResult(new { Success true, Message 排序更新成功 }); } catch (Exception ex) { return new JsonResult(new { Success false, Message ex.Message }); } } } }3. 主页面Index.cshtmlhtml预览page model ProcessKanban.Pages.ProcessKanban.IndexModel { ViewData[Title] 工序看板; } div classprocess-container !-- 工序树状图区域 -- div classprocess-tree h3工序路径树状图/h3 div idtree-container/div /div !-- 拖拽式工序看板区域 -- div classprocess-kanban h3工序看板拖拽排序/h3 div idkanban-container classkanban-list foreach (var node in Model.ProcessNodes) { div classkanban-item>4. 样式文件process-kanban.csscss.process-container { display: flex; gap: 20px; padding: 20px; } .process-tree { width: 35%; border: 1px solid #e0e0e0; padding: 15px; border-radius: 8px; background-color: #f9f9f9; } .process-kanban { width: 65%; border: 1px solid #e0e0e0; padding: 15px; border-radius: 8px; background-color: #f9f9f9; } .kanban-list { min-height: 500px; padding: 10px; border: 1px dashed #ccc; border-radius: 4px; } .kanban-item { background-color: white; border: 1px solid #2196F3; border-radius: 6px; padding: 10px; margin-bottom: 10px; cursor: move; } .kanban-subitem { background-color: #f0f8ff; border: 1px solid #64B5F6; border-radius: 4px; padding: 8px; margin: 5px 0 5px 20px; cursor: move; } .kanban-granditem { background-color: #e3f2fd; border: 1px solid #90CAF9; border-radius: 4px; padding: 6px; margin: 3px 0 3px 40px; cursor: move; } .item-header { font-weight: bold; margin-bottom: 5px; } .btn-save { margin-top: 15px; padding: 8px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .btn-save:hover { background-color: #45a049; } /* 拖拽样式 */ .ui-draggable-dragging { opacity: 0.8; box-shadow: 0 2px 8px rgba(0,0,0,0.2); } .ui-sortable-placeholder { height: 60px; background-color: #f0f0f0; border: 1px dashed #888; margin-bottom: 10px; }5. 前端交互脚本process-kanban.jsjavascript运行$(document).ready(function () { // 1. 初始化树状图 initProcessTree(); // 2. 初始化拖拽排序 initDragSort(); // 3. 保存排序按钮事件 $(#save-sort).click(saveSort); }); // 初始化工序树状图 function initProcessTree() { // 获取后台传递的工序数据 var processData Json.Serialize(Model.ProcessNodes); // 转换为jstree格式 var treeNodes convertToJsTreeFormat(processData); // 初始化jstree $(#tree-container).jstree({ core: { data: treeNodes, check_callback: true, themes: { stripes: true } }, plugins: [contextmenu, dnd, state], contextmenu: { items: { rename: { label: 重命名, action: function (obj) { this.rename(obj); } } } } }); } // 转换数据为jstree格式 function convertToJsTreeFormat(nodes) { return nodes.map(node { return { id: node.Id, text: node.Name, children: node.Children.length 0 ? convertToJsTreeFormat(node.Children) : false, data: { level: node.Level, parentId: node.ParentId, sort: node.Sort } }; }); } // 初始化拖拽排序 function initDragSort() { // 一级节点拖拽 $(.kanban-list).sortable({ items: .kanban-item, placeholder: ui-sortable-placeholder, cursor: move, update: function (event, ui) { // 拖拽后更新排序前端临时 updateItemSort(); } }); // 二级节点拖拽 $(.item-children).sortable({ items: .kanban-subitem, placeholder: ui-sortable-placeholder, cursor: move, update: function (event, ui) { updateSubItemSort($(this).parent().data(id)); } }); // 三级节点拖拽 $(.item-grandchildren).sortable({ items: .kanban-granditem, placeholder: ui-sortable-placeholder, cursor: move }); } // 更新一级节点排序 function updateItemSort() { $(.kanban-item).each(function (index) { $(this).data(sort, index 1); }); } // 更新二级节点排序 function updateSubItemSort(parentId) { $(.kanban-item[data-id${parentId}] .kanban-subitem).each(function (index) { $(this).data(sort, index 1); }); } // 保存排序到本地JSON function saveSort() { // 构建更新后的数据结构 var updatedNodes []; $(.kanban-item).each(function () { var item $(this); var node { Id: item.data(id), Name: item.find(.item-header).text().split(()[0].trim(), ParentId: 0, Level: item.data(level), Sort: item.data(sort) || $(this).index() 1, Children: [] }; // 处理二级节点 item.find(.kanban-subitem).each(function () { var subItem $(this); var subNode { Id: subItem.data(id), Name: subItem.text().trim().split(\n)[0], ParentId: node.Id, Level: 2, Sort: subItem.data(sort) || $(this).index() 1, Children: [] }; // 处理三级节点 subItem.find(.kanban-granditem).each(function () { var grandItem $(this); subNode.Children.push({ Id: grandItem.data(id), Name: grandItem.text().trim(), ParentId: subNode.Id, Level: 3, Sort: $(this).index() 1, Children: [] }); }); node.Children.push(subNode); }); updatedNodes.push(node); }); // 提交到后台保存 $.ajax({ url: /ProcessKanban/Index?handlerUpdateSort, type: POST, contentType: application/json, data: JSON.stringify(updatedNodes), headers: { RequestVerificationToken: $(input[name__RequestVerificationToken]).val() }, success: function (res) { if (res.Success) { alert(排序保存成功); } else { alert(保存失败 res.Message); } }, error: function (err) { alert(请求异常 err.statusText); } }); }6. 布局页_Layout.cshtml关键引入html预览!DOCTYPE html html langzh-CN head meta charsetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleViewData[Title] - 工序看板系统/title link relstylesheet href~/css/process-kanban.css / /head body header h1工序管理看板系统/h1 nav a asp-page/ProcessKanban/Index工序看板/a /nav /header main classcontainer RenderBody() /main RenderSection(Scripts, required: false) /body /html7. Program.cs 配置csharp运行var builder WebApplication.CreateBuilder(args); // 添加Razor Pages支持 builder.Services.AddRazorPages(); // 注入IWebHostEnvironment用于读取本地文件 builder.Services.AddSingletonIWebHostEnvironment(builder.Environment); var app builder.Build(); // 中间件配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(/Error); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); app.Run();三、功能说明树状图展示基于 jstree 实现工序的主子分节点层级展示支持节点右键重命名、拖拽调整层级拖拽看板一级工序节点总装 / 涂装等支持拖拽排序二级 / 三级子节点也支持独立拖拽排序拖拽后点击「保存排序」可将最新排序更新到本地 JSON 数据库本地文件数据库使用 JSON 文件存储 10 组工序数据无需依赖 SQL Server 等数据库轻量化部署ASP.NET Core Razor Pages采用页面模型分离CSHTMLCSHTML.CS符合 Razor Pages 最佳实践。四、部署与运行创建ASP.NET Core Razor Pages 项目.NET 6/7/8 均可按上述目录结构创建文件并粘贴代码确保 Data 文件夹下有 ProcessDb.json 文件启动项目访问/ProcessKanban路径即可看到功能页面。五、扩展建议增加节点增删改查功能加入数据验证如工序名称重复、ID 唯一优化拖拽交互如跨层级拖拽增加数据导出 / 导入功能接入 SQLite/EF Core 替代纯 JSON 文件适合数据量较大场景。