zoukankan      html  css  js  c++  java
  • [ PHP+jQuery ] ajax 多级联动菜单的应用:电商网站的用户地址选择功能 ( 一 )

    /**
      jQuery version: 1.8.3
       Author: 小dee
       Date: 2014.11.5
    */

    说明:分析其他网站的图片较多,可以在目录跳过直接看本文 demo

    目录:        

    ▼ 其他网站分析

     ■  亚马逊     

     ■  淘宝      

     ■  京东      

     ■  当当      

    ▼ 本文案例     

     ■ demo1   

     ■ demo2   

     ■ demo3 [ 后面的博文再写 ]

    惯例,先看看他山之石,选择了四家比较大的电商网站:亚马逊、淘宝、京东、当当,看看它们的地址联动菜单是怎么做的。

    1. 亚马逊返回目录】【下一节:淘宝

    图1 默认界面

    说明:只能选择第一级菜单,二三级为灰色不可用

    图2 点击第一级菜单

    说明:这种下拉方式我第一次真正注意到,平时也许用到过但是没有察觉。我感觉这种方式体验相对于传统下拉菜单非常好,对于数据量不多的数据可以采用这种方式,对于数据量稍大的数据,也许能做成 ajax 翻页的形式,不过我没有见过。此时后两级菜单仍然不可用。下拉数据的格式是 n 行 4 列。

    图3. 选择一个直辖市,比如北京

    说明:直辖市"北京"的第二级菜单默认自动选择了北京,而且用户不能修改;第三级菜单"区县"自动展现。选择完这一级就完成了填写工作。

    直辖市的添加用了 3 步 ( 鼠标点击 3 次 ),当然详细街道地址得用户手动填写。

    下面看看普通省份。

    图4. 选择一个普通省份

    截图省略了几个步骤。它比选择直辖市多了一个选择二级地级市的步骤 ( 4步 ),其他都一样。

    2. 淘宝 【返回目录】【下一节:京东

    图1. 默认显示界面

    说明:固定两级联动菜单,第一级是选择国内国外,第二级是选择城市 ( 所有的省市区县都包含在这一级的下拉菜单中 )。

    图2.点击"请选择城市"

    说明:和亚马逊一样,下拉出现的是一个面板,不同的是它这个是个 tab 面板,省、市、县包括街道这四级都包含在这里面了,默认显示省份,所有的省份按首字首拼进行分类和排列。

    图3. 选择一个直辖市比如北京

    说明:直接显示 tab 面板的"县区"。

    图4.选择"县区",比如朝阳,显示"街道"。加上选择街道,用户需要点击 4 步。

    图5.此时如果反过来点击"城市",显示

    图6.如果反过来点击"省份",则显示"北京"高亮的面板

    图7.再选择普通省份,比如选择排第一行的"福建"

    说明:默认跳转至"城市"面板

    淘宝的地址填写到此结束。

    相比亚马逊,我更喜欢淘宝的设计。

    总结一:这两个网站的联动菜单都没有使用传统的下拉框,实际上它们都没有使用 html 的 <select></select>标签,优点在于:1.不需要用户额外再去点击下级菜单来展开选择项;2.不需要用户拖动下拉菜单的竖直滚动条;3.大多数情况下用户不用太多的视觉跨越就能找到自己需要的选项。

    再来看看另两家使用传统选择列表的电商网站:京东和当当。

    3.京东 【返回目录】【下一节:当当

    图1.默认界面

    说明:默认三级联动

    图2.点击第一级下拉

    图3.选择一个直辖市:北京

    图4. 选择第二级菜单

    图5.

    图6. 继续选择

    说明:直辖市选择结束。用户需要6步。

    再看看普通省份,比如河北。图7

    图8,选择石家庄

    图9.选择晋州

    说明:此时动态添加了第四级菜单

    图10.继续选择

    说明:普通省份选择结束。用户共需要点击8次。

    4.最后看看当当网 返回目录】【下一节:本文案例

    图1.默认界面

    图3.选择一个直辖市北京

    说明:此步设计我认为是一个败笔,当选择第一级栏目为北京时,第二级栏目应该固定为北京或者直接显示北京的各个区,而不是让用户进行一个完全多余的一个行为。

    图4.在第三级栏目中选择北京

    图5.继续选择

    图5.选择结束

    说明:用户进行了 6 步。

    总结二. 使用传统的下拉选择菜单,用户在选择时可能会使用下拉的滚动条,这增加了用户的操作;而且用户的视觉跨度( 自上而下 ) 往往更大。站在用户的角度,我更青睐前两家网站的设计方式。

    =======================================华丽的分割线=======================================

    现在自己设计省市的多级联动菜单,分别设计 3 种类型: 【返回目录】【下一节:demo1

    1.传统的下拉选择菜单

    2.仿亚马逊的下拉面板 [ 后面的博文写 ]

    3.仿淘宝的 tab 面板 [ 后面的博文写 ]

    数据库分为 4 张表:province,city,area,street,分别表示省份、城市、区县和街道,其中前三张表的数据时完整的,街道表中只有一条测试数据,用来测试动态添加级联菜单。

    表的结构如下

    mysql> desc province;
    +------------+-------------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +------------+-------------+------+-----+---------+----------------+
    | id | int(11) | NO | PRI | NULL | auto_increment |
    | provinceID | varchar(6) | YES | | NULL | |
    | province | varchar(40) | YES | | NULL | |
    +------------+-------------+------+-----+---------+----------------+

    mysql> desc city;
    +--------+-------------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +--------+-------------+------+-----+---------+----------------+
    | id | int(11) | NO | PRI | NULL | auto_increment |
    | cityID | varchar(9) | YES | | NULL | |
    | city | varchar(50) | YES | | NULL | |
    | father | varchar(6) | YES | | NULL | |
    +--------+-------------+------+-----+---------+----------------+

    mysql> desc area;
    +--------+-------------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +--------+-------------+------+-----+---------+----------------+
    | id | int(11) | NO | PRI | NULL | auto_increment |
    | areaID | varchar(50) | YES | | NULL | |
    | area | varchar(60) | YES | | NULL | |
    | father | varchar(6) | YES | | NULL | |
    +--------+-------------+------+-----+---------+----------------+

    mysql> desc street;
    +----------+-------------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +----------+-------------+------+-----+---------+----------------+
    | id | int(11) | NO | PRI | NULL | auto_increment |
    | streetID | varchar(9) | YES | | | |
    | street | varchar(40) | YES | | | |
    | father | varchar(6) | YES | | | |
    +----------+-------------+------+-----+---------+----------------+

    一. 传统的下拉菜单 返回目录

    设计思路:

    默认显示的是三级下拉菜单:省份、城市、县区,只有选择数据表中含有街道数据的区县才会显示第四级菜单,demo 完整的效果图如下:

    图1. 默认显示三级下拉菜单

    图2. 当区县含有街道信息时,动态加载第四级菜单

     

    (1) 首先当页面加载时,同时使用 ajax 加载省份菜单,另外两级菜单不可用,代码:

    addr.hrml html 部分代码片段:

    <form id="select-form" action="addr.php" method="post"> 
        
            <!-- 第一级 省份 -->
            <select id="province">    
                <option>选择省份</option>
            </select>
    
            <!-- 第一级 城市 初始状态不可用 -->
            <select id="city" disabled>    
                <option>选择城市</option>
            </select>
    
            <!-- 第一级 区县 初始状态不可用 -->
            <select id="area" disabled>    
                <option>选择区县</option>
            </select>
            
            <!-- 隐藏域 -->
            <input type="hidden" id="pid" name="pid">
            <input type="hidden" id="p" name="p">
            <input type="hidden" id="cid" name="cid">
            <input type="hidden" id="c" name="c">
            <input type="hidden" id="aid" name="aid">
            <input type="hidden" id="a" name="a">
    
            <input id="sub" type="submit" value="提交">
    
        </form><br>
        
        <!-- 显示选择的结果 -->
        省份:<{$pid}> - <{$p}><br>
        城市:<{$cid}> - <{$c}><br>
        区县:<{$aid}> - <{$a}><br>
        <{if $sid && $s }>
        街道:<{$sid}> - <{$s}>
        <{/if}>

    addr.html js 部分代码片段

    $(function(){
        
            //ajax方式加载第一级菜单 - 省份
            $.post("sel.php",{
                city:true
            },function(data,textStatus){
            
                //接收json数据
                var dataObj = eval("("+data+")"); //转换为json对象 
    
                $.each(dataObj,function(idx,item){ 
                                         
                    $option_new = $("<option value=""+item.provinceID+"">"+item.province+"</option>");
                    $option_new.insertAfter($("#province").children(":first"));
                })
    
            });
    
    //....

    sel.php 代码片段:

    //初始加载所有的省份信息
    if($_POST['city']){
    
        $sql = "select provinceID,province from province order by id desc";
    
        if( $conne->getRowsNum($sql) > 0 ){
        
            $rows = $conne->getRowsArray($sql);
            
            //把二维数组转换成json格式
            echo json_encode($rows);
        }
    }

    (2) 当选择第一级菜单时,有两种情况:

    一是第一次选择该菜单,需要对该级 select 标签下的 option 进行遍历,当select 的值 ( 选中的 ) 和 列表中 option 的值相等时,就把该option 添加上 selected 属性,同时把该 option 的 value 和 text 放入表单的隐藏域;

    二是之前已经选择过该菜单,现在再次选择,则需要首先把下级 select 的子元素 ( 即 option ) 已经添加的 selected 属性去掉,同时下级 selected 全部不可用,下级菜单对应的隐藏域的值全部清空,在遍历时需要把没有最终选中的 option 的 selected 属性去掉。

    当选择完省份之后,使用 ajax 加载 第二级的城市菜单,如果获取数据成功,则加载菜单;否则城市菜单不可用。代码如下:

    addr.html js 部分代码片段:

     1 //选择第一级菜单 - 省份
     2 $("#province").bind("change",function(){
     3 
     4     //第三级菜单 - 地区不可用
     5     $(this).nextAll().children().removeAttr("selected");
     6     $(this).siblings("select").attr("disabled",true);
     7 
     8 
     9     //同时清除隐藏域
    10     $("#cid").val("");
    11     $("#c").val("");
    12     $("#aid").val("");
    13     $("#a").val("");
    14 
    15     if($("#street").length > 0){
    16     
    17         $("#street").remove();
    18         $("#sid").val("");
    19         $("#s").val("");
    20     }
    21     
    22     //遍历option
    23     $(this).children().each(function(){
    24 
    25         if($(this).val() == $(this).parent().val()){
    26 
    27             //选中该条
    28             $(this).attr("selected",true);
    29             
    30             //把该条的id放入隐藏域
    31             $("#pid").val($(this).val());
    32             $("#p").val($(this).text());
    33         }else{
    34         
    35             //没有选中的去掉之前的selected
    36             $(this).removeAttr("selected");
    37         }
    38 
    39     });
    40 
    41     //选中省份之后,使用ajax获取第二季菜单 - 城市
    42     $.post("sel.php",{
    43         pid : $("#pid").val()
    44     },function(data,textStatus){
    45     
    46         //如果有返回值
    47         if(data){
    48 
    49             //城市菜单可用
    50             $("#city").attr("disabled",false);
    51 
    52             //删除之前选择省份加载的城市
    53             $("#city").children(":not(:first)").remove();
    54             
    55             //接收json数据
    56             var dataObj = eval("("+data+")"); //转换为json对象 
    57 
    58             $.each(dataObj,function(idx,item){ 
    59                                      
    60                 $option_new = $("<option value=""+item.cityID+"">"+item.city+"</option>");
    61                 $option_new.insertAfter($("#city").children(":first"));
    62             })
    63         }else{
    64         
    65             //没有返回值,说明第一级菜单没有选中省份
    66             //城市菜单恢复默认而且不可用
    67             $("#city").children().removeAttr("selected");
    68             $("#city").attr("disabled",true);
    69         }
    70     });
    71 
    72 });

     sel.php 代码片段:

    //当接收到post的pid,加载城市信息
    if( isset($_POST['pid']) && $_POST['pid']!="选择省份" ){
    
        $sql = "select cityID,city from city where father = ".$_POST['pid']." order by id desc";
    
        if( $conne->getRowsNum($sql) > 0 ){
        
            $rows = $conne->getRowsArray($sql);
            
            //把二维数组转换成json格式
            echo json_encode($rows);
        }
    }

    其他各级的实现都与第一级的实现类似,最后是动态添加下拉菜单,如果 ajax 没有返回查询的数据,则说明没有该级菜单下没有下级菜单了,代码如下:

    addr.html js 部分代码片段:

      1 //选择第三级菜单 - 地区
      2 $("#area").bind("change",function(){
      3 
      4     if($("#street").length > 0){
      5     
      6         $("#street").remove();
      7         $("#sid").val("");
      8         $("#s").val("");
      9     }
     10     
     11     //遍历option
     12     $(this).children().each(function(){
     13 
     14         if($(this).val() == $(this).parent().val()){
     15         
     16             //排除第一条(即"请选择")
     17             if( $(this).parent().val() !== $(this).parent().children(":first").val() ){
     18 
     19                 //选中该条
     20                 $(this).attr("selected",true);
     21                 
     22                 //把该条的id和名称放入隐藏域
     23                 $("#aid").val($(this).val());
     24                 $("#a").val($(this).text());
     25             }else{
     26             
     27                 //当是第一条时,把隐藏域的值清空,无法提交
     28                 $("#aid").val("");
     29                 $("#a").val("");
     30             }
     31         }else{
     32         
     33             //没有选中的去掉之前的selected
     34             $(this).removeAttr("selected");
     35         }
     36 
     37     });    
     38 
     39     //检测是否还有下一级
     40     $.post("sel.php",{aid:$("#aid").val()},function(data,textStatus){
     41     
     42         //如果有返回值
     43         if(data){
     44 
     45             //动态添加下一级菜单
     46             $street = $("<select id="street"><option>选择街道</option></select>");
     47             $street.insertAfter($("#area"));
     48 
     49             //删除之前选择省份加载的街道
     50             $("#street").children(":not(:first)").remove();
     51             
     52             //接收json数据
     53             var dataObj = eval("("+data+")"); //转换为json对象 
     54 
     55             $.each(dataObj,function(idx,item){ 
     56                                      
     57                 $option_new = $("<option value=""+item.streetID+"">"+item.street+"</option>");
     58                 $option_new.insertAfter($("#street").children(":first"));
     59 
     60                 //如果有第四级
     61                 if($("#street").length > 0){
     62 
     63                     $("#street").live("change",function(){
     64                     
     65                         //清空隐藏域
     66                         $("#sid").remove();
     67                         $("#s").remove();
     68                         
     69                         //遍历option
     70                         $(this).children().each(function(){
     71 
     72                             if($(this).val() == $(this).parent().val()){
     73 
     74                                 //选中该条
     75                                 $(this).attr("selected",true);
     76                                 
     77                                 //添加隐藏域并把把该条的id放入隐藏域
     78                                 $sid = $("<input type="hidden" id="sid" name="sid">");
     79                                 $s = $("<input type="hidden" id="s" name="s">");
     80 
     81                                 $sid.insertAfter($("#a"));
     82                                 $s.insertAfter($("#sid"));
     83 
     84                                     //排除第一条(即"请选择")
     85                                     if( $(this).parent().val() !== $(this).parent().children(":first").val() ){
     86 
     87                                         //选中该条
     88                                         $(this).attr("selected",true);
     89                                         
     90                                         //把该条的id和名称放入隐藏域
     91                                         $("#sid").val($(this).val());
     92                                         $("#s").val($(this).text());
     93                                     }else{
     94                                     
     95                                         //当是第一条时,把隐藏域的值清空,无法提交
     96                                         $("#sid").val("");
     97                                         $("#s").val("");
     98                                     }
     99                             }else{
    100                             
    101                                 //没有选中的去掉之前的selected
    102                                 $(this).removeAttr("selected");
    103                             }
    104 
    105                         });    
    106                     });
    107                 }
    108             })
    109         }else{
    110         
    111             //没有返回值,说明上一级菜单没有选中,删除第四级菜单
    112             $("#street").children().removeAttr("selected");
    113             $("#street").remove();
    114         }
    115     });
    116 });

    提交表单:

    addr.html js 部分代码片段

     1 //如果有一栏为空或者不可用,不允许提交
     2 $("#sub").click(function(){
     3 
     4     if($("#street").length == 0){ 
     5 
     6         if($("#pid").val() == "" || $("#p").val() == "" || $("#cid").val() == "" || $("#c").val() == "" || $("#aid").val() == "" || $("#a").val() == "" || $("#city").attr("disabled") === true || $("#area").attr("disabled") === true){
     7     
     8             alert("请完成选择");
     9             return false;
    10         }
    11     }else{
    12 
    13         if($("#pid").val() == "" || $("#p").val() == "" || $("#cid").val() == "" || $("#c").val() == "" || $("#aid").val() == "" || $("#a").val() == "" || $("#city").attr("disabled") === true || $("#area").attr("disabled") === true || $("#s").length == 0 || $("#s").val() == "" || $("#sid").length == 0 || $("#sid").val() == ""){
    14     
    15             alert("请完成选择");
    16             return false;
    17         }
    18     }
    19 });
    View Code

    如图:

    图1. 3 级联动

    图2. 4 级联动

    demo1 代码下载地址↓↓↓

    作者:小dee
    说明:作者写博目的是记录开发过程,积累经验,便于以后工作参考。
    如需转载,请在文章页面保留此说明并且给出原文链接。谢谢!
     
  • 相关阅读:
    课后总结
    构建之法阅读笔记01
    软件工程周总结02
    开课博客
    二维数组最大子数组和
    大二下周总结四
    大二下周总结三
    定义一个整型数组,返回该数组中子数组和的最大值
    软件工程开课
    定义一个数组返回最大子数组的值(1)
  • 原文地址:https://www.cnblogs.com/dee0912/p/4070899.html
Copyright © 2011-2022 走看看