一、前言
项目开发时偶尔会遇到需要将几组数据的排列结果展示成一个树状表格。举一个简单的例子:现有3组数据,主语词(我、你),谓语词(打、坑),宾语词(他,她),需要将主谓宾的各种排列结果展示成如下表格:
二、思路
由上述例子可以看出,解决关键点在于两个点:将数据排列组合出所有排列结果;将所得结果通过html表格显示出来
具体阐述下思路:
- n组数据中的n,为表格的列数
- n组数据,即为n个list,每个list都有若干个item,每个item数据结构一致,如果给每个list和每个item编号,可以得到一个例子数据:
[
{
name:"主语"
items:[{itemId: 0, itemName: "我"},{itemId: 1, itemName: "你"},]
},
{
name:"谓语"
items:[{itemId: 0, itemName: "打"},{itemId: 1, itemName: "坑"},]
},
{
name:"宾语"
items:[{itemId: 0, itemName: "他"},{itemId: 1, itemName: "她"},]
}
]
3.有了上述的数据结构,但以此并不能直接以表格形式显示。需要将其转成以下排列组合的结果。这个list的length就是表格的行数,每个item都代表的表格的一行数据
[
{
rangeArr: [0,0,0],//每个数字是上述的itemId
result:"我打他"//代表组合结果
},
{
rangeArr: [0,0,1],//每个数字是上述的itemId
result:"我打她"//代表组合结果
},
{
rangeArr: [0,1,0],//每个数字是上述的itemId
result:"我坑他"//代表组合结果
},
{
rangeArr: [0,1,1],//每个数字是上述的itemId
result:"我坑她"//代表组合结果
}
]
4.有了上述结构,算是对排列结果有了比较直观的感受。但如果要绘制成html表格,由于表格中td必须大量运用rowspan(因为跨行),而3中的数据我们并看不出每个td需要设置多少rowspan。所以有下列步骤:
- 首先得让3中的list根据rangeArr去排序,按rangeArr[0]、rangeArr[1]…递减的优先级排序
- rangeArr[0]相同的item(rangeArr[0]为0的4个),其第一列的的td合并(该td的rowspan为4)
- 在rangeArr[0]相同的item中,再找出rangeArr[1]相同的item(rangeArr[1]为0的2个),其第二列的的td合并(该td的rowspan为2)
- 依次类推,直到rangeArr[n-1](n为列数)
得到如下数据结构:
[
{
rangeArr: [0,0,0],//每个数字是上述的itemId
result:"我打他",//代表组合结果
rowSpanArss: [4,2,1]//0代表该td不存在
},
{
rangeArr: [0,0,1],//每个数字是上述的itemId
result:"我打她",//代表组合结果
rowSpanArss: [0,0,1]//0代表该td不存在
},
{
rangeArr: [0,1,0],//每个数字是上述的itemId
result:"我坑他",//代表组合结果
rowSpanArss: [0,0,1]//0代表该td不存在
},
{
rangeArr: [0,1,1],//每个数字是上述的itemId
result:"我坑她",//代表组合结果
rowSpanArss: [4,2,1]//0代表该td不存在
}
...//省略
]
5.有了上述数据结构,就可以通过vue遍历出一个完整的表格了
三、代码
1.将二.2中的数据结构,转成二.3中的数据结构,即将给定的list转成排列组合后的结果。这里的基本思路是将现有的sourceList,遍历其所有项,对于每一个项,遍历其所有值,将其所有可能的结果组合出来,得到rangeList(resList)。其中为了书写方便,用parseJSON+toJSON结合的方式进行深克隆,临时存储上一次遍历的结果数据。
function toCombination(sourceList){
var rangeList = [];//排练组合后的list
var isFirstItem = true;//是否是第一个项
$.each(sourceList, function(i, n){
var items = n.items || [];
var cloneRangeList = $.parseJSON($.toJSON(rangeList));//克隆
rangeList = [];
$.each(items, function(i2, item){
var itemId = item.i;
if(itemID < 0){
return true;
}
if(isFirstItem){
rangeList.push({
rangeArr: [itemId]
});
}else{
$.each(cloneRangeList, function(i3, cloneItem){
var rangeArr = cloneItem.rangeArr || [];
rangeList.push({
rangeArr: rangeArr.push(itemId);
});
});
}
});
isFirstItem = false;
});
return rangeList;
}
2.给1中所生成的list,加上rowspan,如二.4中所述,具体代码如下。
该方法的目的是将rangeList处理加上rowspan的数据。首先需要得到表格的列数(rangeLen),然后遍历每一列,对rangeList中的每一项,找到和它拥有同一个前缀(rangeXArr)的其它项,rowspan的数量就等于匹配到的其他项的个数。最终得到rangeRowspanList
function setRowSpan(rangeList){
var rangeLen = rangeList[0].rangeArr.length;
for(var i = 0; i < rangeLen; i++){
var lastXArr = '';//上一个
var hasCountRow = false;
for(var index = 0; index < rangeList.length; index++){
var rangeListItem = rangeList[index] || {};
var rangeArr = rangeListItem.rangeArr;
var rangeArrStr = rangeArr.join("_");
//从0到i的rangeArr元素数组
var rangeXArr = rangeArr.filter(function(var, rangeIndex){
return rangeIndex <= i;
});
var rowSpan = 1;
var hasFind = false;
for(var index2 = 0; index2 < rangeList.length; index2++){
var rangeListItem2 = rangeList[index2] || {};
var rangeArr2 = rangeListItem2.rangeArr;
var rangeArrStr2 = rangeArr2.join("_");
if(rangeArrStr == rangeArrStr2){
continue;
}
var isSame = true;
for(var j = 0; j <= i; j++){
if(rangeArrStr[j] != rangeArrStr2[j]){
isSame = false;
break;
}
}
if(isSame){
hasFind = true;
rowSpan++;
}
if(!isSame && hasFind){
//由于rangeList是按rangeArr排序的
//hasFind为true之后如果isSame为false
//说明后面的也都是false,可以直接brak
break;
}
}
if(!rangeListItem.rowSpanArr){
rangeListItem.rowSpanArr = [];
}
if(!hasCountRow || rangeXArr.toString() != lastXArr.toString()){
rangeListItem.rowSpanArr[i] = rowSpan;
hasCountRow = true;
}else{
rangeListItem.rowSpanArr[i] = 0;
}
lastXArr = rangeXArr;
}
}
}
3.通过vue渲染出表格,代码如下图。通过上面的rangeRowspanList,可以很轻易用vue的模板语法将其渲染在页面上,而对于每一项的名称显示,我们需要另外写一个vue方法getItemName,该方法是取源数据sourceList,取得对应的名称渲染在页面上
<table>
<tr>
<td v-for="item in souceList">{{item.n}}</td>
</tr>
<tr v-for="item in rangeRowspanList">
<td :rowspan="item.rowSpanArr[index]"
v-for="(rangeItem, index) in item.rangeArr">
<div>{{getItemName(index, rangeItem)}}</div>
</td>
</tr>
</table>
getItemName: function(index, itemId){
var sourceList = this.sourceList || [];
var current = sourceList[index] || {};
var currentItems = current.items || [];
var currentItem = currentItems.find(function(item){
return item.i == itemId;
});
return (currentItem && currentItem.name ? currentItem.name : "");
}
总结
该方案的思路比较复杂,但核心点在于,将不能直接渲染的源数据,变成用vue就可简单渲染的数据。所以首先得明确vue需要什么样的数据才能渲染我们需要的表格出来。比如本方案需要渲染的表格,我们需要知道哪些td的rowspan是多少,而源数据并没有直接给出,这就需要我们用代码将源数据转换得到我们需要的结果。