1. 项目概述:jEasyUI树形网格开发实战
树形结构数据展示一直是企业级应用开发中的高频需求。最近在重构一个供应链管理系统时,我遇到了一个典型场景:需要同时展示产品分类的层级关系(树形结构)和每个分类下的产品明细(表格数据)。这种"树+表"的复合视图如果采用传统方式开发,至少需要200行以上的jQuery DOM操作代码,而使用jEasyUI的树形网格(TreeGrid)组件,最终实现只用了不到50行代码。
jEasyUI作为基于jQuery的UI组件库,其TreeGrid组件完美解决了这类复杂数据展示需求。它本质上是一个可以展开/折叠节点的特殊DataGrid,支持异步加载、多级嵌套、行列冻结等企业级功能。在最新1.9.x版本中,更增加了对拖拽排序、单元格编辑等实用特性的支持。
2. 核心设计解析
2.1 数据结构设计
TreeGrid要求的数据格式是标准的JSON数组,每个节点对象必须包含:
- id: 唯一标识符
- text: 节点显示文本
- children: 子节点数组(可选)
- 其他自定义字段(如iconCls、state等)
javascript复制[{
"id": "1",
"text": "电子产品",
"price": "",
"stock": "",
"children": [{
"id": "11",
"text": "手机",
"price": "3999",
"stock": "120"
}]
}]
关键点:父节点通常包含空值字段,这些字段只在叶子节点有实际数据。这是树形网格与普通表格的核心区别。
2.2 组件初始化配置
基础配置示例:
javascript复制$('#treegrid').treegrid({
url: 'get_data.php',
idField: 'id',
treeField: 'text',
columns: [[
{field: 'text', title: '分类名称', width: 200},
{field: 'price', title: '单价', width: 100},
{field: 'stock', title: '库存', width: 100}
]]
});
必须参数说明:
idField: 节点ID字段名(对应数据中的id)treeField: 作为树形显示的字段(通常是text)columns: 表格列配置,与DataGrid格式一致
2.3 异步加载实现
对于大数据量场景,需要分步加载节点数据:
javascript复制$('#treegrid').treegrid({
// ...其他配置
onBeforeExpand: function(node){
$(this).treegrid('options').url = 'get_children.php?id='+node.id;
return true;
}
});
当用户点击展开图标时,会向服务端请求该节点的子数据。服务端应返回标准JSON数组。
3. 高级功能实现
3.1 多级表头支持
复杂业务场景常需要多级表头:
javascript复制columns: [[
{title: '基础信息', colspan: 2},
{title: '销售数据', colspan: 3}
],[
{field: 'text', title: '名称', width: 100},
{field: 'code', title: '编码', width: 80},
{field: 'price', title: '单价', width: 80},
{field: 'discount', title: '折扣', width: 80},
{field: 'total', title: '总价', width: 80}
]]
3.2 单元格编辑功能
启用编辑需要配置editor属性:
javascript复制{field: 'stock', title: '库存', width: 80, editor: {
type: 'numberbox',
options: {
min: 0,
precision: 0
}
}}
编辑完成后,可通过getChanges方法获取修改数据:
javascript复制var edits = $('#treegrid').treegrid('getChanges');
$.post('save.php', {edits: JSON.stringify(edits)});
3.3 自定义节点图标
通过iconCls字段指定CSS类:
css复制.icon-category {
background: url('images/folder.png') no-repeat center;
}
.icon-product {
background: url('images/product.png') no-repeat center;
}
数据中指定:
javascript复制{
"id": "1",
"text": "电子产品",
"iconCls": "icon-category"
}
4. 性能优化技巧
4.1 虚拟滚动技术
对于超大数据量(>1000节点),启用virtualScroll:
javascript复制$('#treegrid').treegrid({
// ...其他配置
rownumbers: true,
scrollbarSize: 0,
onBeforeLoad: function(){
$(this).treegrid('options').pageSize = 50;
}
});
4.2 数据缓存策略
前端缓存已加载节点:
javascript复制var nodeCache = {};
function loadNode(id, callback) {
if(nodeCache[id]) {
callback(nodeCache[id]);
} else {
$.get('get_node.php?id='+id, function(data){
nodeCache[id] = data;
callback(data);
});
}
}
4.3 批量操作优化
展开所有节点时的性能陷阱:
javascript复制// 错误做法:递归展开所有节点
function expandAll() {
var roots = $('#treegrid').treegrid('getRoots');
$.each(roots, function(){
$('#treegrid').treegrid('expand', this.id);
});
}
// 正确做法:分批展开
function expandAll() {
var roots = $('#treegrid').treegrid('getRoots');
var i = 0;
var batchSize = 10;
function doBatch() {
var end = Math.min(i + batchSize, roots.length);
for(; i < end; i++) {
$('#treegrid').treegrid('expand', roots[i].id);
}
if(i < roots.length) {
setTimeout(doBatch, 100);
}
}
doBatch();
}
5. 常见问题解决方案
5.1 节点加载异常
症状:点击展开图标无反应
排查步骤:
- 检查浏览器控制台是否有JS错误
- 使用Fiddler/Charles查看请求是否发出
- 验证服务端返回数据格式是否符合要求
5.2 编辑状态丢失
典型场景:分页后编辑内容消失
解决方案:
javascript复制// 在分页前保存编辑
$('#treegrid').treegrid({
onBeforePaging: function(){
var edits = $(this).treegrid('getChanges');
if(edits.length) {
return confirm('有未保存的修改,继续将丢失更改');
}
}
});
5.3 样式冲突处理
当与其他UI框架共存时,可能出现样式问题:
css复制/* 重置关键样式 */
.treegrid .datagrid-cell {
padding: 6px 4px !important;
}
.treegrid-hit {
background-position: -144px -72px !important;
}
6. 实战案例:产品管理系统
完整实现代码结构:
html复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>产品分类管理</title>
<link rel="stylesheet" type="text/css" href="jquery-easyui/themes/default/easyui.css">
<style>
.toolbar {
padding: 5px;
background: #fafafa;
}
.icon-save {
background: url('images/save.png') no-repeat center;
}
</style>
</head>
<body>
<div class="easyui-layout" fit="true">
<div data-options="region:'center',border:false">
<div id="toolbar" class="toolbar">
<a href="#" class="easyui-linkbutton" iconCls="icon-add" plain="true">新增</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-save" plain="true" onclick="saveChanges()">保存</a>
</div>
<table id="productGrid"></table>
</div>
</div>
<script src="jquery-easyui/jquery.min.js"></script>
<script src="jquery-easyui/jquery.easyui.min.js"></script>
<script>
$(function(){
$('#productGrid').treegrid({
url: 'api/products',
toolbar: '#toolbar',
idField: 'id',
treeField: 'name',
columns: [[
{field: 'name', title: '产品名称', width: 200},
{field: 'price', title: '价格', width: 100, editor: 'numberbox'},
{field: 'stock', title: '库存', width: 100, editor: {
type: 'numberbox',
options: {min: 0}
}}
]],
onDblClickRow: function(row){
$(this).treegrid('beginEdit', row.id);
},
onAfterEdit: function(row, changes){
$.post('api/save_product', row, function(resp){
$.messager.alert('提示', resp.msg);
});
}
});
});
function saveChanges() {
var edits = $('#productGrid').treegrid('getChanges');
if(edits.length) {
$.post('api/batch_save', {data: edits}, function(resp){
$('#productGrid').treegrid('reload');
});
}
}
</script>
</body>
</html>
服务端处理示例(PHP):
php复制// get_data.php
header('Content-Type: application/json');
$data = [
[
'id' => '1',
'name' => '电子产品',
'price' => '',
'stock' => '',
'children' => [
[
'id' => '11',
'name' => '智能手机',
'price' => '3999',
'stock' => '50',
'state' => 'open'
]
]
]
];
echo json_encode($data);
7. 扩展思考:与Vue/React集成
虽然jEasyUI是jQuery时代的产物,但在老系统维护中仍有价值。现代开发中可以通过以下方式整合:
7.1 封装为Vue组件
javascript复制Vue.component('tree-grid', {
template: '<div ref="container"></div>',
props: ['url', 'columns'],
mounted() {
$(this.$refs.container).treegrid({
url: this.url,
columns: [this.columns],
onSelect: (row) => this.$emit('select', row)
});
},
methods: {
reload() {
$(this.$refs.container).treegrid('reload');
}
}
});
7.2 响应式数据绑定
javascript复制// 监听数据变化自动更新
watch: {
treeData(newVal) {
$('#treegrid').treegrid('loadData', newVal);
}
}
在实际项目中使用jEasyUI TreeGrid时,我总结出几个黄金法则:
- 始终在onBeforeExpand中动态设置URL,避免一次性加载全部数据
- 对于可编辑表格,一定要实现脏数据检查机制
- 超过500个节点时,必须启用虚拟滚动或分页加载
- 复杂业务逻辑应该放在服务端,前端只负责展示和简单验证
