目录
- 需求 - 要实现的效果
- 初始代码
- 代码升级(可供多个表格使用)
- `CommonTable.vue 子组件 `
- 使用子组件1 - `父组件 - 图1~图3使用`
- 效果展示
- 使用子组件2 - `父组件 - 图4使用`
- 效果展示
- 注意
- 【代码优化 - 解决bug】
需求 - 要实现的效果
父组件中 info 数据示例
const info = {itemMap: {警告: [{total: 28,cfzl: '1',cfzlView: '警告',wfxl: '12',wfxlView: '超速行驶',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 3,cfzl: '1',cfzlView: '警告',wfxl: '17',wfxlView: '未低速通过',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 6,cfzl: '1',cfzlView: '警告',wfxl: '26',wfxlView: '违法停车',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 21,cfzl: '1',cfzlView: '警告',wfxl: '28',wfxlView: '违法装载',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 3,cfzl: '1',cfzlView: '警告',wfxl: '49',wfxlView: '其他影响安全行为',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 1,cfzl: '1',cfzlView: '警告',wfxl: '28',wfxlView: '违法装载',jtfs: 'B21',jtfsView: '中型栏板半挂车'}],罚款: [{total: 56,cfzl: '2',cfzlView: '罚款',wfxl: '12',wfxlView: '超速行驶',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 6,cfzl: '2',cfzlView: '罚款',wfxl: '17',wfxlView: '未低速通过',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 12,cfzl: '2',cfzlView: '罚款',wfxl: '26',wfxlView: '违法停车',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 42,cfzl: '2',cfzlView: '罚款',wfxl: '28',wfxlView: '违法装载',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 6,cfzl: '2',cfzlView: '罚款',wfxl: '49',wfxlView: '其他影响安全行为',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{total: 2,cfzl: '2',cfzlView: '罚款',wfxl: '28',wfxlView: '违法装载',jtfs: 'B21',jtfsView: '中型栏板半挂车'}]},columns: [{// total: 28,// cfzl: '1',// cfzlView: '警告',// wfxl: '12',// wfxlView: '超速行驶',jtfs: 'B11',jtfsView: '重型栏板半挂车'},{// total: 1,// cfzl: '1',// cfzlView: '警告',// wfxl: '28',// wfxlView: '违法装载',jtfs: 'B21',jtfsView: '中型栏板半挂车'}]
}
初始代码
父组件
<!-- info 数据来源 → info 数据示例 -->
<CommonTable :info="info"/>
CommonTable.vue 子组件
<template><el-table:data="tableData"borderstripemax-height="400"size="mini":span-method="firstColMergeSpan"><el-table-column align="center" prop="cfzl" label="处罚种类" /><el-table-column align="center" prop="wfxwfl" label="违法行为分类" /><el-table-columnv-for="(item, index) in columns":key="index"align="center":prop="item.jtfs":label="item.jtfsView || '-'"><template slot-scope="{ row, $index }"><span>{{ row[item.jtfs] | noDataFilter }}</span></template></el-table-column><el-table-column align="center" prop="xj" label="小计" /></el-table>
</template><script>
export default {name: 'CommonTable',components: {},props: {info: {type: Object,required: true}},data() {return {spanArr: []}},computed: {tableData() {const list = []Object.entries(this.info?.itemMap || {}).forEach((i) => {i[1]?.forEach((ii) => {list.push({// cfzl: ii.cfzlView,cfzl: i[0],wfxwfl: ii.wfxlView,[ii.jtfs]: ii.total,jtfs: ii.jtfs})})})return list.map((item) => {return {...item,xj: this.columnKeyList.reduce((a, b) => {return a + (item[b] || 0)}, 0)}})},columns() {return this.info.columns || []},columnKeyList() {return this.columns.map((item) => item.jtfs)}},watch: {info() {this.xjPosition()this.firstColMergeCount()}},created() {},methods: {// 计算小计行插入位置xjPosition(Human = this.tableData) {const doctorMap = {}for (let i = 0; i < Human.length; i++) {// 找出相同名称的行数const doctorName = Human[i].cfzlif (doctorMap[doctorName] !== undefined) {doctorMap[doctorName].push(i)} else {doctorMap[doctorName] = [i]}}const keyArr = []for (const k in doctorMap) {// 取出key并倒序,防止正序插入会影响行下标keyArr.unshift(k)}keyArr.forEach((ele, index) => {const lastIndex = doctorMap[ele][doctorMap[ele].length - 1] // 找出相同名称最后一行插入合计数据const obj = this.xjRowDataCalc(Human, ele) // 计算出小计行数据Human.splice(lastIndex + 1, 0, obj) // 插入})},// 小计行计算xjRowDataCalc(data, name) {const obj = {cfzl: name, // 第一列用于合并单元格wfxwfl: '小计'}this.columnKeyList.forEach((key) => {obj[key] = 0})data.forEach((item) => {// “处罚种类” 相同的加起来if (item.cfzl === name) {this.columnKeyList.forEach((key) => {obj[key] += Number(item[key] || 0)})}})obj.xj = this.columnKeyList.reduce((a, b) => {return a + (obj[b] || 0)}, 0)return obj},// 合并单元格firstColMergeSpan({ row, column, rowIndex, columnIndex }) {if (columnIndex === 0) {const _row = this.spanArr[rowIndex]const _col = _row > 0 ? 1 : 0return {rowspan: _row,colspan: _col}}},// 计算要合并的单元格firstColMergeCount() {let contactDot = 0this.spanArr = []this.tableData.forEach((item, index) => {item.index = indexif (index === 0) {this.spanArr.push(1)} else {// 根据相同 “处罚种类” 来合并if (item.cfzl === this.tableData[index - 1].cfzl) {this.spanArr[contactDot] += 1this.spanArr.push(0)} else {contactDot = indexthis.spanArr.push(1)}}})}}
}
</script><style lang='scss' scoped>
</style>
代码升级(可供多个表格使用)
图1
图2
图3
图4
根据接口返回数据不同(数据格式一致,只是部分字段名不一致),向子组件传入不同的字段名。
CommonTable.vue 子组件
<template><el-table:data="tableData"borderstripemax-height="400"size="mini":span-method="firstColMergeSpan"><!-- 第一列 --><el-table-column align="center" :prop="oneColPropField" :label="oneColLabelField" /><!-- 第二列 --><el-table-column align="center" :prop="twoColPropField" :label="twoColLabelField" /><!-- 其他数量列 --><el-table-columnv-for="(item, index) in columns":key="index"align="center":prop="item[countColPropsField]":label="item[countColLabelField] || '-'"><template slot-scope="{ row, $index }"><span>{{ row[item[countColPropsField]] | noDataFilter }}</span></template></el-table-column><!-- “小计”列 --><el-table-column align="center" prop="xj" label="小计" /></el-table>
</template><script>
export default {name: 'CommonTable',components: {},props: {info: {type: Object,required: true},// 自定义表格列selfColumns: {type: Array,default: () => []},// 表格列非自定义时(接口获取)列字段名columnKeyField: {type: String,default: 'jtfs'},// 第一列字段名oneColPropField: {type: String,required: true},// 第一列展示header文本oneColLabelField: {type: String,required: true},// 第一列数据来源字段名oneColDataField: {type: String,required: true},// 第二列字段名twoColPropField: {type: String,required: true},// 第二列展示header文本twoColLabelField: {type: String,required: true},// 第二列数据来源字段名twoColDataField: {type: String,required: true},// 其他数量 列 字段名countColPropsField: {type: String,default: 'jtfs'},// 其他数量 列 展示header文本字段名countColLabelField: {type: String,default: 'jtfsView'},// 其他数量 列 数据来源字段名countColDataField: {type: String,default: 'total'}},data() {return {spanArr: []}},computed: {// 表格数据处理tableData() {const list = []Object.entries(this.info?.itemMap || {}).forEach((i) => {i[1]?.forEach((ii) => {list.push({// [this.oneColPropField]: ii[this.oneColDataField] // 第一列数据(第一列数据部分表格 ii 中无第一列数据,所以使用↓↓↓下一行代码 i[0] 取第一列数据)[this.oneColPropField]: i[0], // 第一列数据[this.twoColPropField]: ii[this.twoColDataField], // 第二列数据[ii[this.countColPropsField]]: ii[this.countColDataField] // 其他数量列数据// jtfs: ii.jtfs})})})return list.map((item) => {return {...item,// 计算小计数量xj: this.columnKeyList.reduce((a, b) => {return a + (item[b] || 0)}, 0)}})},columns() {/*** 表格列获取* 父组件有传表格列 selfColumns 就用 selfColumns* 否则用接口获的表格列*/return (this.selfColumns.length && this.selfColumns) || this.info.columns || []},columnKeyList() {// 获取表格每列字段名组成数组return this.columns.map((item) => item[this.columnKeyField])}},watch: {info() {this.xjPosition()this.oneColMergeCount()}},created() {},methods: {// 计算小计行插入位置xjPosition(Human = this.tableData) {const doctorMap = {}for (let i = 0; i < Human.length; i++) {// 找出相同名称的行数const doctorName = Human[i][this.oneColPropField]if (doctorMap[doctorName] !== undefined) {doctorMap[doctorName].push(i)} else {doctorMap[doctorName] = [i]}}const keyArr = []for (const k in doctorMap) {// 取出key并倒序,防止正序插入会影响行下标keyArr.unshift(k)}keyArr.forEach((ele, index) => {const lastIndex = doctorMap[ele][doctorMap[ele].length - 1] // 找出相同名称最后一行插入合计数据const obj = this.xjRowDataCalc(Human, ele) // 计算出小计行数据Human.splice(lastIndex + 1, 0, obj) // 插入})},// 小计行数据计算xjRowDataCalc(data, name) {const obj = {[this.oneColPropField]: name, // 第一列用于合并单元格[this.twoColPropField]: '小计' // 第二列数据}this.columnKeyList.forEach((key) => {obj[key] = 0 // 其他书两列数据})data.forEach((item) => {// 第一列 oneColPropField 数据相同的加起来if (item[this.oneColPropField] === name) {this.columnKeyList.forEach((key) => {obj[key] += Number(item[key] || 0)})}})// 小计列数据总和(小计行和小计列交汇处数据)obj.xj = this.columnKeyList.reduce((a, b) => {return a + (obj[b] || 0)}, 0)return obj},// 合并单元格firstColMergeSpan({ row, column, rowIndex, columnIndex }) {if (columnIndex === 0) {const _row = this.spanArr[rowIndex]const _col = _row > 0 ? 1 : 0return {rowspan: _row,colspan: _col}}},// 计算要合并的单元格oneColMergeCount() {let contactDot = 0this.spanArr = []this.tableData.forEach((item, index) => {item.index = indexif (index === 0) {this.spanArr.push(1)} else {// 第一列相同的合并if (item[this.oneColPropField] === this.tableData[index - 1][this.oneColPropField]) {this.spanArr[contactDot] += 1this.spanArr.push(0)} else {contactDot = indexthis.spanArr.push(1)}}})}}
}
</script><style lang='scss' scoped>
</style>
使用子组件1 - 父组件 - 图1~图3使用
<!-- info 数据来源 → info 数据示例 -->
<CommonTable:info="info"one-col-prop-field="cfzl"one-col-label-field="处罚种类"one-col-data-field="cfzlView"two-col-prop-field="wfxwfl"two-col-label-field="违法行为分类"two-col-data-field="wfxlView"
/>
效果展示
使用子组件2 - 父组件 - 图4使用
<CommonTable:info="info"one-col-prop-field="cfzl"one-col-label-field="处罚种类"one-col-data-field="cfzlView"two-col-prop-field="wfxwfl"two-col-label-field="违法行为分类"two-col-data-field="wfxlView"column-key-field="timenum"count-col-props-field="timenum"count-col-label-field="label":self-columns="columns"
/><script>export default {data() {return {columns: [...Array(24).keys()].map((item) => {return {timenum: `${item}`,label: `${item}-${item + 1}`}}),info: {itemMap: {警告: [{timenum: 17,total: 9,cfzl: '1',cfzlView: '警告',wfxl: '69',wfxlView: '其他影响安全行为',jtfs: null,jtfsView: null},{timenum: 17,total: 3,cfzl: '1',cfzlView: '警告',wfxl: '58',wfxlView: '违法上道路行驶',jtfs: null,jtfsView: null}]},columns: []}}}}
</script>
效果展示
注意
- 使用子组件1 和 使用子组件2 中 info 数据不同
【代码优化 - 解决bug】
解决数据重复问题
<template><el-table:data="tableData"borderstripemax-height="400"size="mini":span-method="firstColMergeSpan"><!-- 第一列 --><el-table-column align="center" :prop="oneColPropField" :label="oneColLabelField" /><!-- 第二列 --><el-table-column align="center" :prop="twoColPropField" :label="twoColLabelField" /><!-- 其他数量列 --><el-table-columnv-for="(item, index) in columns":key="index"align="center":prop="item[countColPropsField]":label="item[countColLabelField] || '-'"><template slot-scope="{ row, $index }"><span>{{ row[item[countColPropsField]] | noDataFilter }}</span></template></el-table-column><!-- “小计”列 --><el-table-column align="center" prop="xj" label="小计" /></el-table>
</template><script>
export default {name: 'CommonTable',components: {},props: {info: {type: Object,required: true},// 自定义表格列selfColumns: {type: Array,default: () => []},// 表格列非自定义时(接口获取)列字段名columnKeyField: {type: String,default: 'jtfs'},// 第一列字段名oneColPropField: {type: String,required: true},// 第一列展示header文本oneColLabelField: {type: String,required: true},// 第一列数据来源字段名oneColDataField: {type: String,required: true},// 第二列字段名twoColPropField: {type: String,required: true},// 第二列展示header文本twoColLabelField: {type: String,required: true},// 第二列数据来源字段名twoColDataField: {type: String,required: true},// 其他数量 列 字段名countColPropsField: {type: String,default: 'jtfs'},// 其他数量 列 展示header文本字段名countColLabelField: {type: String,default: 'jtfsView'},// 其他数量 列 数据来源字段名countColDataField: {type: String,default: 'total'}},data() {return {spanArr: []}},computed: {// 表格数据处理tableData() {const list = []Object.entries(this.info?.itemMap || {}).forEach((i) => {i[1]?.forEach((ii) => {/** ** 解决数据重复问题 start ****/const listDataIndex = list.findIndex((listItem) => listItem[this.twoColPropField] === ii[this.twoColDataField] )// 判断即将要 push 进 list 的数据 是否与 list 中已有数据的第二列数据有重复,有重复的话,就在 list 中重复的第二列数据的行数据中只添加当前数据为“其他数量列数据”if (listDataIndex !== -1) {list[listDataIndex][ii[this.countColPropsField]] = ii[this.countColDataField]return}/** ** 解决数据重复问题 end ****/list.push({// [this.oneColPropField]: ii[this.oneColDataField] // 第一列数据(第一列数据部分表格 ii 中无第一列数据,所以使用↓↓↓下一行代码 i[0] 取第一列数据)[this.oneColPropField]: i[0], // 第一列数据[this.twoColPropField]: ii[this.twoColDataField], // 第二列数据[ii[this.countColPropsField]]: ii[this.countColDataField] // 其他数量列数据// jtfs: ii.jtfs})})})return list.map((item) => {return {...item,// 计算小计数量xj: this.columnKeyList.reduce((a, b) => {return a + (item[b] || 0)}, 0)}})},columns() {/*** 表格列获取* 父组件有传表格列 selfColumns 就用 selfColumns* 否则用接口获的表格列*/return (this.selfColumns.length && this.selfColumns) || this.info.columns || []},columnKeyList() {// 获取表格每列字段名组成数组return this.columns.map((item) => item[this.columnKeyField])}},watch: {info() {this.xjPosition()this.oneColMergeCount()}},created() {},methods: {// 计算小计行插入位置xjPosition(Human = this.tableData) {const doctorMap = {}for (let i = 0; i < Human.length; i++) {// 找出相同名称的行数const doctorName = Human[i][this.oneColPropField]if (doctorMap[doctorName] !== undefined) {doctorMap[doctorName].push(i)} else {doctorMap[doctorName] = [i]}}const keyArr = []for (const k in doctorMap) {// 取出key并倒序,防止正序插入会影响行下标keyArr.unshift(k)}keyArr.forEach((ele, index) => {const lastIndex = doctorMap[ele][doctorMap[ele].length - 1] // 找出相同名称最后一行插入合计数据const obj = this.xjRowDataCalc(Human, ele) // 计算出小计行数据Human.splice(lastIndex + 1, 0, obj) // 插入})},// 小计行数据计算xjRowDataCalc(data, name) {const obj = {[this.oneColPropField]: name, // 第一列用于合并单元格[this.twoColPropField]: '小计' // 第二列数据}this.columnKeyList.forEach((key) => {obj[key] = 0 // 其他书两列数据})data.forEach((item) => {// 第一列 oneColPropField 数据相同的加起来if (item[this.oneColPropField] === name) {this.columnKeyList.forEach((key) => {obj[key] += Number(item[key] || 0)})}})// 小计列数据总和(小计行和小计列交汇处数据)obj.xj = this.columnKeyList.reduce((a, b) => {return a + (obj[b] || 0)}, 0)return obj},// 合并单元格firstColMergeSpan({ row, column, rowIndex, columnIndex }) {if (columnIndex === 0) {const _row = this.spanArr[rowIndex]const _col = _row > 0 ? 1 : 0return {rowspan: _row,colspan: _col}}},// 计算要合并的单元格oneColMergeCount() {let contactDot = 0this.spanArr = []this.tableData.forEach((item, index) => {item.index = indexif (index === 0) {this.spanArr.push(1)} else {// 第一列相同的合并if (item[this.oneColPropField] === this.tableData[index - 1][this.oneColPropField]) {this.spanArr[contactDot] += 1this.spanArr.push(0)} else {contactDot = indexthis.spanArr.push(1)}}})}}
}
</script><style lang='scss' scoped>
</style>
【注】计算小计插入位置等部分方法参考文章 https://blog.csdn.net/seeeeeeeeeee/article/details/133122424