Vue中使用Element表格嵌套实现复杂逻辑报表
Vue中使用Element表格嵌套实现复杂逻辑报表某天,接到一个逻辑比较复杂的报表开发任务,如下:这里是这个报表的左半部分,可以看到,左边的四列属性,功能属性,物料编码,产品名称,规格为固定属性,右边的所有前三列数据相同的子数据(数量,单价,类别)按照月份分类以后再按照客户进行分类,再在此基础上计算小计(数量*单价)。当然,这还是不是全部内容,我们再来看表格的右半部分:在表格的最后,需要计算整行的
某天,接到一个逻辑比较复杂的报表开发任务,如下:
这里是这个报表的左半部分,可以看到,左边的四列属性,功能属性,物料编码,产品名称,规格为固定属性,右边的所有前三列数据相同的子数据(数量,单价,类别)按照月份分类以后再按照客户进行分类,再在此基础上计算小计(数量*单价)。
当然,这还是不是全部内容,我们再来看表格的右半部分:
在表格的最后,需要计算整行的小计数量总和,小计总和并按汇率相应的转化为人民币和美元。
还有年度,月份,客户的查询条件选择。。。
在我初次看到这个报表时,头脑一片空白,并不知道从何下手,但由于这是必要需求,也只能理清思路,迎难而上。
解决思路
以下整理一下我对这个问题的思考方法和心路历程,从大的方面来说,无非一下两种方式:
- 后台处理好数据格式传出 :这种解决方式需要在后台建立多个实体,并按层级封装数据,要处理好如此复杂的数据层级,需要在主数据实体中写下功能属性,物料编码,产品名称,规格和月份实体类型的的List,再在月份的实体中建立一个客户类型的List,最后在客户实体中建立四个字段,数量,单价,类别,小计;至此,表格的主体算是构建完成,但值得注意的是,我们需要统计每一行的总和,所以还需要在最外层主数据实体中加上小计数量,USD和RMB,数据完全处理好之后,只需要在vue中循环打印主数据,打印过程中如果碰到List就把List的值循环打印出来,下层的客户也是如此,因为数据格式是后端一次性查询得到封装好的,所以,只需要处理好后端复杂逻辑的写法,放到前端中来,处理方式并不复杂,只需循环表格便可,不是我们此文章介绍的重点。
- 前台进行数据的过滤并展示:这种方式的优势在于,后台不需要建立多个实体和撰写复杂逻辑,只需查询所有数据,而数据的处理则主要交给前端,但缺陷也是很明显,就是数据的安全性更低,但由于我最终采用的是这种实现方式,所以着重跟大家讲一讲。
首先,我们要了解Element表格组件的基本属性和传值方式,这是Element的官网地址,不清楚的话可以先了解一二。
步入正题,是不是感觉这个有点像我们想要的一个效果,虽然没有循环,但表头格式真是我们所需要的。
话不多说,看一下实例代码。
<template>
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="date"
label="日期"
width="150">
</el-table-column>
<el-table-column label="配送信息">
<el-table-column
prop="name"
label="姓名"
width="120">
</el-table-column>
<el-table-column label="地址">
<el-table-column
prop="province"
label="省份"
width="120">
</el-table-column>
<el-table-column
prop="city"
label="市区"
width="120">
</el-table-column>
<el-table-column
prop="address"
label="地址"
width="300">
</el-table-column>
<el-table-column
prop="zip"
label="邮编"
width="120">
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-08',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-06',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}]
}
}
}
</script>
大概就是在一个el-table-column中嵌入多个el-table-column,再在下面的el-table-column中也进行这步操作,就实现了多级表头,依照这个原理,我们可以在一个el-table-column中实现使用嵌套循环来得到拥有真实数据的多级表头,模仿此写法之后实现的代码如下:
<el-row style="margin-top:30px">
<el-table :data="realData" v-loading="loading" border="">
<el-table-column v-if="realData.length>0" v-for="(item,index) in tableLabel1" align="center" :fixed="item.fixed" :key="index" :label="item.label" :prop="item.prop" :width="item.width" fit="">
</el-table-column>
<!-- 所有行 -->
<el-table-column v-for="(item,index) in tableLabel2" align="center" :fixed="item.fixed" :key="index" :label="item.label" :prop="item.prop" :width="item.width" fit="">
<!-- 所有客户 -->
<el-table-column v-for="(aitem,aindex) in acInput" v-if="aitem.mo==item.value" :key="aindex" :label="aitem.name" align="center" :width="aitem.width" fit="">
<!-- 客户下数量单价币种小计 -->
<el-table-column v-if="" align="center" :prop="getqtyprop(aitem.moac)" width="80" label="数量" fit="">
</el-table-column>
<el-table-column v-if="" align="center" :prop="getamountprop(aitem.moac)" width="80" label="单价" fit="">
</el-table-column>
<el-table-column v-if="" align="center" :prop="gettranprop(aitem.moac)" width="80" label="币种" fit="">
</el-table-column>
<el-table-column v-if="" align="center" :prop="gettotalprop(aitem.moac)" width="80" label="小计" fit="">
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column v-if="realData.length>0" v-for="(item,index) in tableLabel3" align="center" :key="index" :label="item.label" :prop="item.prop" :width="item.width" fit="">
</el-table-column>
</el-table>
</el-row>
acInput: [],//客户(查询得到,用于展示)
tableLabel1: [
{ label: '功能属性', width: '160', prop: 'new_functionalattributes', fixed: 'left' },
{ label: '物料编码', width: '160', prop: 'productnumber', fixed: 'left' },
{ label: '产品名称', width: '160', prop: 'productname', fixed: 'left' },
{ label: '规格', width: '160', prop: 'new_productmodelname', fixed: 'left' },
],
tableLabel2: [
],
tableLabel3: [
{ label: '小计数量', width: '160', prop: 'totalqty' },
{ label: 'USD', width: '160', prop: 'dollertotalamount' },
{ label: 'RMB', width: '160', prop: 'totalamount' }
],
但是我们中间部分的表头内容为动态的,甚至需要结合查询条件来获取对应的表头,所以我们暂且把它置空,写一个新的方法获取到正确的表头值赋给tableLabel2
var new_sales_forecastlinestitle = rtcrm.invokeHiddenApi("new_report_management", "ForecastByAccount/SelectForecastLineTitle", params);
console.log("表头数据");
console.log(new_sales_forecastlinestitle);
//赋值数据
this.tableData = new_sales_forecastlinestitle.data;
//表头数据处理
if (new_sales_forecastlinestitle != null && new_sales_forecastlinestitle.data != null && new_sales_forecastlinestitle.data.length > 0) {
//找到每个月对应的
for (var i = 1; i < 13; i++) {
//定义是否包含此月标记
var ishavemonth = false;
//判断客户是否与上一个相同得标记
var accflag = "";
for (var j in new_sales_forecastlinestitle.data) {
//得到对应的月份
if (new_sales_forecastlinestitle.data[j].new_month == i) {
//与上一个不相同
if (accflag != new_sales_forecastlinestitle.data[j].acc) {
//把客户加入到该月份下面
var obj = {
name: this.getAccountName(new_sales_forecastlinestitle.data[j].acc).data[0].name,
value: new_sales_forecastlinestitle.data[j].acc,
mo: new_sales_forecastlinestitle.data[j].new_month,//所属月份
moac: new_sales_forecastlinestitle.data[j].new_month + new_sales_forecastlinestitle.data[j].accountnumber//所属月份+客户编号
}
this.acInput.push(obj);
}
accflag = new_sales_forecastlinestitle.data[j].acc;
ishavemonth = true;
}
}
if (ishavemonth == true) {
//放到表头中
var obj = {
label: i + '月',
value: i,
width: '320',
prop: ''
}
this.tableLabel2.push(obj);
}
}
}
这样一来表头的数据就已经渲染完毕了,注意是表头,也可以正常的显示了,也可以在条件查询时显示查询到的表头,意思是不会固定的显示十二个月和所有的客户数据,只会根据有无数据来确定是否显示此列,可简称为动态表头。
此时可以调一调表格的宽度,然后固定一下需要固定的列和表头,提高真实性和用户体验。
做到这里,算是完成了小半部分的内容,当时处于探索阶段的我做到这里的时候已经过去了一两天。
接下来到了最重要的部分,渲染表格里的数据,一说起Vue数据传值,第一时间想到肯定是prop属性了,它可以为为一条属性确定一个名字,但我们需要传值时,只需要传出对应prop名的属性字段值就可以了。
下面便是数据渲染的核心逻辑:
var new_sales_forecastlines = rtcrm.invokeHiddenApi("new_report_management", "ForecastByAccount/SelectForecastLineAndProduct", params);
console.log("待处理的数据");
console.log(new_sales_forecastlines);
//实际数据处理
if (new_sales_forecastlines != null && new_sales_forecastlines.data != null && new_sales_forecastlines.data.length > 0) {
//判断产品是否与上一个相同得标记
var proflag = "";
var totalqty = 0;
var totalamount = 0;
//对于每一条数据
for (var i in new_sales_forecastlines.data) {
//如果不是第一个并且不是最后一个而且是新的话就直接添加了
if (proflag != new_sales_forecastlines.data[i].new_product_id && i != 0) {
//如果包含产品,就放到数据里
if (realitem['productname'] != null && realitem['productname'] != undefined && realitem['productname'] != '') {
realitem['totalqty'] = totalqty;//赋值小计数量
realitem['totalamount'] = totalamount;//赋值RMB
realitem['dollertotalamount'] = (totalamount / this.getExchangerate().exchangerate).toFixed(2);//赋值USB
this.realData.push(realitem);
totalqty = 0;
totalamount = 0;
}
}
//与上一个不同,新的
if (proflag != new_sales_forecastlines.data[i].new_product_id) {
//初始化空的对象,用来存放处理后的数据
var realitem = {};
//先放功能属性
var funs = rtcrm.getPicklistOptions("new_sales_forecastline", "new_functionalattributes");
console.log("功能属性:");
console.log(funs);
if (funs != null && funs.length > 0) {
for (var j in funs) {
var functionAttr = funs[j];
if (functionAttr["attributevalue"] == new_sales_forecastlines.data[i].new_functionalattributes) {
realitem['new_functionalattributes'] = functionAttr["value"];
}
}
}
var params = {
id: new_sales_forecastlines.data[i].new_product_id
}
//查询出产品相关信息(产品三个字段)
var products = rtcrm.invokeHiddenApi("new_report_management", "ForecastByAccount/SelectProduct", params);
if (products != null && products.data != null && products.data.length > 0) {
realitem['productname'] = products.data[0].productname;//产品名称
realitem['productnumber'] = products.data[0].productnumber;//产品编码
realitem['new_productmodelname'] = products.data[0].new_productmodelname;//产品规格
}
}
var monthflag = new_sales_forecastlines.data[i].new_month;
var accountflag = new_sales_forecastlines.data[i].accountnumber;
//动态赋值单价,数量...
realitem[monthflag + accountflag + 'sumqty'] = new_sales_forecastlines.data[i].sumqty;//数量
realitem[monthflag + accountflag + 'sumamount'] = new_sales_forecastlines.data[i].sumamount;//单价
realitem[monthflag + accountflag + 'transactioncurrencyidname'] = new_sales_forecastlines.data[i].transactioncurrencyidname;//币种
realitem[monthflag + accountflag + 'subtotal'] = new_sales_forecastlines.data[i].subtotal;//小计
totalqty += new_sales_forecastlines.data[i].sumqty;//累加数量
totalamount += new_sales_forecastlines.data[i].subtotal;//累加小计
//如果是最后一个累加完再添加
if (i == (new_sales_forecastlines.data.length - 1)) {
//如果包含产品,就放到数据里
if (realitem['productname'] != null && realitem['productname'] != undefined && realitem['productname'] != '') {
realitem['totalqty'] = totalqty;//赋值小计数量
realitem['totalamount'] = totalamount;//赋值RMB
realitem['dollertotalamount'] = (totalamount / this.getExchangerate().exchangerate).toFixed(2);//赋值USB
this.realData.push(realitem);
totalqty = 0;
totalamount = 0;
}
}
//产品判同标记赋值
proflag = new_sales_forecastlines.data[i].new_product_id;
}
}
console.log("处理后的数据");
console.log(this.realData);
主要是包含两个难点,第一个难点是要把几条子数据合成一条显示,这里使用的处理办法是,在后端按照产品排序好将数据传过来,然后在前端中判断当前产品是否与上一个产品相同,如果相同则将数量,单价,币种以及小计添加进去,我们还需在一行完成时统计总数量和总价格(这里可以用累加实现)。
这里又会有出现第二个难点,举个例子来说,一列可能会有多个数量,这就必须保证他的每一个数量属性字段prop都不相同。所以我们决定给他加上唯一标识,就是在其属性名前拼接月份和客户id,这样就保证了他的唯一性,我们在el-table-column给他对应的添加上对应的prop,然后设置一下数据源,然后就成功啦。
这里主要展示了实现的核心功能代码,至于条件的选择,以及样式设计和细节的数据处理还需要依照实际情况选择适合自己的写法。
创作不易,看过给个关注吧!
更多推荐
所有评论(0)