由于人员有两三千,需求要按省-市-人员三级菜单展示,便于勾选,起初的想法是ajax获取用户数据,然后循环按省市构造人员展示结构,但是测试发现数据量过大,导致页面卡死。无奈只有采用后台拼接好页面结构的字符串,然后前端获取数据后在最外层div直接append的方式来提高展示速度。测试两千多用户展示在6-7秒。下面是简要实现方式:
1、前端页面:
<!DOCTYPE html >
<%@include file="/common/jsp/header.jsp"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>菜单首页</title>
<script type="text/javascript"
src="${ctx}/webResources/js/jquery-3.3.1.min.js"></script>
<script type="text/javascript"
src="${ctx}/webResources/bootstrap-3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript"
src="${ctx}/webResources/js/jquery.itab.1.2.js"></script>
<script type="text/javascript"
src="${ctx}/webResources/js/sidebar-menu.js"></script>
<style type="text/css">
a:link,a:visited{
text-decoration:none; /*超链接无下划线*/
}
a:hover{
text-decoration:underline; /*鼠标放上去有下划线*/
}
li {list-style-type:none;margin:0px;white-space: nowrap;}
.consureBtn {
width: 30px;
height: 25px;
background: #0188fb;
border: none;
color: white;
margin-left: 0px;
margin-top:10px;
position:absolute;
bottom:0px;
right:10px;
}
</style>
</head>
<script type="text/javascript">
$(document).ready(function(){
$("#part1").css("height","397px");
$(".btn-default").css("margin-top","-10px");
getUsers();
});
function getUsers(){
$(".menu ul").css("display","none");
$(".menu").html('');
//ajax获取用户数据
var url='showUsers.action';
var data={userId:$('#userId').val(),userName:$("#userName").val()};
$.ajax({
url:url,
data:data,
dataType:'json',
async:false, //必须是同步,否则页面在数据加载完成前就渲染结束
success:function(obj){
if(obj.result){
var arr=obj.data;
$(".menuLeft").append(arr);
}else{
alert(obj.message);
}
},
error:function(){
alert("数据加载失败");
}
});
initMenu();
}
/* 放弃这种在页面循环创建html结构的方式,采用后台直接生成
function getUsers(){
$(".menu ul").css("display","none");
$(".menu").html('');
var url='showUsers.action';
var data={userId:$('#userId').val(),userName:$("#userName").val()};
$.ajax({
url:url,
data:data,
dataType:'json',
async:false, //必须是同步
success:function(obj){
if(obj.result){
var arr=obj.data;
for(var i=0;i<arr.length;i++){
var provEl='<li id="{provSec}"><input class="yeye" type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)">'
+'<a href="###"><img class="jiahao" alt="" src="${ctx}/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;">'
+'<span>{provName}</span></a><ul style="text-align:left;margin-left: -23px;"></ul></li>';
var cityEl='<li id="{citySec}"><input class="father" type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)">'
+'<a href="#"><img class="jiahao" alt="" src="${ctx}/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;">'
+'<span>{cityName}</span></a><ul style="text-align:left;margin-left: -22px;"></ul></li>';
var userEl='<li id="{userId}"><input class="child" type="checkbox" onclick="choseThis(this)"><a href="#">{userName}</a></li>';
var provId=arr[i].provId;
var provName =arr[i].provName;
var cityId = arr[i].cityId;
var cityName=arr[i].cityName;
var userId=arr[i].userId;
var userName=arr[i].userName;
//判断省份节点是否存在存在就直接添加元素,否则就先创建省份节点再添加元素
if(!document.getElementById(provId)){
var str=provEl.replace("{provName}",provName).replace("{provSec}",provId);
$(".menuLeft").append(str);
}
if(!document.getElementById(cityId)){
var str=cityEl.replace("{cityName}",cityName).replace("{citySec}",cityId);
$("#"+provId).find('ul').eq(0).append(str);
}
var str=userEl.replace("{userId}",userId).replace("{userName}",userName);
$("#"+cityId).find('ul').eq(0).append(str);
}
}else{
alert(obj.message);
}
},
error:function(){
alert("数据加载失败");
}
});
initMenu();
} */
function initMenu(){
$(".menu ul").css("display","none"); //文件准备好后,ul的样式为隐藏
$(".menu a").on("click",function(){ //点击a的时候就执行以下函数
$(this).next().toggle(function(){
if($(this).css("display")=='none'){
$(this).prev().children(".jiahao").attr("src","{ctx}/common/img/jiahao.png");
}else{
$(this).prev().children(".jiahao").attr("src","{ctx}/common/img/jianhao.png");
}
}); //toggle提供为隐藏和显示相互转换的方法 意思是点击的这个 元素的下一个元素是显示的话就隐藏,是隐藏的话就显示
});
}
</script>
//引如勾选人员的操作js
<script type="text/javascript"
src="${ctx}/common/js/menuMove.js"></script>
<body>
<div id="tree" style="width:600px;text-align:center;">
<input type='hidden' value='${userId }' id='userId'>
<div style="width:300px;float:left;border-right:1px solid #ccc;">
<input type="text" id="userName" style="width:200px;margin-left:18px;height:20px;padding-left:10px;border:1px solid #C7C7C7;"placeholder="搜索人员">
<img src="${ctx }/common/img/sousuo.png" style="width:20px;height:20px;top:10px;cursor:pointer;position: absolute;margin-top:2px;" onclick="getUsers();">
<div class="queryUsers" style="width:230px;margin-top:0px;height:300px;overflow:auto;float:right;margin-right:10px;">
<ul class="menuLeft menu" style="margin-left:-40px;text-align:left;" vlaue='menuLeft'>
<!-- -->
<%-- <li id='1'><input class='yeye' type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)"><a href="###"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>A系统</span></a>
<ul style="text-align:left;margin-left: 103px;">
<li id='11'><input class='father' type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)"><a href="#"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>部门1</span></a>
<ul>
<li id='111'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档1</a></li>
<li id='112'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档2</a></li>
</ul>
</li>
<li id='12'><input class='father' type="checkbox" id="c1" value="selectAll" title="全选" onclick="choseThis(this)"><a href="#"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>部门1</span></a>
<ul>
<li id='121'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档111111111111111</a></li>
<li id='122'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档2</a></li>
</ul>
</li>
</ul>
</li>
<li id='2'><input id='' class='yeye' type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)"><a href="###"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>B系统</span></a>
<ul>
<li id='21'><input class='father' type="checkbox" value="selectAll" title="全选" onclick="choseThis(this)"><a href="#"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>部门1</span></a>
<ul>
<li id='211'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档312</a></li>
<li id='212'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档321</a></li>
</ul>
</li>
<li id='22'><input class='father' type="checkbox" id="c1" value="selectAll" title="全选" onclick="choseThis(this)"><a href="#"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>部门1</span></a>
<ul>
<li id='221'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档21</a></li>
<li id='222'><input class='child' type="checkbox" onclick="choseThis(this)"><a href="#">文档21</a></li>
</ul>
</li>
</ul>
</li> --%>
</ul>
</div>
</div>
<div style="width:200px;float:left;">
<div>
<span>已选择的人员</span>
<img alt="" src="${ctx}/common/img/del.png" style="width:20px;height:20px;position:absolute;right:50px;cursor:pointer;" onmouseover="sureDel(this);" onmouseout="notDel(this);" onclick="deleteUsers();" title='移除'>
</div>
<div class="selectedUsers" style="width:250px;margin-top:0px;max-height:300px;overflow-y:auto;float:left;margin-left:25px;">
<ul class="menuRight menu"style="margin-left:-25px;text-align:left;" vlaue='menuRight'>
<%-- <li><input type="checkbox" id="c1" value="selectAll" title="全选" onclick="choseThis(this);"><a href="###"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>A系统</span></a>
<ul>
<li><input type="checkbox" id="c1" value="" title="全选" value="selectAll" onclick="choseThis(this);"><a href="#"><img class="jiahao"alt="" src="${ctx }/common/img/jiahao.png" style="width:15px;height:15px;margin-right:6px;"><span>部门1</span></a>
<ul>
<li><input type="checkbox" onclick="choseThis(this);"><a href="#">文档1</a></li>
<li><input type="checkbox" onclick="choseThis(this);"><a href="#">文档2</a></li>
</ul>
</li>
</ul>
</li> --%>
</ul>
</div>
</div>
<div>
<button class="consureBtn " onclick="submitUsers()" id="addUser"
style="width:80px;">提交</button>
</div>
</div>
</body>
</html>
前端页面主要分为左右两部分,左侧用于展示用户,右侧展示左侧勾选的用户。
2、引入复制左侧结构到右侧的menuMove.js
//联级菜单移动
$(document).ready(function(){
/*$(".menu ul").css("display","none"); //文件准备好后,ul的样式为隐藏
$(".menu a").on("click",function(){ //点击a的时候就执行以下函数
$(this).next().toggle(function(){
if($(this).css("display")=='none'){
$(this).prev().children(".jiahao").attr("src","/${ctx}/common/img/jiahao.png");
}else{
$(this).prev().children(".jiahao").attr("src","/${ctx}/common/img/jianhao.png");
}
}); //toggle提供为隐藏和显示相互转换的方法 意思是点击的这个 元素的下一个元素是显示的话就隐藏,是隐藏的话就显示
});*/
});
function choseThis(obj) {
var value = $(obj).attr("value");
if (obj.checked == false) {
if(value == 'selectAll'){
var li=$(obj).parent();
var checkboxs=li.find("input[type='checkbox']");
for(var i=0;i<checkboxs.length;i++){
checkboxs.eq(i).prop('checked',false);
}
}
$(obj).parent().parent().prev().prev().prop("checked",false);
$(obj).parent().parent().parent().parent().prev().prev().prop("checked",false);
} else {
//查询
//leftToRight(obj);
if (value == 'selectAll') {
var li=$(obj).parent();
var checkboxs=li.find("input[type='checkbox']");
for(var i=0;i<checkboxs.length;i++){
if(checkboxs.eq(i).prop('checked')){
continue;
}
checkboxs.eq(i)[0].checked=true;
}
}
var ul=$(obj).parent().parent();
var allLen=ul.find("input[type='checkbox']").length;
var trueLen=ul
.find("input[type='checkbox']:checked").length;
if (allLen==trueLen) {
ul.prev().prev().prop("checked",true);
}
var ulFather=ul.parent().parent();
if(ulFather.find("input[type='checkbox']").length==ulFather.find("input[type='checkbox']:checked").length){
ul.parent().parent().prev().prev().prop('checked',true);
}
//查询
leftToRight(obj);
}
}
//将左侧的人员移到右侧
function leftToRight(obj) {
var cls=$(obj).attr("class");
if(cls=='yeye'){
if($(obj).parent().parent().attr("class").startsWith('menuRight')){
return;
}
var ul=$(obj).parent();
var id=ul.attr("id");
var menuRight=$('.menuRight').find('#'+id);
if(menuRight.length!=0&&menuRight.find('input[type="checkbox"]:checked').length==ul.find('input[type="checkbox"]:checked').length){
return;
}
$('.menuRight').find('#'+id).remove();
ul=ul.clone(true);
ul.children('input[type="checkbox"]:checked').prop("checked",false);
//省下面的li
ul.find('ul').css('margin-left','-22px');
//市下面的li
ul.find('ul').find('li').find('ul').css('margin-left','-22px');
$('.menuRight').append(ul);
}else if(cls=='father'){
if($(obj).parent().parent().parent().parent().attr("class").startsWith('menuRight')){
return;
}
var ulLi=$(obj).parent();
var li=$(obj).parent().parent().parent();
var id=li.attr("id");
var ul=$(obj).parent().parent();
var menuRightLi=$('.menuRight').children('#'+id);
var id1=ulLi.attr('id');
if(menuRightLi.length!=0){
li=$('.menuRight').children('#'+id);
ul=li.children("ul");
var childLi=ul.children("#"+id1);
childLi.remove();
//console.log('===='+childLi.get(0));
ulLi=ulLi.clone(true);
ulLi.children('input[type="checkbox"]:checked').prop('checked',false);
ul.append(ulLi);
ulLi=null;
}else{
var input=ul.prev().prev().clone(true);
var a=ul.prev().clone(true);
li=li.clone(true);
ul=ul.clone(true);
ulLi=ulLi.clone(true);
ulLi.children('input[type="checkbox"]:checked').prop('checked',false);
//console.log(input.get(0));
//console.log(a.get(0));
ul.html(ulLi);
//console.log(ul.get(0));
//console.log(ulLi.get(0));
li.html('');
li.append(input);
li.append(a);
li.append(ul);
$('.menuRight').append(li);
a=input=ul=ulLi=li=null;
}
$('.menuRight').find('ul').css('margin-left','-22px');
$('.menuRight').find('ul').find('li').find('ul').css('margin-left','-22px');
}else if(cls=='child'){
if($(obj).parent().parent().parent().parent().parent().parent().attr("class").startsWith('menuRight')){
return;
}
var li=$(obj).parent().clone(true);
li.children('input[type="checkbox"]:checked').prop('checked',false);
var ul=$(obj).parent().parent().clone(true);
var inputChild=$(obj).parent().parent().prev().prev().clone(true);
var aChild=$(obj).parent().parent().prev().clone(true);
var ulFather=$(obj).parent().parent().parent().parent().clone(true);
var input=$(obj).parent().parent().parent().parent().prev().prev().clone(true);
var a=$(obj).parent().parent().parent().parent().prev().clone(true);
var liFather=$(obj).parent().parent().parent().clone(true);
var liYeye=$(obj).parent().parent().parent().parent().parent().clone(true);
var yeyeId=liYeye.attr("id");
var righYes=$('.menuRight').children('#'+yeyeId);
if(righYes.length>0){
var fatherId=liFather.attr("id");
var ul1=righYes.children('ul');
var liFather1=ul1.children('#'+fatherId);
if(liFather1.length>0){
ul=liFather1.children('ul');
var id=li.attr('id');
var li1=ul.children('#'+id);
if(li1.length>0){
//如果存在该子节点则无需操作
}else{
ul.append(li);
}
}else{
ul.html('');
ul.append(li);
liFather.html('');
liFather.append(inputChild);
liFather.append(aChild);
liFather.append(ul);
ul1.append(liFather);
}
}else{
ul.html('');
ul.append(li);
liFather.html('');
liFather.append(inputChild);
liFather.append(aChild);
liFather.append(ul);
ulFather.html('');
ulFather.append(liFather);
liYeye.html('');
liYeye.append(input);
liYeye.append(a);
liYeye.append(ulFather);
$('.menuRight').append(liYeye);
}
li=ul=inputChild=aChild=ulFather=input=a=liFather=liYeye=null;
}
}
function sureDel(obj) {
obj.src = '/${ctx}/common/img/del1.png';
}
function notDel(obj) {
obj.src = '/${ctx}/common/img/del.png';
}
//将dom元素转成字符串
function domToString (node) {
var tmpNode = $('<div></div>');
tmpNode.append(node.clone(true));
var str = tmpNode.html();
tmpNode = node = null; // 解除引用,以便于垃圾回收
return str;
}
function deleteUsers(){
var checkedUsers=$('.menuRight').find('input[type="checkbox"]:checked');
if(checkedUsers.length==0){
alert('请选择需要删除的人员');
return;
}
//ul:menuLeft
var leftCheckeds=$('.menuLeft').find('input[type="checkbox"]:checked').parent().parent();
for(var i=0;i<checkedUsers.length;i++){
var id=checkedUsers.eq(i).parent().attr('id');
checkedUsers.eq(i).parent().remove();
//console.log(leftCheckeds.find('#'+id).attr('id'));
leftCheckeds.find('#'+id).find('input[type="checkbox"]').prop('checked',false);
//如果同级中没有被选择的checkbox就将父级置为未选择
if(leftCheckeds.find('#'+id).parent().find('input[type="checkbox"]:checked').length==0){
leftCheckeds.parent().parent().find('input[type="checkbox"]').prop('checked',false);
}
//如果同级中没有被选择的checkbox就将父级置为未选择
if(leftCheckeds.find('#'+id).parent().parent().parent().find('input[type="checkbox"]:checked').length==0){
leftCheckeds.find('#'+id).parent().parent().parent().parent().find('input[type="checkbox"]').prop('checked',false);
}
}
}
3、后台接收ajax请求的java方法,这里也是重点,需要拼接好三级菜单的结构:
首先是查询数据库获取省份Id、省份名称、地市Id、地市名称、用户Id、用户名称,然后分别将省份、地市和用户的html结构放到map中用于后面的循环拼接。
@RequestMapping("/showUserMenu")
public void showUsersOutGroup2(String userId,String userName){
//省去查询数据的条件部分。。。
List<Object[]> modelList=this.userDataService.findUsers(paraMap);
if(modelList.size()==0){
this.getResponseError("-1", "...");
return;
}
Map<String,Object> mapResults=new HashMap<String,Object>();
//存储省-省的html结构的数据
Map<String,Object> provMap=new HashMap<String,Object>();
//存储省-[市-市的html结构]的数据
Map<String,Map<String,Object>> cityMap=new HashMap<String,Map<String,Object>>();
//存储省-[市-[市下面的用户的html结构列表]]
Map<String,Map<String,List<String>>> userMap=new HashMap<String,Map<String,List<String>>>();
for(Object[] o:modelList){
//Map<String,Object> map=new HashMap<String,Object>();
String provId="";
String cityId="";
if (null==o[3])
{
provId="其他";
}else {
provId=o[3].toString();
}
if (null==o[4])
{
cityId="其他";
}else {
cityId=o[4].toString();
}
//省份的html结构如下,省份的结构里包含市的
String provEl="<li id=\""+provId+"\"><input class=\"yeye\" type=\"checkbox\" value=\"selectAll\" title=\"全选\" onclick=\"choseThis(this)\"><a href=\"###\"><img class=\"jiahao\" alt=\"\" src=\"${ctx}/common/img/jiahao.png\" style=\"width:15px;height:15px;margin-right:6px;\"><span>"+provId+"</span></a><ul style=\"text-align:left;margin-left: -23px;\">";
//市的html结构如下,市的html结构要包含用户的
String cityEl="<li id=\""+cityId+"\"><input class=\"father\" type=\"checkbox\" value=\"selectAll\" title=\"全选\" onclick=\"choseThis(this)\"><a href=\"#\"><img class=\"jiahao\" alt=\"\" src=\"${ctx}/common/img/jiahao.png\" style=\"width:15px;height:15px;margin-right:6px;\"><span>"+cityId+"</span></a><ul style=\"text-align:left;margin-left: -22px;\">";
//用户的html结构如下
String userEl="<li id=\""+o[0].toString()+"\"><input class=\"child\" type=\"checkbox\" onclick=\"choseThis(this)\"><a href=\"#\">"+o[1].toString()+"</a></li>";
//如果省份map不存在该省份的键就直接插入,否则不用插入
if (!provMap.containsKey(provId))
{
provMap.put(provId, provEl);
}
//如果地市map没有该省份的键就插入省份数据和新建地市map存储
if (!cityMap.containsKey(provId))
{
Map<String,Object> cityNode= new HashMap<String,Object>();
cityNode.put(cityId, cityEl);
cityMap.put(provId, cityNode);
}else {
//如果地市map存在该省份的键就获取该省份对应的地市map并判断是否已经插入该地市,如果存在就不插入否则就插入
if (!cityMap.get(provId).containsKey(cityId))
{
cityMap.get(provId).put(cityId, cityEl);
}
}
//默认用户数据没有重复,直接插入
if (null!=userMap.get(provId))
{
if (null!=userMap.get(provId).get(cityId))
{
//避免重还是判断一下存不存在重复
if (!userMap.get(provId).get(cityId).contains(userEl))
{
userMap.get(provId).get(cityId).add(userEl);
}
}else {
//如果省份节点存在,地市节点不存在
List<String> users=new ArrayList<String>();
users.add(userEl);
userMap.get(provId).put(cityId, users);
}
}else {
//如果不存在该省份的键先创建对象再插入值
Map<String,List<String>> cityNode= new HashMap<String,List<String>>();
List<String> users=new ArrayList<String>();
users.add(userEl);
cityNode.put(cityId, users);
userMap.put(provId, cityNode);
}
}
//这个htmlStruc就是拼接的最终要到前端展示的三级菜单的结构
String htmlStruc=createUserStruc(provMap,cityMap,userMap);
mapResults.put("result", true);
mapResults.put("data", htmlStruc);
try {
response.getWriter().println(JSONObject.fromObject(mapResults));
} catch (IOException e) {
e.printStackTrace();
}
}
//下面是提取出来的将省市用户html结构组合起来的方法
//将单独存储的省、市、人员html结构组合在一起
public String createUserStruc( Map<String,Object> provMap,Map<String,Map<String,Object>> cityMap,Map<String,Map<String,List<String>>> userMap){
//下面是拼接整个省-市-用户的html结构
StringBuffer sb=new StringBuffer();
String prov="";
//遍历省份节点,拼接每个省份的html结构
for (Map.Entry<String, Object> provEntry:provMap.entrySet())
{
String provId=provEntry.getKey();
//如果临时存储省份id的变量的值和省份map获取的key不一致说明到下一个省份了这时需要拼接上结束标签
if (!provId.equals(prov))
{
//prov为空说明还没拼接前面部分,不需要拼接结束标签
if (!"".equals(prov))
{
sb.append("</ul></li>");
}
prov=provId;
}
sb.append(provEntry.getValue());
//遍历该省份对应的地市map
for (Map.Entry<String,Object> cityEntry:cityMap.get(provId).entrySet())
{
String cityId=cityEntry.getKey();
sb.append(cityEntry.getValue());
//获取省-市对应的用户list拼接用户结构
List<String> users = userMap.get(provId).get(cityId);
for (int i = 0; i < users.size(); i++ )
{
sb.append(users.get(i));
}
sb.append("</ul></li>");
}
}
return sb.toString();
}