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 数据,idITreeNode 接口的 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;
}