由于当前项目的需求,需要一个针对多级分项排列组合的处理程序,工程师由于项目周期比较紧张,无法费时间研究,就甩到我这边了。
整体需求是这样的,首先需要一个多级分项的树形结构(前台页面)前台代码如下。
<html>
<head>
<script src="bootstrap/js/jquery.js"></script>
<script>
//监听li中的添加按钮
$(document).on('click','#itemlist a.itemadd',function(){
var parent = $(this).parent();
var newItem = $("<li><input type='checkbox'><span id='title'></span><a class='itemadd' href='#'>+</a> <a class='itemdel' href='#'>-</a><ul></ul></li>");
parent.find('ul').first().append(newItem);
var index = newItem.index() + 1;
var levelIndex = parent.attr('index') + index;
newItem.attr('index',levelIndex);
newItem.find('#title').text('item:'+levelIndex);
});
//监听li中的删除按钮
$(document).on('click','#itemlist a.itemdel',function(){
var parent = this.parentNode;
parent.remove();
});
//监听添加根ul的添加按钮
$(document).on('click','#rootadd',function(){
var newRoot = $("<ul><li index=''><input type='checkbox'><span id='title'>item:</span><a class='itemadd' href='#'>+</a> <a class='itemdel' href='#'>-</a><ul></ul></li></ul>");
$('#itemlist').append(newRoot);
var index = newRoot.index() + 1 +"";
newRoot.find('li').attr('index', index);
newRoot.find('#title').text('item:'+index);
});
//监听遍历所有可能性按钮
$(document).on('click','#traversal',function(){
var items = {};//初始化要回传的数据
items['returnRequired'] = $('#returnRequired')[0].checked ? 1 : 0;
items['items'] = {};
//在itemlist容器中便利所有li对象
$('#itemlist').children('ul').each(function(index,child){
//console.info(travesal($(child)));
items['items'][index] = travesal($(child))[0];//回传数据赋值
});
//console.info(items);
//向服务器提交数据进行处理
$.post('itemshandler.php', items, function(data, status){
//to do something here;
var skusigns = $.parseJSON(data);
$('#skucount').html(skusigns.length);
var returnHtml = "<ul>";
$.each(skusigns,function(index,skusign){
returnHtml += "<li>"+skusign['skusign']+"</li>";
});
returnHtml += "</ul>";
$('#skulist').html(returnHtml);
});
});
//递归遍历ul节点
function travesal(obj){
//console.info(obj);
var arr = {};//初始化数组
//判断根节点是否为UL
if(obj.is('UL')){
//遍历所有li子元素
obj.children('li').each(function(index,child){
arr[index] = {};//初始化数组
arr[index]['text'] = $(child).children('#title').text();//获取li中的元素并加到数组中去
arr[index]['required'] = $(child).children('input:checked').length;
arr[index]['child'] = travesal($(child).children('ul'));//递归下一节点数据
});
return arr;
}
}
</script>
</head>
<body>
<a href="#" id="rootadd">添加项目</a>
是否遍历必选项<input id="returnRequired" type="checkbox">
<a href="#" id="traversal">生成服务项目</a>
<div id="itemlist">
</div>
<span id="skucount"></span>
<div id="skulist">
</div>
</body>
</html>
以上代码作用只是可以前台动态维护属性list结构。
每个LI中有一个checkbox,选中的checkbox在业务中的逻辑为必选项,也就是排列组合的时候必须有他,目标就是将前台收成的树形数据对象post给php代码,由php代码负责将树中的所有必要项与可选项的组合遍历出来,并输出。
以下是php代码
<?php
$items = $_REQUEST;
//var_dump($items);
echo json_encode(getSkuItems($items['items'],$items['returnRequired']));
/*
* 功能描述:将传入的分项列表根据其必选条件递归遍历所有可能组合行程sku标准,并输出数组。
* @param: $items 传入的分项节点,数据必须有索引
* @param: $returnRequired default false 是否仅返回必选项的组合。
* @param: $hasParent default false 传入的分项节点是否有父节点。
* @return: array();
* by eric
*/
function getSkuItems($items, $returnRequired = false, $hasParent = false){
$hasChild = true;
$skuItems = array();//初始化sku
$itemContainer = array();//创建分项容器,用于区分必选与可选项
//开始遍历分项数据
foreach($items as $index => $item){
$itemType = $item['required']? 'requiredItems': 'optionalItems';//判断是否为必选项
if(isset($item['child'])){//判断是否有子节点
//递归遍历所有子节点的分项
foreach(getSkuItems($item['child'], $returnRequired, true) as $skuItem){
switch($itemType){
case 'requiredItems':
//必选项需要在根节点上合并,故单独设置index,结构与可选项不一样
//在这里可以进行相应skusign的设置,目前使用text代替。
$itemContainer[$itemType][$index][] = array('skusign' => $item['text'].",".$skuItem['skusign']);
break;
case 'optionalItems':
$itemContainer[$itemType][] = array('skusign' => $item['text'].",".$skuItem['skusign']);
break;
default:
break;
}
}
}else{
switch($itemType){
case 'requiredItems':
$itemContainer[$itemType][$index][] = array('skusign' => $item['text']);
break;
case 'optionalItems':
$itemContainer[$itemType][] = array('skusign' => $item['text']);
break;
default:
break;
}
$hasChild = false;
}
}
//整理可选项
if($hasParent){//如果有父节点则需要则不需要考虑组合的合理性,直接为可选项设置空值
if(!$hasChild && isset($itemContainer['requiredItems'])){//如果没有子节点且同级下没有必选项的时候给optional增加空记录,允许不选的可能出现。
$itemContainer['optionalItems'][] = array('skusign' => '');
}else{//由于不可以不选,所以此级节点的数据将被视为必须处理节点对待。
$returnRequired = false;
}
}else{
$itemContainer['optionalItems'][] = array('skusign' => '');
}
//整理必选项
if(isset($itemContainer['requiredItems'])){//如果设置了必选项,则通过itemComb方法进行组合处理
$itemContainer['requiredItems'] = itemsComb($itemContainer['requiredItems']);
}else{//否则设置一个空值
$itemContainer['requiredItems'][] = array('skusign' => '');
}
//将必选项与可选项进行组合
foreach($itemContainer['requiredItems'] as $rItem){
if(isset($itemContainer['optionalItems']) && !$returnRequired){//如果同级有可选项,且返回必选参数为是否的时候遍历optional项目列表
foreach($itemContainer['optionalItems'] as $oItem){
$sperate = "";
if(!empty($rItem['skusign']) && !empty($oItem['skusign']))
$sperate = ",";
$newItems = array('skusign' => $rItem['skusign'].$sperate.$oItem['skusign']);
$skuItems[] = $newItems;
}
}else{
$newItems = array('skusign' => $rItem['skusign']);
$skuItems[] = $newItems;
}
}
return $skuItems;
}
/*
* 功能描述:通过递归的方法将分项进行排列组合
* @param:$items array() 需要排列组合的分项数组,必须有键值
* @return:array
* by eric
*/
function itemsComb($items){
if(count($items) == 1){//只有一个分项表的时候直接返回分项表
foreach($items as $item){
return $item;
}
return null;
}
$returnArr = array();
if(count($items) == 2){//有两个分项表的时候进行依次的排列组合
$newItems = array();
foreach($items as $item){
$newItems[] = $item;
}
foreach($newItems[0] as $item0){
foreach($newItems[1] as $item1){
$returnArr[] = array('skusign' => $item0['skusign'].",".$item1['skusign']);
}
}
return $returnArr;
}
if(count($items) > 2){//大于两个分项表的时候抽取一个表其他的递归
$tempItems = array();
$isFirst = true;
foreach($items as $item){
if($isFirst){
$newItems[] = $item;
$isFirst = false;
}else{
$tempItems[] = $item;
}
}
$newItems[] = itemsComb($tempItems);
return itemsComb($newItems);
}
return null;
}
?>
由于好久没有研究代码了,以上代码用了我整整一天时间,实在惭愧,还是分享出来。
另外,手头事情太多,没有时间继续琢磨了,看看能不能有高手帮我实现仅将必要项的第一个组合抽取出来,并不遍历所有组合。