你的位置:首页 > ASP.net教程

[ASP.net教程]通过实例学习Fireasy开发(中篇)


      本文目录

      通过实例学习Fireasy开发(上篇)

      通过实例学习Fireasy开发(中篇)

      通过实例学习Fireasy开发(下篇)

      通过实例学习Fireasy开发(补充)

 

      上篇我们介绍了进行Fireasy开发的前期准备,接下来,我们将通过两个小功能来演示怎么进行业务开发。

 

      一、部门列表页面

      部门是树型结构,Fireasy采用了00010001这样的编码来支持树结构实体的快速开发。

      1、 修改Dept类(使用Dept_Ex分部类文件),在类名称上添加EntityTreeMappingAttribute特性,InnerSign指定存储00010001编码的属性,在Dept类里是使用No作为这一标识的。

  //如果要启用实体验证,请使用以下特性,并在 DeptMetadata 中定义验证特性。  [MetadataType(typeof(DeptMetadata))]   [EntityTreeMapping(InnerSign="No")]  public partial class Dept  {  }

      编码每一层默认使用4位编码,如果要改成2位,可以将SignLength属性设置为2。

      

      2、 修改Dept类,使它实现ITreeNode<>接口,目的是返回json的时候可以使用子节点等这样的属性,后面会介绍。

  //如果要启用实体验证,请使用以下特性,并在 DeptMetadata 中定义验证特性。  [MetadataType(typeof(DeptMetadata))]   [EntityTreeMapping(InnerSign="No")]  public partial class Dept: ITreeNode<Dept>  {    public System.Collections.Generic.List<Dept> Children { get; set; }    System.Collections.IList ITreeNode.Children    {      get { return Children; }      set { Children = (System.Collections.Generic.List<Dept>)value; }    }    public bool HasChildren { get; set; }    public bool IsLoaded { get; set; }  }

 

      3、 打开DeptList.aspx文件,将datagrid替换成treegrid,修改loadData函数如下:

    //加载数据    function loadData() {      $('#dg').treegrid({        url: 'DeptList.ajx/GetDepts',        fit: true,//自动填充        idField: 'id',        treeField: 'text',        columns: [[                    { field: 'text', title: '名称', width: 150, halign: 'center', align: 'left', sortable: true },          { field: 'no', title: '编码', width: 150, halign: 'center', align: 'left', sortable: true }        ]]      })    }

      treeField属性指定作为节点控制的字段,我们使用text作为其字段。

 

      4、treegrid的返回json是一个树结构,且展开子节点时是用id参数把当前节点的主键值传递过去的。在DeptList.aspx.cs文件中,修改GetDepts方法如下:

    /// <summary>    /// 根据查询条件获取部门。    /// </summary>    /// <param name="id">当前的父ID。</param>    /// <param name="keyword">关键字</param>    /// <returns></returns>    [ExceptionBehavior(true)]    public List<Dept> GetDepts(string id, string keyword)    {      //添加一个json转换器      ServiceContext.Current.Converters.Add(new DeptJsonConverter());      var result = new List<Dept>();      using (var context = new DbContext())      {        //查询出父部门        Dept parent = null;        if (!string.IsNullOrEmpty(id))        {          parent = context.Depts.Get(id);          if (parent == null)          {            return result;          }        }        //创建一个Dept的树实体持久化对象        var persister = context.CreateTreePersister<Dept>();        //查询出它的所有孩子        foreach (var child in persister.QueryChildren(parent).ToList())        {          child.Children = new List<Dept>();          //再判断一下这个孩子是否还有孩子          child.HasChildren = persister.HasChildren(child);          result.Add(child);        }        return result;      }    }

      在每展开一个节点的时候,treegrid都会来请求该方法,id即当前节点的主键值。Fireasy提供了EntityTreePersister这样一个类,可以方便的查询出下级孩子,插入节点,移动节点等。

      DeptJsonConverter类是一个json格式的转换类,我们查看其基类TreeNodeJsonConverter可得知,它实则是提供treegrid所需的json数据格式,如:      

{ id: "", text: "", children: [], state: "closed", attributes: { } }

      它的构造方法是传入一个字典类型,指定其他属性值,比如 text、no、attributes 等等。id不用指定,在基类里已经输出了,因为ITreeNode接口有一个Id属性的(如果实体类型的主键不是ID或不是string类型,要想办法实现它)。

    private class DeptJsonConverter : TreeNodeJsonConverter<Dept>    {      public DeptJsonConverter()        : base(new Dictionary<string, Func<Dept, object>>           {             { "text", s => s.Name },             { "no", s => s.No },            { "attributes", s => new { no = s.No } }          })      {      }    }

      好了,DeptList页面的修改就完成了,如果你现在迫不急待的想看到结果如何,可以在TB_Dept表里加入三条数据(注意NO的编码规则):

      浏览DeptList.aspx页面,如果人品较好的话,应该能看到树结构数据了:

 

      二、部门编辑页面

      DeptEdit.aspx看似简单,其实要处理的地方很多,0^0,我们需要让部门创建到某一个节点下,或是从某一节点移动到某一节点,这里有几个细节的地方必须得说明一下。

      首先,得介绍一下Html扩展。Fireasy提供了类似MVC的HTML扩展标记,并由Fireasy.EasyUI组件提供了丰富的easyui扩展,比如TextBox、ComboBox、ComboTree、DateBox、NumberBox等等,具体可以查看Fireasy.EasyUI组件中的类。

      那么,在DeptEdit.aspx(以后的所有Edit页面都会存在)中,看这一段代码:

  <% var Html = new HtmlHelper<WebApplication.Data.Model.Dept>(); %>  <div data-options="region:'center',border:false">    <table class="form-body">            <tr>        <td class="addon">ID</td>        <td><%= Html.TextBox(s => s.Id) %></td>      </tr>      <tr>        <td class="addon">编码</td>        <td><%= Html.TextBox(s => s.No) %></td>      </tr>      <tr>        <td class="addon">名称</td>        <td><%= Html.TextBox(s => s.Name) %></td>      </tr>      <tr>        <td class="addon">排序</td>        <td><%= Html.TextBox(s => s.OrderNo) %></td>      </tr>    </table>  </div>

      第一行定义了一个Html变量,它是基于实体类型的HTML扩展,在使用EasyUI扩展的时候,就可以把相应的属性引用进来了。使用基于实体类型的HTML扩展还有一个好处,实体的验证规则,如必填、长度、小数位数等,都是通过实体类型的*Metadata类来定义的,可以看*_EX.cs文件。

      DeptEdit页面需要增加一个域,用来选择上级单位。随便把No删掉,把排序改成NumberBox。

  <% var Html = new HtmlHelper<WebApplication.Data.Model.Dept>(); %>  <div data-options="region:'center',border:false">    <table class="form-body">      <tr>        <td class="addon">上级部门</td>        <td><%= Html.ComboTree("cboParent").MarkNoClear() %></td>      </tr>      <tr>        <td class="addon">名称</td>        <td><%= Html.TextBox(s => s.Name) %></td>      </tr>      <tr>        <td class="addon">排序</td>        <td><%= Html.NumberBox(s => s.OrderNo) %></td>      </tr>    </table>  </div>

      修改js代码,加入一个loadDepts函数,沿用DeptList.aspx.cs中的方法,只是需要增加两个参数,targetId和currentId,targetId是combobox中选中的节点,即当前部门的父部门,currentId是当前修改的部门ID。GetDepts方法的修改呆会再介绍。

    var pid = '<%= Request.QueryString["parentId"] %>';    //加载模块    function loadDepts() {      $('#cboParent').combotree({        url: "DeptList.ajx/GetDepts?targetId=" + pid + '&currentId=' + id,        panelWidth: 260,        onLoadSuccess: function () {          $('#cboParent').combotree('setValue', pid);        }      })    }

      pid在添加部门的时候是从DeptList.aspx,修改DeptList.aspx的addInfo函数如下:

    //添加信息    function addInfo() {      var row = $('#dg').treegrid('getSelected');      var parentId = row == null ? '' : row.id;      common.showDialog('DeptEdit.aspx?parentId=' + parentId, '部门', 500, 300, function () {        $('#dg').treegrid('reload');      });    }

      返回到DeptEdit.aspx,修改loadInfo函数,在新增和修改场景下加入loadDepts函数:

    //加载信息    function loadInfo() {      if (id != '') {        $.getJSON('DeptEdit.ajx/GetDept?id=' + id, function (data) {          common.processResult(data, function () {            $('#form1').form('load', data);            loadDepts();          });        });        $('#btnSaveAndNew').remove();      }      else {        loadDepts();      }    }

      你可能注意到了,在修改部门的时候,pid并没有从DeptList.aspx中传入,所以修改部门的时候pid为空,loadDepts将读到所有的数据,它不会选定当前部门的父节点。因此,我们需要对GetDept方法做一些修改,目的是能返回当前部门的上级部门ID:

    /// <summary>    /// 根据ID获取部门。    /// </summary>    /// <param name="id">信息ID。</param>    /// <returns></returns>    public object GetDept(string id)    {      using (var context = new DbContext())      {        var info = context.Depts.Get(id);        if (info != null)        {          //创建一个Dept的树实体持久化对象          var persister = context.CreateTreePersister<Dept>();          //找以它的上一个模块          var parentId = persister.RecurrenceParent(info).FirstOrDefault().AssertNotNull(s => s.Id);          return info.Extend(new { ParentId = parentId });        }        return null;      }    }

      好了,返回到DeptEdit.aspx页面中,修改loadInfo函数:

    //加载信息    function loadInfo() {      if (id != '') {        $.getJSON('DeptEdit.ajx/GetDept?id=' + id, function (data) {          common.processResult(data, function () {            $('#form1').form('load', data);            if (data.ParentId != undefined) {              pid = data.ParentId;            }            loadDepts();          });        });        $('#btnSaveAndNew').remove();      }      else {        loadDepts();      }    }

      现在,不要忘了DeptList.aspx.cs中的GetDepts方法,刚刚我们增加了两个参数targetId和currentId。

      targetId是目标节点,它可能处于某一层下,我们需要在返回的json数据中,逐级的把这些节点加载出来,这样,页面那边才选得中这个节的。

      currentId是当前节点,目的是在修改部门的时候,不能让它选择到它下面的节点,不然移动节点将发生异常。

    /// <summary>    /// 根据查询条件获取部门。    /// </summary>    /// <param name="id">当前的父ID。</param>    /// <param name="targetId">目标节点,返回的json中要能看到它。</param>    /// <param name="currentId">当前部门的节点,不要列出它的孩子。</param>    /// <param name="keyword">关键字</param>    /// <returns></returns>    [ExceptionBehavior(true)]    public List<Dept> GetDepts(string id, string targetId, string currentId, string keyword)    {      //添加一个json转换器      ServiceContext.Current.Converters.Add(new DeptJsonConverter());      var result = new List<Dept>();      using (var context = new DbContext())      {        //查询出父部门        Dept parent = null;        if (!string.IsNullOrEmpty(id))        {          parent = context.Depts.Get(id);          if (parent == null)          {            return result;          }        }        //创建一个Dept的树实体持久化对象        var persister = context.CreateTreePersister<Dept>();        foreach (var child in persister.QueryChildren(parent).ToList())        {          //排除当前模块          if (currentId == child.Id)          {            continue;          }          child.Children = new List<Dept>();          child.HasChildren = persister.HasChildren(child);          result.Add(child);        }        //如果要显示到子模块        if (!string.IsNullOrEmpty(targetId))        {          var target = context.Depts.Get(targetId);          if (target != null)          {            //得到父级ID列表 0:父 1:祖父 2: 曾祖父 .....            var parents = persister.RecurrenceParent(target).Select(s => s.Id).ToList();            //递归展开一下级            result.Expand(parents, (childId) => GetDepts(childId, targetId, currentId, keyword), parents.Count - 1, currentId);          }        }        return result;      }    }

      RecurrenceParent得到目标节点的父列表,那么我们从根祖父这一级一直往下递归展开。Expand是一个扩展方法。

      现在,我们来看看改造后的DeptEdit.aspx页面的运行效果。我们在DeptList.aspx中选择一个子节点办公室,点击添加按钮。

      弹出的DeptEdit.aspx中,上级部门自动填充到了办公室,并且选定了它。

 

      最后,我们完成部门保存的代码调整。

      在DeptEdit.aspx中修改saveInfo函数,增加一个parentId参数。

    //保存信息    function saveInfo(isNew) {      if (!$('#form1').form('validate')) {        return;      }      var postData = new Object();      //将表单填充的内容序列化为json      var data = $('#form1').form('save');      postData["info"] = JSON.stringify(data);      var parentId = $('#cboParent').combotree('getValue');      common.showProcess();      $.post('DeptEdit.ajx/SaveDept?parentId=' + parentId + '&id=' + id, postData, function (result) {        common.processResult(result, function () {          if (isNew) {            $('#form1').form('clear');          }          id = isNew ? '' : result.data;          common.setReturnValue(true);        });      });    }

      相应的,DeptEdit.aspx.cs文件中的SaveDept方法也要作调整如下:

    /// <summary>    /// 保存部门。    /// </summary>    /// <param name="id">id。</param>    /// <param name="parentId">父部门ID。</param>    /// <param name="info">要保存的数据。</param>    /// <returns>id</returns>    public object SaveDept(string id, string parentId, Dept info)    {      using (var context = new DbContext())      {        var persister = context.CreateTreePersister<Dept>();        var parent = persister.First(parentId);        if (string.IsNullOrEmpty(id))        {          info.Id = Guid.NewGuid().ToString();          if (parent == null)          {            persister.Create(info);          }          else          {            persister.Insert(info, parent, EntityTreePosition.Children);          }        }        else        {          persister.Update(info, s => s.Id == id);          //需要取出NO,不然无法移动          info.No = persister.First(id).No;          persister.Move(info, parent, EntityTreePosition.Children);        }        return Result.Success("保存成功。", info.Id);      }    }

      对了,DeptList.aspx中,editInfo和deleteInfo函数要把row.Id改成row.id。

 

      至此,部门管理的功能就完成了,由于篇幅太长(预想中没有这么长),员工管理将在下篇中介绍。