【经验总结】vue + element-ui 踩坑—— table 篇

工作一年,主要职责是负责公司后台管理平台的开发与维护。此间面对各种业务需求,通过面向谷歌编程等常见方式,积累了一些微不足道的经验。

本篇为总结的第一篇(也许有其他篇)—— table 篇

对于后台管理平台来说,最必不可少的元素就是 table 表格,几乎每个页面都涉及到表格的使用,甚至常作为是页面的主体部分。
因此,如何维护这些 table,并且根据业务需求完善不同解决方案,便是此文所会表达的内容。

主要技术栈如题为 vue 全家桶配合 element-ui(其他技术栈其实思路是类似的),因此主要还是对 el-table 等的再封装等。element-ui 的文档已经非常通俗易懂,本文不涉及一些文档上已有的基本用法。

接下来我会模拟一些简单的数据来展示一些业务问题的解决方案,其目的在展示思路,代码以简易为主。

1. 自定义列表项

很多时候我们需要将后端数据作展示优化

// mock 数据(跳过直接往下看)
tableData: [
  {
    id: "12987122",
    name1: "王小虎",
    name2: "王小虎",
    name3: "王小虎",
    address1: "上海市普陀区金沙江路 1518 弄",
    address2: "上海市普陀区金沙江路 1518 弄",
    address3: "上海市普陀区金沙江路 1518 弄",
    amount1: "234",
    amount2: "3.2",
    amount3: 10,
    amount4: "4.43",
    amount5: 12
  },
  {
    id: "12987123",
    name1: "王小虎",
    name2: "王小虎",
    name3: "王小虎",
    address1: "上海市普陀区金沙江路 1518 弄",
    address2: "上海市普陀区金沙江路 1518 弄",
    address3: "上海市普陀区金沙江路 1518 弄",
    amount1: "165",
    amount2: "4.43",
    amount3: 12,
    amount4: "4.43",
    amount5: 12
  },
  {
    id: "12987124",
    name1: "王小虎",
    name2: "王小虎",
    name3: "王小虎",
    address1: "上海市普陀区金沙江路 1518 弄",
    address2: "上海市普陀区金沙江路 1518 弄",
    address3: "上海市普陀区金沙江路 1518 弄",
    amount1: "324",
    amount2: "1.9",
    amount3: 9,
    amount4: "4.43",
    amount5: 12
  },
  {
    id: "12987125",
    name1: "王小虎",
    name2: "王小虎",
    name3: "王小虎",
    address1: "上海市普陀区金沙江路 1518 弄",
    address2: "上海市普陀区金沙江路 1518 弄",
    address3: "上海市普陀区金沙江路 1518 弄",
    amount1: "621",
    amount2: "2.2",
    amount3: 17,
    amount4: "4.43",
    amount5: 12
  },
  {
    id: "12987126",
    name1: "王小虎",
    name2: "王小虎",
    name3: "王小虎",
    address1: "上海市普陀区金沙江路 1518 弄",
    address2: "上海市普陀区金沙江路 1518 弄",
    address3: "上海市普陀区金沙江路 1518 弄",
    amount1: "539",
    amount2: "4.1",
    amount3: 15,
    amount4: "4.43",
    amount5: 12
  }
],

本次 table 数据以上面数据模拟后端传值。我们除了要展示这些字段,还要将后面 5 个 数据作相除或求百分比等,常规写法如下(不用细看):


  
  
  
  
  
  
  
  
  
  
  
  
  
    
  
  
    
  
  
    
  
  
    
  
  
    
  
  
    
  
  
    
  
  
    
  

image

可以看到,仅仅是这十来个字段,就让页面显得非常臃肿,而且很多重复,可想而知如果字段增致几十上百,展示方式更加繁琐,开发维护不易。

用表驱动编程进行优化

表驱动法是《代码大全》里面提到编程方法,适用于多个 if-else 这样形式的代码,这里自然十分适用。

demo 代码的目录结构

image

tableData.js

将要展示的字段按顺序,以一定参数形式的数组结构放在 TABLE_DATA_MAP 对象内,如目前仅有的 tableDemo 即表示为我们上面代码的表结构数组。

/**
 *  参数作用说明:
 *      key: 展示字段
 *      label: 列头名称
 *      width: 列宽
 *      sortable: 是否可筛选
 *      hidden: 隐藏默认展示字段
 *      Dict: 展示用字典
 *      isFixedTwo: 保留两位(可配合分子/分母使用)
 *      isPercent: 百分号展示(配合分子/分母使用)
 *      molecule: 分子
 *      denominator: 分母
 **/

export const TABLE_DATA_MAP = {
  tableDemo: [
    {
      key: "name1",
      label: "姓名1",
      width: 100,
    },
    {
      key: "name2",
      label: "姓名2",
      width: 100,
    },
    {
      key: "name3",
      label: "姓名3",
      width: 100,
    },
    {
      key: "address1",
      label: "地址1",
      width: 180,
    },
    {
      key: "address2",
      label: "地址2",
      width: 180,
    },
    {
      key: "address3",
      label: "地址3",
      width: 180,
    },
    {
      key: "amount1",
      label: "数值1",
      width: 100,
      sortable: true,
    },
    {
      key: "amount2",
      label: "数值2",
      width: 100,
      sortable: true,
    },
    {
      key: "amount3",
      label: "数值3",
      width: 100,
      sortable: true,
    },
    {
      key: "amount4",
      label: "数值4",
      width: 100,
      sortable: true,
    },
    {
      key: "amount5",
      label: "数值5",
      width: 100,
      sortable: true,
    },
    {
      key: "amount6",
      molecule: "amount1",
      denominator: "amount2",
      label: "数值6",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount7",
      molecule: "amount1",
      denominator: "amount3",
      label: "数值7",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount8",
      molecule: "amount1",
      denominator: "amount4",
      label: "数值8",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount9",
      molecule: "amount1",
      denominator: "amount5",
      label: "数值9",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount10",
      molecule: "amount1",
      denominator: "amount2",
      label: "数值10",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount11",
      molecule: "amount1",
      denominator: "amount3",
      label: "数值11",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount12",
      molecule: "amount1",
      denominator: "amount4",
      label: "数值12",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount13",
      molecule: "amount1",
      denominator: "amount5",
      label: "数值13",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
  ]
}

tableColumn.vue

用于对 el-table-colum 的二次封装,配合上面表结构使用(直接看代码,其中 toFixedTwo,toPercent 函数在 mixin 混入)




Table.vue

优化后的页面如下,与之前相比是不是简洁了不少



除了一些必要参数(如 key label)外,你可以在 tableData.js 中自定义任何参数,配合 tableColumn.vue 使用。与此同时,你可以在 tableColumn.vue 上对一些单独字段进行特殊处理

// 对 xxx 字段进行自定义

合计列

此时如果需求要求合计值,也能够通过 TABLE_DATA_MAP 内数据快速实现(表驱动法经典场景,你可以想象不用现在的方法需要几个 if-else)



image

动态列表配置

对于一些列表字段较多的 table 页面,实现列表字段的动态配置的需求就自然而然产生了。
也是得益于我们的表驱动法,我们能够很简单得做到这一点。

更新的目录结构:

image

Table.vue



transfer.vue




tableColumn.vue




store/index.js

这里使用 vuex 存储 currentTableData(现在所配置的列表字段),如果是实际工作中,该数据应该存储于后端数据(后端保存当前用户对该页面的设置,而后在 tableColumn.vue 页获取)

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    currentTableData: {}
  },
  mutations: {
    SET_TABLE_DATA(state, { type, data }) {
      state.currentTableData[type] = data
    }
  },
  actions: {
  },
  modules: {
  }
})
image
image

思路十分简单,本质就是在后端保存一份当前页面用户表格的私人定制 TABLE_DATA_MAP 文件。

2. 前端导出 table

导出 table 表格是很常见的需求,基本上一些统计页面必备。

导出有多种方式:

1. 后端实现数据

主要是后端将生成的 table 数据流给到前端,然后前端生成下载链接,模拟点击效果。

downloadFile(data) {
  if (!data) {
    return
  }
  let url = window.URL.createObjectURL(new Blob([data]));
  let link = document.createElement('a');
  link.style.display = 'none';
  link.href = url;
  link.setAttribute('download', '导出数据.csv');
  document.body.appendChild(link);
  link.click()
}

此种方法适用于有分页且分页量十分大,还有页面数据的展示和导出与后端传递数据(与上面我们需要对数据进行百分比等变化的数据不同)的情况。

2. 前端导出

需要引入 xlsx 和 file-saver

yarn add slsx file-saver -S

前端实现导出常见的又有两种方法:

2.1. 通过页面 Dom 元素获取数据导出

/* eslint-disable */
import FileSaver from 'file-saver'
import XLSX from 'xlsx'

/**
* 导出表格为 excel 格式
* param { Dom } id            // document.getElementById('table')
* param { string } fileName    // test.xlsx
  * param { Boolean } rawBool 纯文本解析将不会解析值
*/

export function exportExcelByDom(id, fileName, rawBool = true) {
  /**
  * element-ui fixed 是生成两个 table,一个仅用于固定
  * 判断要导出的节点中是否有 fixed 的表格
  * 如果有,转换 excel 时先将该 dom 移除,然后 append 回去
  */
  const fix = document.querySelector('.el-table__fixed') || document.querySelector('.el-table__fixed-right');
  let wb;
  /**
  * 从表生成工作簿对象
  */
  if (fix) {
    wb = XLSX.utils.table_to_book(document.getElementById(id).removeChild(fix), { raw: rawBool });
    document.getElementById(id).appendChild(fix);
  } else {
    wb = XLSX.utils.table_to_book(document.getElementById(id), { raw: rawBool });
  }

  /* 获取二进制字符串作为输出 */
  const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' })
  try {
    /**
    * Blob 对象表示一个不可变、原始数据的类文件对象。
    * Blob 表示的不一定是JavaScript原生格式的数据。
    * File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
    * 返回一个新创建的 Blob 对象,其内容由参数中给定的数组串联组成。
    * 设置导出文件名称
    */
    FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName)
  } catch (e) {
    if (typeof console !== 'undefined') console.log(e, wbout)
  }
  return wbout
}

此种方法适用于无分页、导出数据即为页面看到的样子的情况。

2.2 通过 Export2Excel.js

/* eslint-disable */
import FileSaver from 'file-saver'
import XLSX from 'xlsx'

/**
* Export2Excel.js
* param { Array } th            // ['姓名']
* param { Array } keyArray      // ['name']
  * param { Array } jsonData    // 处理好的所有数据
*/

export function export_json_to_excel(th, keyArray, jsonData, defaultTitle) {

  /* original data */
  let data = jsonData.map(v => keyArray.map(j => v[j]));
  data.unshift(th);
  let ws_name = "SheetJS";

  let wb = new Workbook(), ws = sheet_from_array_of_arrays(data);

  /* add worksheet to workbook */
  wb.SheetNames.push(ws_name);
  wb.Sheets[ws_name] = ws;

  let wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });
  let title = defaultTitle || '导出数据'
  FileSaver(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), title + ".xlsx")
};
...
// 其他部分省略

Export2Excel.js 网上有很多版本,大同小异。我对其 export_json_to_excel 函数作了封装,Export2Excel.js 里面也有通过 DOM 导出的方法,但使用时会崩溃,因此通过 DOM 导出推荐 2.1 方法

又得益于我们之前的 TABLE_DATA_MAP 文件,2.2 方法导出基本没有工作量的问题,节省了很大时间(相信看到这里,你能够体会到表驱动法对 table 的意义)

doExport2Excel() {
  const tHeader = ["ID"];
  const keyArray = ["id"];
  this.TABLE_DATA_MAP.tableDemo.forEach(item => {
    tHeader.push(item.label);
    keyArray.push(item.key);
  });
  // 这里 jsonData 应该是所要导出的所有数据,可让后端传值
  const jsonData = this.tableData;
  jsonData.forEach(list => {
    this.TABLE_DATA_MAP.tableDemo.forEach(keyObject => {
      if (keyObject.isPercent && keyObject.isPercent === true) {
        list[keyObject.key] = this.toPercent(
          list[keyObject.molecule],
          list[keyObject.denominator]
        );
      } else if (keyObject.isFixedTwo && keyObject.isFixedTwo === true) {
        list[keyObject.key] = this.toFixedTwo(
          list[keyObject.molecule],
          list[keyObject.denominator]
        );
      }
    });
  });
  export_json_to_excel(tHeader, keyArray, jsonData, "数据导出");
},

这种方法比 2.1 好在:很多时候导出的 table 列与展示的是不一致的(如通过列表配置,展示字段少于导出字段情况),我们甚至可以在导出时对某些字段作不同于页面展示的数据处理。

与此同时其解决了后端导出数据会与展示数据不一致的问题,在主动性和灵活性上更胜一筹。


花了快一天时间写 demo + 整理,暂时先写这么多

不定时更新。。。

以上完整代码看 这里

整理不易,别忘了点个赞!

你可能感兴趣的:(【经验总结】vue + element-ui 踩坑—— table 篇)