Tree 的 Json 序列化
Tree 或 TreeGrid 控件所接收的数据是一个树型的 Json
,格式如下:
[{
"id": 1,
"text": "Node 1",
"state": "closed",
"children": [{
"id": 11,
"text": "Node 11"
}, {
"id": 12,
"text": "Node 12"
}]
}, {
"id": 2,
"text": "Node 2",
"state": "closed"
}]
Fireasy 使用 ITreeNode
接口配合 JsonConverter
转换器,就能轻松地向 Tree 或 TreeGrid 提供数据。
在需要返回的实体类上实现 ITreeNode
接口,并实现其主要属性,如下:
public class Org : ITreeNode<Org>
{
#region 实现 ITreeNode
public List<Org> Children { get; set; }
IList ITreeNode.Children
{
get
{
return Children;
}
set
{
Children = (List<Org>)value;
}
}
public bool HasChildren { get; set; }
object ITreeNode.Id => OrgID;
public bool IsLoaded { get; set; }
#endregion
/// <summary>
/// 获取或设置机构ID。
/// </summary>
[PropertyMapping(ColumnName = "OrgID", Description = "机构ID", GenerateType = IdentityGenerateType.AutoIncrement, IsPrimaryKey = true, IsNullable = false)]
public virtual int OrgID { get; set; }
/// <summary>
/// 获取或设置编码。
/// </summary>
[PropertyMapping(ColumnName = "Code", Description = "编码", Length = 50, IsNullable = true)]
public virtual string Code { get; set; }
/// <summary>
/// 获取或设置名称。
/// </summary>
[PropertyMapping(ColumnName = "Name", Description = "名称", Length = 100, IsNullable = true)]
public virtual string Name { get; set; }
/// <summary>
/// 获取或设置属性。
/// </summary>
[PropertyMapping(ColumnName = "Attribute", Description = "属性")]
public virtual int Attribute { get; set; }
}
在返回对象时使用 DynamicTreeNodeJsonConverter
转换器,如下:
public class OrgController : Controller
{
private IAdminService _adminService;
public OrgController(IAdminService adminService)
{
_adminService = adminService;
}
public async Task<IActionResult> GetOrgs([FromServices] JsonSerializeOptionHosting hosting, int? id, int? targetId, int? currentId)
{
var converter = new DynamicTreeNodeJsonConverter<Org>(s => s.Name, s => s.Code, s => s.Attribute);
hosting.Option.Converters.Add(converter);
var orgs = await _adminService.GetOrgsAsync(id, targetId, currentId);
return Json(orgs);
}
}
DynamicTreeNodeJsonConverter
类转换后的 Json
数据,id
由 ITreeNode
接口的 Id
属性来指定,而 text
则由构造函数中的第一个参数 textExp
来指定,attExps
是附加的其他属性。以上示例输出的 Json
示例如下:
[{
"id": 1,
"text": "A公司",
"Code": "01",
"Attribute": 1,
"state": "closed",
"children": [{
"id": 3,
"text": "财务部",
"Code": "0101",
"Attribute": 2
}, {
"id": 4,
"text": "行政部",
"Code": "0102",
"Attribute": 2
}]
}, {
"id": 2,
"text": "B公司",
"Code": "02",
"Attribute": 1,
"state": "closed"
}]
Tree 和 TreeGrid 一般是懒加载的,即不会一次性加载整棵树,而是点击图标 + 时才请求接口获取子级节点数据。因此,接口方法中一般会有 id 参数(easyui默认使用此参数加载子节点)。
在 Fireasy 的最佳实践中,会用到 targetId 和 currentId 两个参数,下面分别对这两个参数进行说明:
- targetId: 当 targetId 不为空时,程序会一直递归到该节点,一般在编辑界面的下拉树控件会很有用。
- currentId: 当 currentId 不为空时,此节点及其子节点不会被加载出来,从而在修改父节点时,保证不会选到自己或子节点。
以下是 GetOrgs 方法的实现过程:
public async Task<List<Org>> GetOrgsAsync(int? parentId, int? targetId, int? currentId, StateFlags? state)
{
Org parent = null;
if (parentId != null)
{
parent = await _context.Orgs.FirstOrDefaultAsync(s => s.OrgID == parentId);
}
var treeOper = _context.CreateTreeRepository<Org>();
var result = await treeOper.QueryChildren(parent)
//如果指定currentId,则需要排除
.AssertWhere(currentId != null, s => s.OrgID != currentId)
.AssertWhere(state != null, s => s.State == state)
.OrderBy(s => s.OrderNo)
//把HasChildren属性扩展出来
.Select(s => s.ExtendAs<Org>(() => new Org
{
HasChildren = treeOper.HasChildren(s, null)
}))
.ToListAsync();
//查找目标节点
if (targetId != null && !TreeNodeExpandChecker.IsExpanded())
{
var target = await _context.Orgs.GetAsync(targetId);
//向上递归所有父节点
var parents = await treeOper.RecurrenceParent(target).Select(s => s.OrgID).ToListAsync();
//向下递归加载子节点
await result.ExpandAsync(parents, async childId => await GetOrgsAsync(childId, targetId, currentId, state), parents.Count - 1);
}
return result;
}