【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)

废话写在最前面

做angular开发已经有很长一段时间了,它的优势已经不用赘述了,尤其是双向绑定和高度模块化,真是装逼利器,甚是好用...

至于KendoUi,接触时间不长,名字看上去是UI框架,但个人觉得更偏js。特别是自定义控件和Grid,是在客户端解析,节约了不少的内存和数据交互的开销。

两大框架各有所长,但稍有不慎就会陷入无尽的苦恼。特别是在没有装js开发插件的IDE中,没有任何错误提示,只能通过浏览器的调式一步一步修改。

做这个小例子,我就是这么过来的。做的时间不长,很多地方值得商榷,抽这个时间把整个过程详细记录下来,仅为以后的开发以此为参考和教训吧...

这里综合做了一个例子:包括Grid的显示、导出Excel、Echart查看走势图...

敲黑板了

先放个最后的效果图,就只做了一个Grid报表而已,【大神可以出门溜达了】很简单...真的很简单的功能...

导出Excel没做,结合NPOI后续再更新吧。

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第1张图片

用到的另外两个框架:

H-UI、BootStrap

主要是样式的优化,H-UI还是咱国人自己写的呢...

后台

1.构建model

1.1用户实体

 public class Customer
    {
        public int Id_int { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public decimal SalaryDecimal { get; set; }
        public string Province { get; set; }
        public string City { get; set; }
        public string Area { get; set; }
        public DateTime CreateDateTime { get; set; }
    }

1.2传递参数DTO

查询条件。我把所有的查询条件封装成了一个对象,虽然也可以直接用拼接参数的方式传值...

 /// 
    /// 查询参数对象
    /// 
    public class CustomerParam
    {
        public string name { get; set; }
        public string stime { get; set; }
        public string etime { get; set; }
        public string area { get; set; }
        public string code { get;set; }
        public int page { get; set; }
        public int pageSize { get; set; }
        public string sort { get; set; }
    }

1.3统计结果集DTO

我做了两个汇总(总工资、总年龄),也封装成了对象。

 /// 
    /// 统计DTO
    /// 
    public class SumDto
    {
        /// 
        /// 总工资
        /// 
        public decimal SumMoney { get; set; }

        /// 
        /// 总年龄
        /// 
        public long TotalAge { get; set; }

2.查询用户列表方法

这里模拟了55条记录,如果需要用到数据库,EF操作表就ok了。

  /// 
        /// 客户分页列表
        /// 
        /// 
        public PagedList GetCustomers(CustomerParam param, out SumDto sumDto)
        {
            var list = new List();
            for (int i = 0; i < 55; i++)
            {
                var costomer = new Customer()
                {
                    CreateDateTime = DateTime.Now.AddMinutes(i),
                    SalaryDecimal = 100 + i,
                    Age = 20 + i,
                    Area = "渝北区" + i,
                    City = "成都" + i,
                    Code = "Customer" + i,
                    Id_int = i,
                    Name = "我叫张三" + i,
                    Province = "重庆" + i
                };
                list.Add(costomer);
            }

            #region 搜索条件

            var i_list = list.ToList();
            if (!string.IsNullOrEmpty(param.stime) && !string.IsNullOrEmpty(param.etime))
            {
                //time
                var s_time = Convert.ToDateTime(param.stime);
                var e_time = Convert.ToDateTime(param.etime);
                i_list = i_list.FindAll(c => c.CreateDateTime >= s_time && c.CreateDateTime <= e_time);
            }
            //code or name
            if (!string.IsNullOrEmpty(param.name))
            {
                i_list = i_list.FindAll(c => c.Code.Contains(param.name) || c.Name.Contains(param.name));
            }

            #endregion

            sumDto = new SumDto()
            {
                SumMoney = i_list.Sum(o => o.SalaryDecimal),
                TotalAge = i_list.Sum(o => o.Age)
            };

            return new PagedList(i_list, param.page, param.pageSize);//自己写的扩展方法
        }
PagedList的重写构造方法, 我在另一篇博客里有记载:

LINQ扩展

3.控制器层

3.1Action指向视图

public ActionResult KendoView_()
        {
            return View();
        }

3.2查询数据源

 /// 
        /// 获取数据源
        /// 
        /// 
        [HttpPost]
        public JsonResult GetUsers(CustomerParam query)
        {
            var sumDto = new SumDto();//用来做统计的对象
            query.page -= 1;//视图传过来的值是1,索引是从0开始,所以-1
            UserService _userService = new UserService();
            var userlist = _userService.GetCustomers(query, out sumDto);
            return Json(new
            {
                data = userlist,
                total = userlist.TotalCount,
                TongJi = sumDto
            });
        }
注意:控制器方法必须注明为HttpPost方式

前台

1.布局页面(引入样式和js)

包括:KendoUI、JQ、Angular、H-ui、BootStrap...相关文件,要去官网或者官方qq群里下载。




    
    @ViewBag.Title

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
    
    
    
    

    
    


    
@RenderBody()

自己重写了一部分样式,也只是为了好看而已...

2.视图

2.1静态页面

@{
    ViewBag.Title = "KendoGrid Test";
    Layout = "~/Views/Shared/_LayoutKendo.cshtml";
    ViewBag.stime = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");//查询开始时间
    ViewBag.etime = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd");//结束时间
}

员工姓名: 创建时间: -

2.2js


注意1.页面初始化方法ChangeTableHeight(),该方法会填充table的空白,即使没有这么多数据。如果不加此方法,table的行高会依赖实际数据量..

如果屏蔽该方法,效果:

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第2张图片

   2.不知道是不是JQ和angular有冲突,用JQ的时间选择器传值,控制器始终获取不到值。所以,在传值之前, 手动将两个时间控件的值赋值给参数对象.

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第3张图片

    3.post请求的时候,参数名必须和控制器方法的参数名一样,大小写也必须一样。

我两边的名字都叫query

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第4张图片

没有上传项目文件,因为复制上面的代码,完全可以正常运行。

后记:

1.这篇博文仅仅是给自己还有更多初学者作为参考笔记,主要是Kendo-Grid和Angular结合的使用,诸多查询效率、代码可读性、代码注释的问题没有解决,还请诸位看官高抬贵手。

2.今天是自己在这家公司的最后一天了,所以才有时间和闲心来总结过去的开发,写这篇文章。来这里2年左右了,细细回想起当初一起加班到凌晨、一起做活动的时光,心中也是各种滋味...看看现在的时间,2017.4.21 下午2.09分,还有不到4个小时就下班了。

很多同事都是不打不相识呢,特别是分公司的几位哥,当初做你们需求的时候真是想把你们拉过来当面打一顿,哈哈...现在我们的关系非常好,私底下也成为了朋友。

抬头望望窗外,阳光明媚,工位上的几盆绿萝,只有托付给以后的同事了...

兄弟们,保重,加油!

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第5张图片


-----------------------------------2017.4.27更新-------------------------------

3.NPOI导出Excel

关于NPOI,我之前用一个系列详细做过记录

【一步一步学NPOI】

3.1DataTable导出Excel方法

/// 
        /// 导出Excel
        /// 
        /// 数据源
        public static void ExportExcel(DataTable dt, string fileName)
        {
            HSSFWorkbook hssfworkbook = new HSSFWorkbook();
            //创建Excel工作表  
            var sheet1 = hssfworkbook.CreateSheet("用户列表");

            var row0 = sheet1.CreateRow(0);
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                var row1 = sheet1.CreateRow(i + 1);
                for (int j = 0; j < dt.Columns.Count; j++)
                {
                    var cell0 = row0.CreateCell(j);
                    cell0.SetCellValue(dt.Columns[j].ToString());
                    var cell1 = row1.CreateCell(j);
                    cell1.SetCellValue(dt.Rows[i][j].ToString());
                }
            }
            //绝对路径-文件名
            string excelName = string.Format(@"D:/{0}_{1}.xlsx", fileName, DateTime.Now.ToString("yyyy_MM_dd"));
            //保存
            FileStream file = new FileStream(excelName, FileMode.Create, FileAccess.Write);
            hssfworkbook.Write(file);
            file.Close();
        }
因为NPOI是基于DataTable操作的,所以写了一个IEnumerable转DataTable的扩展方法:

   public static DataTable ToTable(this IEnumerable list)
        {
            //属性集合
            List userList = new List();

            DataTable dt = new DataTable("MyTable");
            Type type = typeof(T);

            Array.ForEach(type.GetProperties(), p => { userList.Add(p); dt.Columns.Add(p.Name, p.PropertyType); });
            foreach (var item in list)
            {
                var row = dt.NewRow();
                userList.ForEach(u => row[u.Name] = u.GetValue(item, null));
                dt.Rows.Add(row);
            }
            return dt;
        }

3.2前端

页面上一个按钮控件就不记录了;

Angular方法:

 //导出excel
        $scope.Excel = function () {
            $.post('ExportExcel', {}, function (data) {
                if (!data.success) {
                    layer.alert('出错了:'+data.message, { icon: 2 });
                }
            });
        }
控制器:(注意: 我是将导出的数据源保存到Session

   public JsonResult ExportExcel()
        {
            if (Session["userlist"] != null)
            {
                var dataSoruce = Session["userlist"] as DataTable;
                Helper.ExportExcel(dataSoruce, "用户列表");
                return Json(new
                {
                    success = true
                });
            }
            else
            {
                return Json(new
                {
                    success = false,
                    message = "获取用户数据源session失败"
                });
            }
        }
大功告成:在本地已经生成了相应的Excel

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第6张图片


Echart生成折线图

祭出echart官网:

多说两句,echart是百度开发的客户端生成的图形报表第三方组件。想起前几年做折线图报表,不知道有第三方框架,硬生生用reportviewer和rdlc来做的,同样实现了这功能。可,人都是会变得,有了框架,谁还走那么多冤枉路呢?

这里我选取前十条记录的年龄和薪水来做折线图。

(才吃晚饭,先出去带幺儿散散步再回来写....)

查看折线图的按钮

页面上放了一个按钮,弹出一个新视图来show折线图。(说明一下:静态的样式都是用的h-ui的样式

按钮标签:

 

click方法:用了layer,单独弹出一个新页面。

  //查看折线图(单独打开一个视图)
        $scope.Zhexian = function () {
            layer.open({
                type: 2,
                title: '薪水年龄折线图',
                shadeClose: true,
                shade: 0.4,
                area: ['60%', '60%'],
                content: "/kendo/Zhexian"	//控制器方法
            });
        }

控制器Zhexian方法:

   public ActionResult Zhexian()
        {
            return View();
        }

折线图View

静态页面很简单,只有一个div,用来做画板。因为要用到angular,所以外层加了一个section。

@{
    ViewBag.Title = "Zhexian";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

注意这个angular的初始化方法,init。这个方法里面就去控制器里查询需要画折线图的数据。

该页面完整的脚本:

var app = angular.module("MyApp", ["kendo.directives"]);
    var myController = app.controller("myController", ["$scope", "$http", function ($scope, $http) {
        $scope.xAxis_data = [];//X轴数据

        $scope.series = [];//折线的对象数组
        //我要统计两条折现,所以创建两个对象
        //折线对象1
        $scope.series_obj_a = {
            name: '年龄',
            type: 'line',
            stack: '总量'
        };
        //折线对象2
        $scope.series_obj_b = {
            name: '工资',
            type: 'line',
            stack: '总量'
        };

        $scope.series_obj_a_data = [];//折线对象1上的值
        $scope.series_obj_b_data = [];//折线对象2上的值

        //初始化方法,得到待折线图的数据
        $scope.init = function () {
            $.post('/kendo/GetZhexianData', {}, function (data) {
                for (var i = 0; i < data.length; i++) {
                    $scope.xAxis_data.push(data[i].UserName);//X轴-用户名
                  
                    $scope.series_obj_a_data.push(data[i].Age);//年龄
                    $scope.series_obj_b_data.push(data[i].Salary);//薪水
                }
                $scope.series_obj_a.data = $scope.series_obj_a_data;
                $scope.series_obj_b.data = $scope.series_obj_b_data;

                $scope.series.push($scope.series_obj_a);
                $scope.series.push($scope.series_obj_b);
                console.log($scope.series);

                $scope.MakeZhexian();
            });
        }

        //画折线图
        $scope.MakeZhexian = function () {
            var myChart = echarts.init(document.getElementById('main'));

            var option = {
                title: {
                    text: '选择前十组数据'
                },
                tooltip: {
                    trigger: 'axis'
                },
                legend: {
                    data: ['工资', '年龄']
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                },
                toolbox: {
                    feature: {
                        saveAsImage: {}
                    }
                },
                xAxis: {
                    type: 'category',
                    boundaryGap: false,
                    data: $scope.xAxis_data
                },
                yAxis: {
                    type: 'value'
                },
                series: $scope.series
            };

            myChart.setOption(option);
        }

    }]);
说明:

1.初始化加载时post到控制器里,得到数据源

 /// 
        /// 画折线图的数据源
        /// 
        /// 
        public JsonResult GetZhexianData()
        {
            var userlist = Session["userlist"] as List;//这里可以用读取数据库的之,为了方便,我就只取了session
            return Json(userlist.Select(u => new
            {
                UserName = u.UserName,
                Salary = u.Salary,
                Age = u.Age
            }).Take(10));//只模拟前10条记录
        }

2.画板上的看到的线条,本质是一个对象数组,每个折线图就是一个对象。

series: $scope.series

3.最终生成两条折线图(工资、年龄),所以我创建了两个折线图对象。


最后的效果

【MVC】AngularJs+KendoUI开发报表Demo(导出Excel和折线图)_第7张图片









你可能感兴趣的:(MVC学习笔记,UI框架,Web前端)