ngTbale假分页实现排序、搜索、导出CSV等功能

一. ngTable功能简化

  使用ngTable经常有分页,排序,过滤等功能,实现诸多功能较为麻烦。为了方便开发过程,可以抽取一些table共同点写一个公有方法。

  注意:

    1. 由于很多特别的需求,可能表格不一定适用于自己的项目,部分地方需要自己调试到适合自己的项目。

    2. 部分功能没有使用ngTable自带的功能,只因为PM需求与之不符合 

  代码如下:

  1. ListTableEX代码:

  1 angular.module('newApp')
  2   .factory('ListTableEX', ['ngTableParams', 'getStorage', function (ngTableParams, getStorage) {
  3     var store = getStorage('localStorage');
  4 
  5     /**
  6      * 创建表格
  7      * @param options1
  8      * @param options2
  9      * @returns {ngTableParams|*}
 10      */
 11     function createListTable(options1, options2) {
 12       var listTable = new ngTableParams(options1, {
 13         counts: options2.counts,
 14         getData: function ($defer, params) {
 15           // 如果不需要强制刷新数据且有缓存数据,那么直接使用缓存数据
 16           // Note: 由于api可能返回空数据或者出错,因此不能通过判断
 17           // 缓存数组容量是否为0来判断有无缓存,而是通过判断该数组是否为
 18           // undefined
 19           if (!listTable.needForceReload && params.items) {
 20             // 复制items中的数据,以免shownItems的更改影响到items
 21             params.shownItems = [];
 22             for (var index in params.items) {
 23               params.shownItems.push(params.items[index]);
 24             }
 25             var itemsFilter = options2.itemsFilter;
 26             if (itemsFilter) {
 27               params.shownItems = itemsFilter(params.shownItems);
 28             }
 29             sortTable(listTable);
 30             listTable.calculatePages();
 31             var itemsCount = params.shownItems.length;
 32             var itemCountPerPage = listTable.count();
 33             var currentPage = listTable.page();
 34             params.total(itemsCount);
 35             $defer.resolve(subArray(params.shownItems, (currentPage - 1) * itemCountPerPage, itemCountPerPage));
 36             return;
 37           }
 38           listTable.needForceReload = false;
 39           var apiParams = {
 40             limit: 20000000,
 41             index: 1
 42           };
 43           options2.getData(apiParams).then(function (data) {
 44             listTable.resetSortParams();
 45             listTable.resetCheckboxes();
 46             var resData = data.data.data;
 47             var onDataResult = options2.onDataResult;
 48             if(onDataResult) {
 49               onDataResult(resData);
 50             }
 51             var items = resData.items;
 52             var dataPreprocessFunc = options2.dataPreprocessFunc;
 53             if (dataPreprocessFunc) {
 54               dataPreprocessFunc(items);
 55             }
 56             // 即使api没有返回数据,也应该设置缓存数组,否则会导致无限刷新
 57             params.items = items ? items : [];
 58             // 由于已经获取到了数据,那么重新reload一次,复用有缓存数据时的处理逻辑
 59             listTable.reload();
 60           }, function () {
 61           });
 62         }
 63       });
 64       listTable.tableName = options1.tableName;
 65       listTable.titles = options1.titles;
 66       listTable.needForceReload = false;
 67       listTable.getItemId = options2.getItemId;
 68       listTable.forceReload = function () {
 69         listTable.needForceReload = true;
 70         listTable.reload();
 71       }
 72       return listTable;
 73     };
 74 
 75     /**
 76      * 初始化排序功能
 77      * @param listTable
 78      */
 79     function initSort(listTable) {
 80       listTable.nextSortType = function () {
 81         return listTable.sortParams.type == 'asc' ? 'desc' : 'asc';
 82       };
 83       listTable.isColumnSorting = function (key) {
 84         return listTable.sortParams.key == key;
 85       };
 86       listTable.isColumnSortingByASC = function (key) {
 87         return listTable.isColumnSorting(key) && listTable.sortParams.type == 'asc';
 88       };
 89       listTable.isColumnSortingByDESC = function (key) {
 90         return listTable.isColumnSorting(key) && listTable.sortParams.type == 'desc';
 91       };
 92       listTable.resetSortParams = function () {
 93         listTable.sortParams = {key: '', type: ""};
 94       }
 95       listTable.resetSortParams();
 96     };
 97 
 98     /**
 99      * 初始化条目选择框
100      * @param listTable
101      * @param withCheckboxes
102      * @returns {*}
103      */
104     function initCheckboxes(listTable, withCheckboxes) {
105       if (withCheckboxes) {
106         listTable.toggleCheckAll = function () {
107           var checkedCount = listTable.checkedIds().length;
108           if (checkedCount == listTable.shownItems.length) {
109             listTable.deselectAll();
110           } else {
111             listTable.selectAll();
112           }
113         };
114         listTable.selectAll = function () {
115           listTable.resetCheckboxes();
116           listTable.checkboxes.selectAll = true;
117           for (var index in listTable.shownItems) {
118             listTable.checkboxes.items[listTable.getItemId(listTable.shownItems[index])] = true;
119           }
120         };
121         listTable.deselectAll = function () {
122           listTable.resetCheckboxes();
123           listTable.checkboxes.selectAll = false;
124           for (var index in listTable.shownItems) {
125             listTable.checkboxes.items[listTable.getItemId(listTable.shownItems[index])] = false;
126           }
127         };
128         listTable.checkedIds = function () {
129           var ids = [];
130           for (var index in listTable.shownItems) {
131             var id = listTable.getItemId(listTable.shownItems[index]);
132             if (listTable.checkboxes.items[id]) {
133               ids.push(id);
134             }
135           }
136           listTable.checkboxes.selectAll = listTable.shownItems ? (listTable.shownItems.length == ids.length) : false;
137           return ids;
138         }
139         listTable.resetCheckboxes = function () {
140           listTable.checkboxes = {
141             selectAll: false,
142             items: {}
143           };
144         };
145       } else {
146         listTable.resetCheckboxes = function () {
147         }
148       }
149       listTable.resetCheckboxes();
150       return listTable;
151     };
152 
153     /**
154      * 初始话分页功能
155      * @param listTable 表格
156      * @private
157      */
158     function initPagination(listTable) {
159       var STORAGE_PAGESIZE_KEY = getKeyForStoragePageSize(listTable);
160 
161       if (store.get(STORAGE_PAGESIZE_KEY)) {
162         listTable.count(store.get(STORAGE_PAGESIZE_KEY)['value']);
163       }
164       listTable.editCount = listTable.count();
165 
166       listTable.prePage = function () {
167         var page = listTable.page();
168         if (page > 1) {
169           listTable.page(page - 1);
170         }
171       };
172       listTable.nextPage = function () {
173         var page = listTable.page();
174         if (page < Math.ceil(1.0 * listTable.total() / listTable.count())) {
175           listTable.page(page + 1);
176         }
177       };
178       listTable.calculatePages = function () {
179         var itemsCount = listTable.shownItems.length;
180         // 计算页导航条数据
181         var itemCountPerPage = listTable.count();
182         store.set(STORAGE_PAGESIZE_KEY, {
183           'value': itemCountPerPage
184         });
185         var totalPage = Math.ceil(1.0 * itemsCount / itemCountPerPage);
186         // 当前页最大为页面总数,最小为1
187         var currentPage = Math.max(1, Math.min(listTable.page(), totalPage));
188         listTable.page(currentPage);
189         listTable.pages = calculateTablePages(currentPage, totalPage);
190       }
191     };
192 
193     function getKeyForStoragePageSize(listTable) {
194       return 'list_table_page_size@' + listTable.tableName;
195     };
196 
197     /**
198      * 从原始数组中分割出一段连续的数组,该操作不会影响原始数组
199      * @param array 原始数组
200      * @param startIndex 分割起始位置
201      * @param length 分割数量
202      * @returns {Array} 分割结果
203      */
204     function subArray(array, startIndex, length) {
205       var result = [];
206       if (array && startIndex > -1 && length > 0) {
207         var maxIndex = Math.min(startIndex + length, array.length);
208         for (var i = startIndex; i < maxIndex; i++) {
209           result.push(array[i]);
210         }
211       }
212       return result;
213     };
214 
215     /**
216      * 对数据进行排序,该操作将直接操作传入的数组顺序
217      * @param items 数据数组
218      * @param key 数据对象的排序关键字
219      * @param sortType 排序类型,'asc'或者'desc'
220      */
221     function sortItems(items, key, sortType) {
222       if (!items || items.length < 1 || !key || key.length < 1) {
223         return;
224       }
225       items.sort(
226         function compareFunction(param1, param2) {
227           var value1 = param1[key];
228           var value2 = param2[key];
229           if (typeof(value1) === 'number' && typeof(value2) === 'number') {
230             return sortType * (value1 - value2);
231           }
232           return sortType * ((value1 + '').localeCompare(value2 + ''));
233         }
234       );
235     };
236 
237     function calculateTablePages(currentPage, totalPage) {
238       if (totalPage == 0) {
239         return [];
240       }
241       var end = Math.min(9, totalPage);
242       var pages = [currentPage];
243       while (pages.length < end) {
244         var pre = pages[0] - 1;
245         var next = pages[pages.length - 1] + 1;
246         if (pre >= 1) {
247           pages.unshift(pre);
248         }
249         if (next <= totalPage) {
250           pages.push(next);
251         }
252       }
253       return pages;
254     };
255 
256     function sortTable(listTable) {
257       var sortParams = listTable.sorting();
258       var sortKey;
259       var sortType;
260       for (var key in sortParams) {
261         sortKey = key;
262         sortType = sortParams[key];
263       }
264       if (!sortType || sortType.length < 1) {
265         sortType = 'asc';
266       }
267       listTable.sortParams.key = sortKey;
268       listTable.sortParams.type = sortType;
269       if (sortKey && sortKey.length > 0) {
270         sortItems(listTable.shownItems, sortKey, sortType == 'asc' ? 1 : -1);
271       }
272     };
273 
274     function init(options1, options2) {
275       var listTable = createListTable(options1, options2);
276       initSort(listTable);
277       initCheckboxes(listTable, options1.withCheckboxes);
278       initPagination(listTable);
279       return listTable;
280     };
281 
282     return {
283       init: init,
284     };
285   }]);

  2. ListTableEX分页代码:

 1 <div class="ng-cloak dahuo-pagination" ng-if="params.pages.length > 0">
 2   <div class="col-lg-6">
 3     <div class="dataTables_info" role="status" aria-live="polite">
 4       {{ params.count() * (params.page() - 1) + 1 }}~
 5       {{ params.count() * (params.page() - 1) + params.data.length }}条,共{{
 6       params.total() }}条记录
 7     </div>
 8   </div>
 9   <div class="col-lg-6 pagination2" style="margin-bottom: 40px">
10     <div class="dataTables_paginate paging_simple_numbers">
11       <ul class="pagination ng-table-pagination">
12         <li><a href="" ng-click="params.prePage()">上一页</a></li>
13         <li ng-repeat="page in params.pages"
14             ng-class="{'active' : page == params.page()}">
15           <a href="" ng-click="params.page(page)">{{ page }}</a>
16         </li>
17         <li><a href="" ng-click="params.nextPage()">下一页</a></li>
18       </ul>
19     </div>
20     <div class="col-lg-3 pull-right">
21       {{params.testOK}}
22       <select class="select form-control" ng-model="params.editCount" ng-change="params.count(params.editCount)"
23               style="padding: 6px; margin-top: 2px;min- 80px;margin-right: 10px"
24               ng-options="size for size in params.settings().counts">
25       </select>
26     </div>
27   </div>
28 </div>

  由于PM的需求变更较大,使用方法可能各不相同。

  基本使用方法如下:

  1. HTML

    本表格是一个有checkBox的表格,具有编辑,新建,上线,下线等功能。

 1 <div>
 2   <div class="row">
 3     <div class="col-lg-12 portlets">
 4       <div class="panel">
 5         <div class="panel-content">
 6           <div class="row dahuo-table-toolbar">
 7             <div class="col-xs-6">
 8               <button type="button" class="btn btn-default" ng-click="activityupload()" ng-disabled="checkedCount() < 1">发布</button>
 9               <button type="button" class="btn btn-default" ng-click="activityoffline()" ng-disabled="checkedCount() < 1">下线</button>
10               {{ checkedCount() }}个被选中
11             </div>
12             <div class="col-xs-6">
13               <div class="pull-right">
14                 <a href="#operation-activityadd">
15                   <button type="button" class="btn btn-primary">+ 新建活动</button>
16                 </a>
17               </div>
18             </div>
19           </div>
20           <div class="table-responsive">
21             <table ng-table="activityTable" class="table trans-table table-hover text-left dahuo-table"
22                      template-pagination="layouts/pagination_v2.html" width="100%">
23               <thead>
24                 <tr>
25                   <th width="5%" class="dahuo-table-header-th text-center" header="'ng-table/headers/checkbox.html'">
26                     <span class="list-checkbox">
27                       <input type="checkbox" ng-model="activityTable.checkboxes.selectAll" ng-disabled="activityTable.items.length < 1" ng-click="activityTable.toggleCheckAll()"/>
28                       <span></span>
29                     </span>
30                   </th>
31                   <th width="{{title.width}}" ng-repeat="title in activityTable.titles" ng-class="{'sort': title.sortable}" ng-click="title.sortable ? activityTable.sorting(title.key, activityTable.nextSortType()) : 0">{{ title.name }}
32                     <li class="fa pull-right" ng-if="title.sortable" ng-class="{'fa-sort-asc': activityTable.isColumnSortingByASC(title.key), 'fa-sort-desc': activityTable.isColumnSortingByDESC(title.key), 'fa-unsorted': !activityTable.isColumnSorting(title.key)}" aria-hidden="true" ng-style="{'color' : (!activityTable.isColumnSorting(title.key) ? '#c9c9c9' : '')}"></li>
33                   </th>
34                 </tr>
35               </thead>
36               <tbody>
37                 <tr ng-repeat="item in $data">
38                   <td class="text-center" header="'ng-table/headers/checkbox.html'">
39                     <span class="list-checkbox">
40                       <input type="checkbox" ng-model="activityTable.checkboxes.items[item.id]"/>
41                       <span></span>
42                     </span>
43                   </td>
44                   <td><a href="#operation-activityedit/{{item.id}}">{{自定义绑值}}</a></td>
45                   <td>{{自定义绑值}}</td>
46                   <td>{{自定义绑值}}</td>
47                   <td>{{自定义绑值}}</td>
48                   <td>{{自定义绑值}}</td>
49                 </tr>
50                 <tr style="height: 100px" ng-if="$data.length < 1">
51                   <td colspan="6" class="text-center">暂时没有数据</td>
52                 </tr>
53               </tbody>
54             </table>
55           </div>
56         </div>
57       </div>
58     </div>
59   </div>
60 </div>

  2. JS

  字段解释:

    (1).tableName:table名称,用于缓存数据时使用的localStorage的key

    (2).titles:table表头控制项目,key是绑定数据的key,name是显示名称,sortable是否可排序

    (3).withCheckboxes:表格是否包含checkboxes的列

    (4).page:当前页

    (5).count:每页显示条目数量

    (6).sorting:默认排序的key和升降序

    (7).counts:每页显示条数数量更改项

    (8).getData:获取表格的数据,一般是发送API。这里有一个坑,后续会详细说明。

    (9).getItemId:对应checkBoxes的itemId,复制即可。

  依赖项:

    DS:API请求依赖

    logger:提示信息依赖

 1 angular.module('newApp')
 2   .controller('ActivityListCtrl', ['$scope', 'ds.activity', 'logger', 'ListTableEX',
 3     function($scope, DS, logger, ListTableEX) {
 4       $scope.activityTable = new ListTableEX.init(
 5         {
 6           tableName:'operation.activity.activity.list',
 7           titles:[
 8             {key: '', name: "活动ID", sortable: true,  '10%'},
 9             {key: '', name: "活动名称", sortable: true,  '35%'},
10             {key: '', name: "开始时间", sortable: true,  '20%'},
11             {key: '', name: "结束时间", sortable: true,  '20%'},
12             {key: '', name: "状态", sortable: true,  '10%'},
13           ],
14           withCheckboxes: true,
15           page: 1,
16           count: 25,
17           sorting: {'id': 'desc'},
18         },
19         {
20           counts: [10, 25, 50, 100],
21           getData: function(apiParams) {
22             return DS.listActivity(apiParams);
23           },
24           getItemId: function(item) {
25             return item.id;
26           }
27         }
28       );
29 
30       $scope.checkedCount = function() {
31         return $scope.activityTable.checkedIds().length;
32       }
33 
34       $scope.activityupload = function() {
35         var checkedIds = $scope.activityTable.checkedIds();
36         if (checkedIds.length < 1) {
37           logger.warning('请选择操作对象');
38           return;
39         }
40 
41         DS.activityUpload(checkedIds)
42           .then(function() {
43             $scope.activityTable.forceReload();
44             logger.success('上线成功');
45           }, function(error) {
46             logger.error('上线失败');
47           });
48       };
49 
50       $scope.activityoffline = function() {
51         var checkedIds = $scope.activityTable.checkedIds();
52         if (checkedIds.length < 1) {
53           logger.warning('请选择操作对象');
54           return;
55         }
56 
57         DS.activityOffline(checkedIds)
58           .then(function() {
59             $scope.activityTable.forceReload();
60             logger.success('下线成功');
61           }, function(error) {
62             logger.error('下线失败');
63           });
64       };
65     }
66   ]);

二. 使用中注意的问题:

  1. 需要自己更改适应服务端返回数据的处理代码:

    对应代码段时listTableEX的“var resData = data.data.data”

  2. 使用中可能Table数据不是来自于API:

    需要使用如下代码:

 1 getData: function(apiParams) {
 2   return {then: function(callback){
 3     var data = {
 4       data:{
 5         data:{
 6           items: [],
 7         }
 8       }
 9     }
10     callback(data);
11   }}
12 }

    然后强行加载数据:

1 $scope.ActivityTable.items = items;
2 $scope.ActivityTable.page(1);
3 $scope.ActivityTable.reload();

  3. 可能数据来自API,但是触发时机不是Table new出来时:

    New出来的代码更改如下:

 1 getData: function(apiParams) {
 2   if (满足API发送条件){
 3     var apiParams = {
 4         (自定义)
 5     };
 6     return DS.activityList(apiParams);
 7   }
 8   return {then: function(callback){
 9     var data = {
10       data:{
11         data:{
12           items: [],
13         }
14       }
15     }
16     callback(data);
17   }}
18 }

     然后发送API获取数据:

1 $scope.ActivityTable.page(1)
2 $scope.ActivityTable.forceReload();

 三. 搜索,过滤,导出CSV插件

  代码都有较长的注释,这里不做过多的介绍。

  导出CSV依赖插件FileSaver.js。

angular.module('newApp')
  .factory('ListTableEXExtend', function (getStorage) {
    /*
    First method:
      ListTableEXExtend.searchFilter(Array, String, Boolean);
      Input:
        Array: ['a', 'b', 'c', 'aa', 'ab', 'cc']
        String: 'a'
        Boolean: true
      Output:
        Array: ['a', 'aa', 'ab']

    Second method:
      ListTableEXExtend.searchFilter(ObjectArray, String, Boolean, Array)
      Input:
        ObjectArray: [
          {id:111, name:'aaa', ex1:'sss', ex2: 'sss'},
          {id:222, name:'11aa', ex1:'sss', ex2: 'sss'},
          {id:333, name:'a1a1', ex1:'sss', ex2: 'sss'},
          {id:444, name:'bbbb', ex1:'sss', ex2: 'sss'}
        ]
        String: 1 or '1'
        Boolean: true
        Array: ['id', 'name']
      Output:
        ObjectArray: [
          {id:111, name:'aaa', ex1:'sss', ex2: 'sss'},
          {id:222, name:'11aa', ex1:'sss', ex2: 'sss'},
          {id:333, name:'a1a1', ex1:'sss', ex2: 'sss'}
        ]
      Others: return items

      Note: if has too much data, Please use method: delay reload Table
    */
    function searchFilter(items, keyword, isIgnoreUpper, regArr) {
      if (!items) {
        console.log("Items must be defined");
        return;
      }
      if (typeof(items) !== "object" || items.constructor !== Array) {
        console.log("Items must be a array");
        return items;
      }
      if (items.length === 0) {
        return items;
      }
      if (!keyword && keyword !== 0 && keyword !== '0') {
        return items;
      }
      /* Copy Items*/
      var newItems = items.concat();
      if (isIgnoreUpper){
        var keyword = keyword.toString().toLowerCase();
      }else {
        var keyword = keyword.toString();
      }
      if (!regArr) {
        var resultItem = [];
        for (var index in items) {
          try {
            if (isIgnoreUpper){
              var newItem = newItems[index].toString().toLowerCase();
            }else {
              var newItem = newItems[index].toString();
            }
          } catch (e) {
            console.log(e.name + ": " + e.message);
          }
          if (newItem.indexOf(keyword) !== -1) {
            resultItem.push(items[index]);
          }
        }
        return resultItem;
      }
      if (typeof(regArr) !== "object" || regArr.constructor !== Array) {
        console.log("regArr must be a array");
        return items;
      }
      if (regArr.length === 0) {
        return items;
      }
      var newRegArr = regArr.concat();
      for (var index in newRegArr) {
        try {
          newRegArr[index].toString();
        } catch (e) {
          console.log(e.name + ": " + e.message);
        }
      }
      var resultItem = [];
      for (var index in items) {
        try {
          var newItem = newItems[index];
          if (typeof(newItem) !== "object" || newItem.constructor !== Object) {
            console.log("Items must be a object array");
            return items;
          }
          for (var index2 in newRegArr) {
            if (!newItem[newRegArr[index2]] && newItem[newRegArr[index2]] !== 0 && newItem[newRegArr[index2]] !== '0') {
              continue;
            }
            if (isIgnoreUpper){
              var newItemToStr = newItem[newRegArr[index2]].toString().toLowerCase();
            }else {
              var newItemToStr = newItem[newRegArr[index2]].toString();
            }
            if (newItemToStr.indexOf(keyword) !== -1) {
              resultItem.push(items[index]);
              break;
            }
          }
        } catch (e) {
          console.log(e.name + ": " + e.message);
        }
      }
      return resultItem;
    }

    /*
      Method:
        ListTableEXExtend.selectFilter(ObjectArray, Key, Value)
        Input:
          ObjectArray: [
            {id:111, name:'aaa', product_id:'123', ex: 'sss'},
            {id:222, name:'11aa', product_id:'113', ex: 'sss'},
            {id:333, name:'a1a1', product_id:'123', ex: 'sss'},
            {id:444, name:'bbbb', product_id:'113', ex: 'sss'},
          ]
          Key: product_id
          Value: 123
        Output:
          ObjectArray: [
            {id:111, name:'aaa', product_id:'123', ex: 'sss'},
            {id:333, name:'a1a1', product_id:'123', ex: 'sss'}
          ]
        Others: return items
    */
    function selectFilter(items, key, value) {
      if (!items) {
        console.log("Items must be defined");
        return;
      }
      if (!key || !value) {
        console.log("Key, value must be defined");
        return items;
      }
      if (typeof(items) !== "object" || items.constructor !== Array) {
        console.log("Items must be a array");
        return items;
      }
      if (items.length === 0) {
        return items;
      }
      key = key.toString();
      var resultItem = []
      for (var index in items) {
        try {
          var item = items[index];
          if (typeof(item) !== "object" || item.constructor !== Object) {
            console.log("Items must be a object array");
            return items;
          }
          if (item[key] == undefined || item[key] == null || item[key] === '') {
            continue;
          }
          if (value === 'defined'){
            resultItem.push(items[index]);
            continue;
          }
          if (item[key] == value) {
            resultItem.push(items[index]);
          }
        } catch (e) {
          console.log(e.name + ": " + e.message);
        }
      }
      return resultItem;
    }

    function _getDateTime(date){
      if (angular.isDate(date)){
        return date.getTime();
      }else {
        return new Date(date).getTime();
      }
    }

    function timeFilter(items, start, end, key){
      if (!items) {
        console.log("Items must be defined");
        return;
      }
      if (!angular.isArray(items)) {
        console.log("Items must be a array");
        return items;
      }
      var startTime = _getDateTime(start);
      var endTime = _getDateTime(end);
      var newItems = items.concat();
      var resultItems = [];
      var newKey = key.toString()
      for (var index in items){
        var newItem = items[index];
        if (!angular.isObject(newItem)) {
          console.log("Items must be a object array");
          return items;
        }
        if (!newItem[newKey]) {
          continue;
        }
        var newItemDate = _getDateTime(newItem[newKey]);
        if (newItemDate >= startTime && newItemDate <= endTime){
          resultItems.push(newItem);
        }
      }
      return resultItems;
    }

    function editItem(items, item, primaryKey, isCreated){
      if (!items) {
        console.log("Items must be defined");
        return;
      }
      if (!item || !primaryKey) {
        console.log("Item, primaryKey must be defined");
        return items;
      }
      if (!angular.isArray(items)) {
        console.log("Items must be a array");
        return items;
      }
      if (!angular.isObject(item)) {
        console.log("Item must be a object");
        return items;
      }
      primaryKey = primaryKey.toString();
      /* Copy Items*/
      var newItems = items.concat();
      for (var index in newItems) {
        try {
          var newItem = newItems[index];
          if (!angular.isObject(newItem)) {
            console.log("Items must be a object array");
            return items;
          }
          if (!newItem[primaryKey] && newItem[primaryKey] != 0 && !item[primaryKey] && item[primaryKey] != 0) {
            continue;
          }else {
            if (newItem[primaryKey] == item[primaryKey]){
              newItems[index] = item;
              isCreated = false;
            }
          }
        } catch (e) {
          console.log(e.name + ": " + e.message);
          return items;
        }
      }
      if (isCreated){
        newItems.push(item);
      }
      return newItems;
    }

    /*
      Usage:
      var csv = new ListTableEXExtend.Csv({
        --titles(Option):
          ObjectArray.
          just lick listtableex.titles, 'key' and 'name' is needed.
          This is used for defining Csv col.
        --items(Option):
          ObjectArray.
          just lick listtableex.items.
        --filterData(Option):
          FuncObject.
          This is used for switch data.
          For example:
            {id(Must be titles'key): function(input){return input + 'Test'}}.
        --defaultChar(Option):
          String or Object.
          String: All table's default char.
          Object: One col default char(Must be titles'key).
          This is used for set default char when <td></td> is like this.
        --handleData(Option):
          Func.
          Must return Data.
          Data is the fileData.
        --fileName(Option):
          String.
        --delimiter(Option):
          String.
          This is used for maping <td> and another <td> with 'char'.
          Default is ',' used for Csv.
          });
      csv has to public method:
        --setItems(Option)
          Note: The items maybe is undefined because API are not repond this time.
          So you need to set items again.
        --setTitles(Option)
          Note: The titles maybe changed.
          So you need to set titles again.
        --exportCsv(Necessary, Main)
        Others are private.
        If you have to get proto, you can add public method.
    */
    function Csv(options) {
      var items = options.items;
      var titles = options.titles;
      var filterData = options.filterData;
      var handleData = options.handleData;
      var defaultChar = options.defaultChar;
      var type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
      var fileName = options.fileName ? options.fileName : 'download.csv';
      var delimiter = options.delimiter ? options.delimiter : ',';
      var data = '';

      function _download(data, fileName) {
        var blob = new Blob(["\ufeff" + data], {
          type: type
        });
        saveAs(blob, fileName);
      }

      function _getData(items, titles) {
        if (!items || !titles || typeof(items) !== "object" || items.constructor !== Array || typeof(titles) !== "object" || titles.constructor !== Array) {
          console.log("Usage:\n\titems: ObjectArray\n\ttitles: ObjectArray\n");
          return false;
        }
        return true;
      }

      function _handleData(items, titles, delimiter, filterData, handleData, defaultChar) {
        data = '';
        if (handleData) {
          data = handleData(items, titles);
        } else {
          var rowData = '';
          for (var index in titles) {
            rowData += titles[index].name + delimiter;
          }
          rowData = rowData.slice(0, rowData.length - 1);
          data += rowData + '\n';
          for (var i in items) {
            var item = items[i];
            var rowData = '';
            for (var j in titles) {
              var title = titles[j];
              if (!title.key) {
                return '';
              } else if (item[title.key] === undefined || item[title.key] === '') {
                if (defaultChar) {
                  if (angular.isObject(defaultChar)) {
                    if (defaultChar[title.key] !== undefined) {
                      rowData += defaultChar[title.key] + delimiter;
                    } else {
                      rowData += delimiter;
                    }
                  } else {
                    rowData += defaultChar + delimiter;
                  }
                } else {
                  rowData += delimiter;
                }
              } else {
                if (filterData) {
                  if (title.filterKey && filterData[title.filterKey]) {
                    var filterResultData = filterData[title.filterKey](item[title.key]);
                    rowData += filterResultData + delimiter;
                  } else if (filterData[title.key]) {
                    var filterResultData = filterData[title.key](item[title.key]);
                    rowData += filterResultData + delimiter;
                  } else {
                    rowData += item[title.key] + delimiter;
                  }
                } else {
                  rowData += item[title.key] + delimiter;
                }
              }
            }
            rowData = rowData.slice(0, rowData.length - 1);
            data += rowData + '\n';
          }
        }
        return data;
      }

      this.setItems = function (value) {
        items = value;
      }

      this.setTitles = function (value) {
        titles = value;
      }

      this.exportCsv = function () {
        if (!_getData(items, titles)) {
          return data;
        }
        data = _handleData(items, titles, delimiter, filterData, handleData, defaultChar);
        _download(data, fileName);
      }
    }

    return {
      searchFilter: searchFilter,
      selectFilter: selectFilter,
      timeFilter: timeFilter,
      editItem: editItem,
      Csv: Csv
    }
  })

  以上导出CSV方法是导出所有分页的数据,当表结构并非以[{...}, {...}]这种结构返回则比较难以实现通用性。对于没有分页的表,可以解析html来导出当页的数据到CSV。

angular.module('newApp')
  .directive('exportCsv', ['$parse', '$timeout', function ($parse, $timeout) {
    /*
      This is used for export ShownTable, Only current page.
      You do not need to handle data.

      For example:
        if ngTable:
          <a class="btn btn-primary" ng-click='csv.generate($event, "FileName", "ngTableBindname")' href=''>export</a>
          <table ng-table="ngTableBindname" class="table trans-table table-hover dahuo-table" width="100%" export-csv="csv">
        else:
          <a class="btn btn-primary" ng-click='csv.generate($event, "FileName", "tableId")' href=''>export</a>
          <table id="tableId" class="table trans-table table-hover dahuo-table" width="100%" export-csv="csv">
    */
    var delimiter = ',';
    var tables = {};
    var type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
    return {
      restrict: 'A',
      scope: false,
      link: function(scope, element, attrs) {
        var data = '';
        if (attrs.ngTable){
          tables[attrs.ngTable] = element;
        }else {
          tables[attrs.id] = element;
        }
        function stringify(str) {
          return '"' + str.replace(/^\s\s*/, '').replace(/\s*\s$/, '').replace(/"/g,'""') + '"';
        }
        function parseTable(table) {
          data = '';
          if (tables[table]){
            var rows = tables[table].find('tr');
          }else {
            return data
          }
          angular.forEach(rows, function(row, i) {
            var tr = angular.element(row),
              tds = tr.find('th'),
              rowData = '';
            if (tr.hasClass('ng-table-filters')) {
              return;
            }
            if (tds.length === 0) {
              tds = tr.find('td');
            }
            angular.forEach(tds, function(td) {
              rowData += stringify(angular.element(td).text()) + Array.apply(null, Array(td.colSpan)).map(function () { return delimiter; }).join('');
            });
            rowData = rowData.slice(0, rowData.length - 1);
            data += rowData + '\n';
          });
        }

        function download(data, filename) {
          var blob = new Blob(["\ufeff" + data], {
            type: type
          });
          saveAs(blob, filename);
        }

        var csv = {
          generate: function(event, filename, table) {
            if (table){
              parseTable(table);
              download(data, filename);
            } else {
              parseTable();
              download(data, filename);
            }
          }
        };
        $parse(attrs.exportCsv).assign(scope.$parent, csv);
      }
    }
  }])
原文地址:https://www.cnblogs.com/ccblogs/p/4917230.html