上效果图:
完整代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Vue2-Element ui实现 复杂动态报表(同组合并,同组小计,二级表头,内容总合计)</title> <!-- CDN方式引入element-ui可能会失败,建议浏览器输入下方网址将源码复制出来放到本地 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- <link rel="stylesheet" href="./element.css"> --> </head> <body> <div id="app"> <h2>某平台双十一电脑销量报表</h2> <div class="tableBox" style="width: 800px;"> <el-table :data="tableData" border :summary-method="getSummaries" :span-method="tablespan" ref="tabRef" :header-cell-style="{background:'#f5f7fa',color:'#666'}" :cell-style="cellClass" show-summary> <el-table-column align="center" prop="brandName" label="品牌名称" width="130"></el-table-column> <el-table-column align="center" prop="saleNumber" label="销售数量" width="110"></el-table-column> <el-table-column align="center" prop="salePrice" label="销售额" width="110"></el-table-column> <el-table-column align="center" prop="profit" label="毛利" width="110"></el-table-column> <el-table-column align="center" label="产品类型"> <el-table-column align="center" prop="Desktop" label="台式机" width="110"></el-table-column> <el-table-column align="center" prop="laptop" label="笔记本" width="110"></el-table-column> </el-table-column> <el-table-column align="center" prop="saleTime" label="统计日期"></el-table-column> </el-table> </div> </div> </body> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <!-- CDN方式引入element-ui可能会失败,建议浏览器输入下方网址将源码复制出来放到本地 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <!-- <script src="./element.js"></script> --> <script type="text/javascript"> const app = new Vue({ el: '#app', // 这里的的app 是盒子定义的id名 data() { return { // 模拟接口返回的表单数据 // 字段说明: 品牌(brandName),品牌代码(brandCode),销售数量(saleNumber),销售额(salePrice)..... list: [ {id:1,brandName:"联想",brandCode:"01",saleNumber:"8",salePrice:"9995",profit:"2100",Desktop:"5",laptop:"3",saleTime:"2023-11-10"}, {id:2,brandName:"惠普",brandCode:"02",saleNumber:"16",salePrice:"13995",profit:"5150",Desktop:"7",laptop:"9",saleTime:"2023-11-11"}, {id:3,brandName:"机械革命",brandCode:"03",saleNumber:"12",salePrice:"8259",profit:"1850",Desktop:"2",laptop:"10",saleTime:"2023-11-10"}, {id:4,brandName:"联想",brandCode:"01",saleNumber:"23",salePrice:"21295",profit:"3620",Desktop:"8",laptop:"15",saleTime:"2023-11-12"}, {id:5,brandName:"惠普",brandCode:"02",saleNumber:"31",salePrice:"59995",profit:"12100",Desktop:"2",laptop:"29",saleTime:"2023-11-13"}, {id:6,brandName:"联想",brandCode:"01",saleNumber:"11",salePrice:"16585",profit:"3250",Desktop:"2",laptop:"9",saleTime:"2023-11-14"}, {id:7,brandName:"联想",brandCode:"01",saleNumber:"7",salePrice:"16540",profit:"1350",Desktop:"2",laptop:"5",saleTime:"2023-11-12"}, {id:8,brandName:"惠普",brandCode:"02",saleNumber:"8",salePrice:"9995",profit:"2100",Desktop:"5",laptop:"3",saleTime:"2023-11-11"}, {id:9,brandName:"联想",brandCode:"01",saleNumber:"6",salePrice:"10500",profit:"1500",Desktop:"1",laptop:"5",saleTime:"2023-11-15"}, {id:10,brandName:"机械革命",brandCode:"03",saleNumber:"8",salePrice:"10850",profit:"1825",Desktop:"5",laptop:"3",saleTime:"2023-11-11"} ], tableData: [] }; }, mounted() { this.GetTabListFun() }, methods: { // 这些都是根据调试经验得出的计算,代码可能看起来有点繁杂,不必纠结,只要知道如何使用即可 GetTabListFun() { this.tableData = this.countClassFun({ Tabchildren: this.$refs.tabRef.$children, // 表格对象的表头列表 DataList: this.list, // 数组对象类型: 接口数据 Countfield: "brandCode", // 字符串类型: 用于归类统计的类别代号字段名,如:根据品牌代码(brandCode)进行归类 countcolumnName: ["销售数量", "销售额", "毛利", "台式机", "笔记本"] // 数组字符串类型:需要统计的列名称(对应列的表头名称) }) }, // 合并行 tablespan({ row, column, rowIndex, columnIndex }) { return this.tablespanFun({ DataList: this.tableData, // 表格数据 RuleField: 'brandCode', // 用于归类统计的类别代号字段名 columnNameList: ['品牌名称'], // 需要合并列的表头名称 row, column, rowIndex }) }, getSummaries(param) { // 总合计 return this.getSummariesFun({ param, CountList: ["销售数量", "销售额", "毛利", "台式机", "笔记本"] // 需要总合计的对应列 }) }, //设置单元格背景色 cellClass(prop) { if (this.tableData.length) { if (prop.row[this.$refs.tabRef.$children[0].prop].includes("合计")) { return { // 直接在这里改你想要的颜色即可 "background-color": "#fdf3ea", "color": "red" } } } }, // 下面是封装好的合并与统计规则,可不必理会,直接复制进去按照上方的用法使用即可================ flattenTree(arr) { // 递归解析多级表头,并将树形表头扁平化为一维对象数组 let newData = [] const expanded = (item) => { if (item.length) { item.forEach(e => { if (e.label && e.prop) { newData.push(e) } expanded(e.$children) }) } } expanded(arr) return newData }, countClassFun({ Tabchildren, DataList, Countfield, countcolumnName }) { if (!(DataList instanceof Array)) return [] try { let newList = JSON.parse(JSON.stringify(DataList)) // 深拷贝一份,避免影响源数据 let arr = [] // 将数据按指定字段进行 分组(Countfield) let idArr = [] // 存放各组id let tableData = [] // 处理完成后的最终数据 // 自动分配表头的key、value(表头名称、名称对应的列表字段名) let TabLabel = this.flattenTree(Tabchildren).map((item) => { return { label: item.label, key: item.prop } }) newList.forEach((item) => { // 循环查出数据一共有多少组不同的归类id(Countfield) if (!idArr.includes(item[Countfield])) { idArr.push(item[Countfield]) } }) idArr.forEach((id, i) => { // 根据id将数据进行切割分组 let list = [] newList.forEach((item) => { if (item[Countfield] == id) { list.push(item) } }) // 创建一条合计数据,字段与普通数据相同 let rowData = JSON.parse(JSON.stringify(newList[0])) Object.keys(rowData).forEach((key, v) => { rowData[key] = "" }); TabLabel.forEach((val) => { if (countcolumnName.includes(val.label)) { // 根据判断当前字段是否需要累加 rowData[val.key] = list.reduce((prev, cur) => { // 累加器 if (!isNaN(cur[val.key])) { // 判断传入的的字段名是否是数字 return prev + Number(cur[val.key]); } }, 0); } }) rowData[TabLabel[0].key] = "合计:" list.push(rowData) // 处理完毕后,往当前分组尾端插入一条计算好的数据 arr[i] = list }) arr.forEach((item) => { // 最后将将分组数据扁平化 tableData.push(...item) }) return tableData } catch (e) { return [] } }, // 报表合并规则 tablespanFun({ DataList, RuleField, columnNameList, row, column, rowIndex }) { // RuleField:数组类型,接收指定有何列的名称 let newList = JSON.parse(JSON.stringify(DataList)) if (columnNameList.includes(column.label)) { if (rowIndex > 0 && row[RuleField] == newList[rowIndex - 1][RuleField]) { return { rowspan: 0, colspan: 0 }; } else { let _row = 1; for (let i = rowIndex + 1; i < newList.length; i++) { if (row[RuleField] == newList[i][RuleField]) { _row++; } else { break; } } return { rowspan: _row, colspan: 1 }; } } }, // 总合计 getSummariesFun({ param, CountList }) { // CountList:*数组类型,接受需要合并的列名称 let { columns, data } = param; let sums = [] columns.forEach((column, index) => { if (index === 0) { sums[index] = "总合计:" return; } if (CountList.includes(column.label)) { const values = data.map((item) => { // 如果是合计行,则不对这行数值累加 if (!Object.values(item)[1].includes("合计")) { return Number(item[column.property]) } }); if (!values.every(value => isNaN(value))) { sums[index] = values.reduce((prev, curr) => { const value = Number(curr); if (!isNaN(value)) { return prev + curr; } else { return prev; } }, 0); } } }) return sums; }, } }) </script> <style> * { padding: 0px; margin: 0px; box-sizing: border-box; } #app { padding: 30px; } h2 { text-align: center; } .tableBox { margin: 20px auto; } /* 组件自带的边框线不太明显,稍微改动一下 */ .el-table .cell { padding: 0px 5px !important; } .el-date-editor .el-range-separator { min-width: 28px !important; } .el-table td.el-table__cell { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border .el-table__cell { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border .el-table__cell { border-right: 1px solid #d0d0d0 !important; } .el-table th.el-table__cell.is-leaf { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border .el-table__cell { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border:after, .el-table--group:after, .el-table:before { background-color: #d0d0d0 !important; } .el-table--border, .el-table--group { border-color: #d0d0d0 !important; } .el-table td, .el-table th.is-leaf { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border th, .el-table--border th.gutter:last-of-type { border-bottom: 1px solid #d0d0d0 !important; } .el-table--border td, .el-table--border th { border-right: 1px solid #d0d0d0 !important; } .el-table__footer-wrapper td.el-table__cell { border-top: 1px solid #d0d0d0 !important; } </style> </html>
复制