【經(jīng)驗(yàn)總結(jié)】vue + element-ui 踩坑—— table 篇

工作一年医清,主要職責(zé)是負(fù)責(zé)公司后臺(tái)管理平臺(tái)的開發(fā)與維護(hù)放可。此間面對(duì)各種業(yè)務(wù)需求法褥,通過(guò)面向谷歌編程等常見方式茫叭,積累了一些微不足道的經(jīng)驗(yàn)。

本篇為總結(jié)的第一篇(也許有其他篇)—— table 篇

對(duì)于后臺(tái)管理平臺(tái)來(lái)說(shuō)挖胃,最必不可少的元素就是 table 表格杂靶,幾乎每個(gè)頁(yè)面都涉及到表格的使用,甚至常作為是頁(yè)面的主體部分酱鸭。
因此吗垮,如何維護(hù)這些 table,并且根據(jù)業(yè)務(wù)需求完善不同解決方案凹髓,便是此文所會(huì)表達(dá)的內(nèi)容烁登。

主要技術(shù)棧如題為 vue 全家桶配合 element-ui(其他技術(shù)棧其實(shí)思路是類似的),因此主要還是對(duì) el-table 等的再封裝等蔚舀。element-ui 的文檔已經(jīng)非常通俗易懂饵沧,本文不涉及一些文檔上已有的基本用法。

接下來(lái)我會(huì)模擬一些簡(jiǎn)單的數(shù)據(jù)來(lái)展示一些業(yè)務(wù)問題的解決方案赌躺,其目的在展示思路狼牺,代碼以簡(jiǎn)易為主。

1. 自定義列表項(xiàng)

很多時(shí)候我們需要將后端數(shù)據(jù)作展示優(yōu)化

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

本次 table 數(shù)據(jù)以上面數(shù)據(jù)模擬后端傳值礼患。我們除了要展示這些字段是钥,還要將后面 5 個(gè) 數(shù)據(jù)作相除或求百分比等,常規(guī)寫法如下(不用細(xì)看):

<el-table
  class="table"
  :data="tableData"
  border
  show-summary
  highlight-current-row
  style="width: 100%"
>
  <el-table-column prop="id" label="ID" width="180"></el-table-column>
  <el-table-column prop="name1" label="姓名1" width="100"></el-table-column>
  <el-table-column prop="name2" label="姓名2" width="100"></el-table-column>
  <el-table-column prop="name3" label="姓名3" width="100"></el-table-column>
  <el-table-column prop="address1" label="地址1" width="180" show-overflow-tooltip></el-table-column>
  <el-table-column prop="address2" label="地址2" width="180" show-overflow-tooltip></el-table-column>
  <el-table-column prop="address3" label="地址3" width="180" show-overflow-tooltip></el-table-column>
  <el-table-column prop="amount1" sortable label="數(shù)值1"></el-table-column>
  <el-table-column prop="amount2" sortable label="數(shù)值2"></el-table-column>
  <el-table-column prop="amount3" sortable label="數(shù)值3"></el-table-column>
  <el-table-column prop="amount4" sortable label="數(shù)值4"></el-table-column>
  <el-table-column prop="amount5" sortable label="數(shù)值5"></el-table-column>
  <el-table-column prop="amount6" sortable label="數(shù)值6">
    <template slot-scope="scope">
      <span>{{toFixedTwo(scope.row.amount1, scope.row.amount2)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount7" sortable label="數(shù)值7">
    <template slot-scope="scope">
      <span>{{toFixedTwo(scope.row.amount1, scope.row.amount3)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount8" sortable label="數(shù)值8">
    <template slot-scope="scope">
      <span>{{toFixedTwo(scope.row.amount1, scope.row.amount4)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount9" sortable label="數(shù)值9">
    <template slot-scope="scope">
      <span>{{toFixedTwo(scope.row.amount1, scope.row.amount5)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount10" sortable label="數(shù)值10">
    <template slot-scope="scope">
      <span>{{toPercent(scope.row.amount1, scope.row.amount2)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount11" sortable label="數(shù)值11">
    <template slot-scope="scope">
      <span>{{toPercent(scope.row.amount1, scope.row.amount3)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount12" sortable label="數(shù)值12">
    <template slot-scope="scope">
      <span>{{toPercent(scope.row.amount1, scope.row.amount4)}}</span>
      <span>
    </template>
  </el-table-column>
  <el-table-column prop="amount13" sortable label="數(shù)值13">
    <template slot-scope="scope">
      <span>{{toPercent(scope.row.amount1, scope.row.amount5)}}</span>
      <span>
    </template>
  </el-table-column>
</el-table>
image

可以看到缅叠,僅僅是這十來(lái)個(gè)字段悄泥,就讓頁(yè)面顯得非常臃腫,而且很多重復(fù)肤粱,可想而知如果字段增致幾十上百弹囚,展示方式更加繁瑣,開發(fā)維護(hù)不易领曼。

用表驅(qū)動(dòng)編程進(jìn)行優(yōu)化

表驅(qū)動(dòng)法是《代碼大全》里面提到編程方法鸥鹉,適用于多個(gè) if-else 這樣形式的代碼蛮穿,這里自然十分適用。

demo 代碼的目錄結(jié)構(gòu)

image

tableData.js

將要展示的字段按順序宋舷,以一定參數(shù)形式的數(shù)組結(jié)構(gòu)放在 TABLE_DATA_MAP 對(duì)象內(nèi)绪撵,如目前僅有的 tableDemo 即表示為我們上面代碼的表結(jié)構(gòu)數(shù)組。

/**
 *  參數(shù)作用說(shuō)明:
 *      key: 展示字段
 *      label: 列頭名稱
 *      width: 列寬
 *      sortable: 是否可篩選
 *      hidden: 隱藏默認(rèn)展示字段
 *      Dict: 展示用字典
 *      isFixedTwo: 保留兩位(可配合分子/分母使用)
 *      isPercent: 百分號(hào)展示(配合分子/分母使用)
 *      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: "數(shù)值1",
      width: 100,
      sortable: true,
    },
    {
      key: "amount2",
      label: "數(shù)值2",
      width: 100,
      sortable: true,
    },
    {
      key: "amount3",
      label: "數(shù)值3",
      width: 100,
      sortable: true,
    },
    {
      key: "amount4",
      label: "數(shù)值4",
      width: 100,
      sortable: true,
    },
    {
      key: "amount5",
      label: "數(shù)值5",
      width: 100,
      sortable: true,
    },
    {
      key: "amount6",
      molecule: "amount1",
      denominator: "amount2",
      label: "數(shù)值6",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount7",
      molecule: "amount1",
      denominator: "amount3",
      label: "數(shù)值7",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount8",
      molecule: "amount1",
      denominator: "amount4",
      label: "數(shù)值8",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount9",
      molecule: "amount1",
      denominator: "amount5",
      label: "數(shù)值9",
      width: 100,
      sortable: true,
      isFixedTwo: true,
      hidden: true,
    },
    {
      key: "amount10",
      molecule: "amount1",
      denominator: "amount2",
      label: "數(shù)值10",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount11",
      molecule: "amount1",
      denominator: "amount3",
      label: "數(shù)值11",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount12",
      molecule: "amount1",
      denominator: "amount4",
      label: "數(shù)值12",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
    {
      key: "amount13",
      molecule: "amount1",
      denominator: "amount5",
      label: "數(shù)值13",
      width: 100,
      sortable: true,
      isPercent: true,
      hidden: true,
    },
  ]
}

tableColumn.vue

用于對(duì) el-table-colum 的二次封裝祝蝠,配合上面表結(jié)構(gòu)使用(直接看代碼,其中 toFixedTwo幻碱,toPercent 函數(shù)在 mixin 混入)

<template>
  <div>
    <div v-for="(item, index) in TABLE_DATA_MAP[tableName]" :key="index + item">
      <el-table-column
        :label="item.label"
        :key="index + item"
        :min-width="item.width"
        :sortable="item.sortable"
        :prop="item.key"
        show-overflow-tooltip
      >
        <template slot-scope="scope">
          <span v-if="!item.hidden">{{ scope.row[item.key] }}</span>
          <span v-if="item.Dict">{{ item.Dict[scope.row[item.key]] }}</span>
          <span
            v-if="item.isFixedTwo"
          >{{toFixedTwo(scope.row[item.molecule], scope.row[item.denominator])}}</span>
          <span
            v-if="item.isPercent"
          >{{toPercent(scope.row[item.molecule], scope.row[item.denominator])}}</span>
        </template>
      </el-table-column>
    </div>
  </div>
</template>

<script>
import { TABLE_DATA_MAP } from "@/utils/tableData";

export default {
  name: "table-column",
  props: {
    tableName: String
  },
  data() {
    return {
      TABLE_DATA_MAP
    };
  }
};
</script>

Table.vue

優(yōu)化后的頁(yè)面如下绎狭,與之前相比是不是簡(jiǎn)潔了不少

<template>
  <div>
    <el-table
      class="table"
      :data="tableData"
      border
      show-summary
      highlight-current-row
      style="width: 100%"
    >
      <el-table-column prop="id" label="ID" width="120" fixed="left"></el-table-column>
      <table-column tableName="tableDemo"></table-column>
    </el-table>
  </div>
</template>
<script>
export default {
  data() {
    return {
      tableData: [
        ...
      ],
    };
  },
  components: {
    "table-column": () => import("@/components/tableColumn")
  },
  methods: {
    getSummaries(param) {
     ...
    }
  }
};
</script>

除了一些必要參數(shù)(如 key label)外,你可以在 tableData.js 中自定義任何參數(shù)褥傍,配合 tableColumn.vue 使用儡嘶。與此同時(shí),你可以在 tableColumn.vue 上對(duì)一些單獨(dú)字段進(jìn)行特殊處理

// 對(duì) xxx 字段進(jìn)行自定義
<template slot-scope="scope">
    <div v-if="item.key === 'xxx'">
        <span>{{(scope.row['xxx'] + scope.row['xxx1']+ scope.row['xxx2']).toFixed(2)}}</span>
     </div>
</template>

合計(jì)列

此時(shí)如果需求要求合計(jì)值恍风,也能夠通過(guò) TABLE_DATA_MAP 內(nèi)數(shù)據(jù)快速實(shí)現(xiàn)(表驅(qū)動(dòng)法經(jīng)典場(chǎng)景蹦狂,你可以想象不用現(xiàn)在的方法需要幾個(gè) if-else)

<template>
  <div>
    <el-table
      class="table"
      :data="tableData"
      border
      show-summary
      :summary-method="getSummaries"
      highlight-current-row
      style="width: 100%"
    >
      <el-table-column prop="id" label="ID" width="120" fixed="left"></el-table-column>
      <table-column tableName="tableDemo"></table-column>
    </el-table>
  </div>
</template>
<script>
import { TABLE_DATA_MAP } from "@/utils/tableData";
export default {
  data() {
    return {
      TABLE_DATA_MAP,
      tableData: [
        ...
      ],
      // totalData 模擬 amount 初始合計(jì)值(很可能合計(jì)值非簡(jiǎn)單的疊加,一般由后端傳遞)
      totalData: {
        amount1: 1883,
        amount2: 15.83,
        amount3: 63,
        amount4: 22.15,
        amount5: 60
      }
    };
  },
  ...
  methods: {
    getSummaries({ columns }) {
      let sums = [];
      columns.forEach((column, index) => {
        if (column.property === "id") {
          sums[index] = "合計(jì)";
          return;
        } else {
          this.TABLE_DATA_MAP.tableDemo.forEach(keyObject => {
            if (keyObject.key === column.property) {
              if (keyObject.isPercent && keyObject.isPercent === true) {
                sums[index] = this.toPercent(
                  this.totalData[keyObject.molecule],
                  this.totalData[keyObject.denominator]
                );
              } else if (
                keyObject.isFixedTwo &&
                keyObject.isFixedTwo === true
              ) {
                sums[index] = this.toFixedTwo(
                  this.totalData[keyObject.molecule],
                  this.totalData[keyObject.denominator]
                );
              } else {
                sums[index] = this.totalData[column.property];
              }
            }
          });
        }
      });
      return sums;
    }
  }
};
</script>
image

動(dòng)態(tài)列表配置

對(duì)于一些列表字段較多的 table 頁(yè)面朋贬,實(shí)現(xiàn)列表字段的動(dòng)態(tài)配置的需求就自然而然產(chǎn)生了凯楔。
也是得益于我們的表驅(qū)動(dòng)法,我們能夠很簡(jiǎn)單得做到這一點(diǎn)锦募。

更新的目錄結(jié)構(gòu):

image

Table.vue

<template>
  <div>
    <el-button style="margin-bottom:10px;" type="primary" @click="dialogs.configuration.show=true">列表配置</el-button>
    <el-table
      ...
    </el-table>
    <el-dialog
      :title="dialogs.configuration.title"
      :visible.sync="dialogs.configuration.show"
      :close-on-click-modal="false"
      width="700px"
    >
      <transfer
        :model="dialogs.configuration.data"
        :tableName="'tableDemo'"
        @close="dialogs.configuration.show=false"
        @editSuc="editSuc('configuration')"
      ></transfer>
    </el-dialog>
  </div>
</template>
<script>
import { TABLE_DATA_MAP } from "@/utils/tableData";
const tableData = [
  ...
];
export default {
  data() {
    return {
      TABLE_DATA_MAP,
      tableData: [],
      totalData: {
        ...
      },
      dialogs: {
        configuration: {
          title: "動(dòng)態(tài)列表配置",
          data: "",
          show: false
        }
      }
    };
  },
  components: {...},
  mounted(){
    this.getList()
  },
  methods: {
    getList() {
      // 模擬數(shù)據(jù)獲取
      setTimeout(() => {
        this.tableData = tableData;
      }, 1000);
    },
    getSummaries({ columns }) {
      ...
    },
    editSuc(obj) {
      this.dialogs[obj].show = false;
      this.$message({
        message: "提交成功",
        type: "success"
      });
      this.tableData = []
      this.getList()
    }
  }
};
</script>

transfer.vue

<template>
  <div>
    <el-transfer
      filterable
      :filter-method="filterMethod"
      filter-placeholder="請(qǐng)輸入表頭名"
      v-model="value"
      :data="data"
    ></el-transfer>
    <el-button type="primary" @click="doSubmit()">提交</el-button>
  </div>
</template>

<script>
import { TABLE_DATA_MAP } from "@/utils/tableData";
export default {
  props: {
    tableName: String
  },
  data() {
    return {
      TABLE_DATA_MAP,
      data: TABLE_DATA_MAP[this.tableName], // 當(dāng)前頁(yè)默認(rèn)值
      value: [], // 現(xiàn)在在 transfer 右測(cè)的值
      filterMethod(query, item) {
        return item.label.indexOf(query) > -1;
      }
    };
  },
  computed: {
    currentTableData() {
      return this.$store.state.currentTableData;
    }
  },
  methods: {
    doSubmit() {
      let sData = [];
      this.value.map(items => {
        this.TABLE_DATA_MAP[this.tableName].forEach(item => {
          if (item.key === items) {
            sData.push(item);
          }
        });
      });
      // 這里如果是實(shí)際項(xiàng)目應(yīng)該會(huì)給后端接口傳值來(lái)保存當(dāng)前用戶該頁(yè)面的設(shè)置
      this.$store.commit("SET_TABLE_DATA", {
        type: this.tableName,
        data: sData
      });
      this.$emit("editSuc");
    }
  },
  mounted() {
    this.value = [];
    // 這里如果是實(shí)際項(xiàng)目 currentTableData 應(yīng)該是后端獲取數(shù)據(jù)摆屯,而不是 vuex 獲取
    if (this.currentTableData && this.currentTableData[this.tableName]) {
      this.currentTableData[this.tableName].forEach(item => {
        if (this.TABLE_DATA_MAP[this.tableName].includes(item.key)) {
          this.value.push(item.key);
        }
      });
    }
  }
};
</script>

tableColumn.vue

<template>
  <div>
    <div
      v-for="(item, index) in ((currentTableData && currentTableData[tableName])? currentTableData[tableName]: TABLE_DATA_MAP[tableName])"
      :key="index + item"
    >
      <el-table-column>
        ...
      </el-table-column>
    </div>
  </div>
</template>

<script>
import { TABLE_DATA_MAP } from "@/utils/tableData";

export default {
  name: "table-column",
  props: {
    tableName: String
  },
  data() {
    return {
      TABLE_DATA_MAP
    };
  },
  computed: {
    // currentTableData 實(shí)際工作中應(yīng)該是保存在后端的值
    currentTableData() {
      return this.$store.state.currentTableData;
    }
  }
};
</script>

store/index.js

這里使用 vuex 存儲(chǔ) currentTableData(現(xiàn)在所配置的列表字段),如果是實(shí)際工作中糠亩,該數(shù)據(jù)應(yīng)該存儲(chǔ)于后端數(shù)據(jù)(后端保存當(dāng)前用戶對(duì)該頁(yè)面的設(shè)置虐骑,而后在 tableColumn.vue 頁(yè)獲取)

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

思路十分簡(jiǎn)單赎线,本質(zhì)就是在后端保存一份當(dāng)前頁(yè)面用戶表格的私人定制 TABLE_DATA_MAP 文件廷没。

2. 前端導(dǎo)出 table

導(dǎo)出 table 表格是很常見的需求,基本上一些統(tǒng)計(jì)頁(yè)面必備垂寥。

導(dǎo)出有多種方式:

1. 后端實(shí)現(xiàn)數(shù)據(jù)

主要是后端將生成的 table 數(shù)據(jù)流給到前端颠黎,然后前端生成下載鏈接,模擬點(diǎn)擊效果矫废。

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', '導(dǎo)出數(shù)據(jù).csv');
  document.body.appendChild(link);
  link.click()
}

此種方法適用于有分頁(yè)且分頁(yè)量十分大盏缤,還有頁(yè)面數(shù)據(jù)的展示和導(dǎo)出與后端傳遞數(shù)據(jù)(與上面我們需要對(duì)數(shù)據(jù)進(jìn)行百分比等變化的數(shù)據(jù)不同)的情況。

2. 前端導(dǎo)出

需要引入 xlsx 和 file-saver

yarn add slsx file-saver -S

前端實(shí)現(xiàn)導(dǎo)出常見的又有兩種方法:

2.1. 通過(guò)頁(yè)面 Dom 元素獲取數(shù)據(jù)導(dǎo)出

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

/**
* 導(dǎo)出表格為 excel 格式
* param { Dom } id            // document.getElementById('table')
* param { string } fileName    // test.xlsx
  * param { Boolean } rawBool 純文本解析將不會(huì)解析值
*/

export function exportExcelByDom(id, fileName, rawBool = true) {
  /**
  * element-ui fixed 是生成兩個(gè) table蓖扑,一個(gè)僅用于固定
  * 判斷要導(dǎo)出的節(jié)點(diǎn)中是否有 fixed 的表格
  * 如果有唉铜,轉(zhuǎn)換 excel 時(shí)先將該 dom 移除,然后 append 回去
  */
  const fix = document.querySelector('.el-table__fixed') || document.querySelector('.el-table__fixed-right');
  let wb;
  /**
  * 從表生成工作簿對(duì)象
  */
  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 });
  }

  /* 獲取二進(jìn)制字符串作為輸出 */
  const wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'array' })
  try {
    /**
    * Blob 對(duì)象表示一個(gè)不可變律杠、原始數(shù)據(jù)的類文件對(duì)象潭流。
    * Blob 表示的不一定是JavaScript原生格式的數(shù)據(jù)竞惋。
    * File 接口基于Blob,繼承了 blob 的功能并將其擴(kuò)展使其支持用戶系統(tǒng)上的文件灰嫉。
    * 返回一個(gè)新創(chuàng)建的 Blob 對(duì)象拆宛,其內(nèi)容由參數(shù)中給定的數(shù)組串聯(lián)組成。
    * 設(shè)置導(dǎo)出文件名稱
    */
    FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), fileName)
  } catch (e) {
    if (typeof console !== 'undefined') console.log(e, wbout)
  }
  return wbout
}

此種方法適用于無(wú)分頁(yè)讼撒、導(dǎo)出數(shù)據(jù)即為頁(yè)面看到的樣子的情況浑厚。

2.2 通過(guò) 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    // 處理好的所有數(shù)據(jù)
*/

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 || '導(dǎo)出數(shù)據(jù)'
  FileSaver(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), title + ".xlsx")
};
...
// 其他部分省略

Export2Excel.js 網(wǎng)上有很多版本,大同小異根盒。我對(duì)其 export_json_to_excel 函數(shù)作了封裝钳幅,Export2Excel.js 里面也有通過(guò) DOM 導(dǎo)出的方法,但使用時(shí)會(huì)崩潰炎滞,因此通過(guò) DOM 導(dǎo)出推薦 2.1 方法

又得益于我們之前的 TABLE_DATA_MAP 文件敢艰,2.2 方法導(dǎo)出基本沒有工作量的問題,節(jié)省了很大時(shí)間(相信看到這里册赛,你能夠體會(huì)到表驅(qū)動(dòng)法對(duì) table 的意義)

doExport2Excel() {
  const tHeader = ["ID"];
  const keyArray = ["id"];
  this.TABLE_DATA_MAP.tableDemo.forEach(item => {
    tHeader.push(item.label);
    keyArray.push(item.key);
  });
  // 這里 jsonData 應(yīng)該是所要導(dǎo)出的所有數(shù)據(jù)钠导,可讓后端傳值
  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, "數(shù)據(jù)導(dǎo)出");
},

這種方法比 2.1 好在:很多時(shí)候?qū)С龅?table 列與展示的是不一致的(如通過(guò)列表配置,展示字段少于導(dǎo)出字段情況)森瘪,我們甚至可以在導(dǎo)出時(shí)對(duì)某些字段作不同于頁(yè)面展示的數(shù)據(jù)處理牡属。

與此同時(shí)其解決了后端導(dǎo)出數(shù)據(jù)會(huì)與展示數(shù)據(jù)不一致的問題,在主動(dòng)性和靈活性上更勝一籌柜砾。


花了快一天時(shí)間寫 demo + 整理湃望,暫時(shí)先寫這么多

不定時(shí)更新。痰驱。证芭。

以上完整代碼看 這里

整理不易,別忘了點(diǎn)個(gè)贊??担映!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废士,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝇完,更是在濱河造成了極大的恐慌官硝,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件短蜕,死亡現(xiàn)場(chǎng)離奇詭異氢架,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)朋魔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門岖研,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事孙援『τ伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拓售,是天一觀的道長(zhǎng)窥摄。 經(jīng)常有香客問我,道長(zhǎng)础淤,這世上最難降的妖魔是什么崭放? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮值骇,結(jié)果婚禮上莹菱,老公的妹妹穿的比我還像新娘。我一直安慰自己吱瘩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布迹缀。 她就那樣靜靜地躺著使碾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祝懂。 梳的紋絲不亂的頭發(fā)上票摇,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音砚蓬,去河邊找鬼矢门。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灰蛙,可吹牛的內(nèi)容都是我干的祟剔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摩梧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼物延!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起仅父,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叛薯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后笙纤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耗溜,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年省容,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖拴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蓉冈,死狀恐怖城舞,靈堂內(nèi)的尸體忽然破棺而出轩触,到底是詐尸還是另有隱情,我是刑警寧澤家夺,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布脱柱,位于F島的核電站,受9級(jí)特大地震影響拉馋,放射性物質(zhì)發(fā)生泄漏榨为。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一煌茴、第九天 我趴在偏房一處隱蔽的房頂上張望随闺。 院中可真熱鬧,春花似錦蔓腐、人聲如沸矩乐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)散罕。三九已至,卻和暖如春傀蓉,著一層夾襖步出監(jiān)牢的瞬間欧漱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工葬燎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留误甚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓谱净,卻偏偏與公主長(zhǎng)得像窑邦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子岳遥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354