欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

收货地址之三级联动式菜单(省、市、区),利用AJAX通信,面向对象编程,并且将菜单放入指定容器中

程序员文章站 2024-03-14 20:46:29
...

效果图
收货地址之三级联动式菜单(省、市、区),利用AJAX通信,面向对象编程,并且将菜单放入指定容器中
实现功能:各级菜单之间实现联动效果。

请求方式:我是利用的AJAX请求数据,GET方式发送,利用了一个组件,将省份名和城市名存为对象直接发送给了服务端。后面我也会将组件的代码开源出来,以便你们理解。

// HTML代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #box{
            margin: 50px 300px;
        }
    </style>
</head>
<body>
    <div id="box"></div>
    <script type="module">
        import Menu from "./js/Menu.js"
        let div=document.querySelector("#box");
        let menu=new Menu();
        menu.appendTo(div);
    </script>
</body>
</html>

// JS代码

import QueryString from "./QueryString.js"
export default class Menu {
    elem;
    constructor(){
        this.elem=this.createMenu(); // 创建DOM元素
        this.ajax("province"); // 初次打开页面,设置菜单默认选项
    }
    createMenu(){
        if(this.elem) return this.elem;
        let div=document.createElement("div");// 创建最外层容器,用于存放所有的菜单容器
        this.render(div);// 创建菜单并渲染
        return div;
    }
    appendTo(parent){ // 将该对象插入到指定容器中
        if(typeof parent==="string") parent=document.querySelector(parent);
        parent.appendChild(this.elem);
    }
    ajax(type,data){ // 创建发送AJAX请求的方法,传入的参数为接口和请求数据
        if(data===undefined) data="";
        data=QueryString.stringify(data);
        var xhr=new XMLHttpRequest();
        xhr.addEventListener("load",e=>this.loadHanlder(e));
        xhr.open("GET","http://10.9.72.222:4008/"+type+"/?"+data);
        xhr.send();
    }
    render(parent){
        for(let i=0;i<3;i++){ 
            let div=document.createElement("div");// 菜单容器
            let div1=document.createElement("div");// 文本容器 
            let span1=document.createElement("span");// 存放选项的文本内容
            let span2=document.createElement("span");// 设置下拉箭头
            let ul=document.createElement("ul"); // 下拉菜单
            Object.assign(div.style,{
                marginLeft:"40px",
                cursor:'pointer',
                display:"inline-block",
                position:"relative",
                backgroundColor:'#fff',
                borderRadius: '4px',
                border: '1px solid #ccc',
            })
            Object.assign(div1.style,{
                color:'#333',
                padding:'6px 12px',
                height:'20px',
                fontSize:'14px',
                userSelect: 'none',
            })
            Object.assign(span1.style,{
                userSelect: 'none',
                fontSize:"14px",
                textAlign:"center",
                whiteSpace:"nowrap",
                lineHeight:"20px",
            })
            Object.assign(span2.style,{
                width:'0',
                height:'0',
                margin:"0px 2px",
                borderTop:'4px solid',
                borderRight:'4px solid transparent',
                borderLeft:'4px solid transparent',
                verticalAlign:'middle',
                display:'inline-block',
            })
            Object.assign(ul.style,{
                display:'none',
                color:'#262626',
                position:'absolute',
                top:'31px',
                left:'-2px',
                float:'left',
                minWidth:'100px',
                padding:'5px 0',
                margin:'2px 0 0',
                fontSize:'14px',
                textAlign:'left',
                listStyle:'none',
                backgroundColor:'#fff',
                border:'1px solid rgba(0, 0, 0, 0.15)',
                borderRadius:'4px',
                boxShadow:'0 6px 12px rgba(0, 0, 0, 0.175)',
            })
            div.addEventListener("mouseover",this.mouseHandler1);// 控制下拉菜单的显示
            div.addEventListener("mouseleave",e=>this.mouseHandler1(e));// 控制下拉菜单的隐藏
            ul.addEventListener("click",e=>this.clickHandler2(e));// 事件委托,点击li,切换菜单选项,并生成后续菜单内容
            ul.addEventListener("mouseover",e=>this.mouseHandler2(e));// 事件委托,li的鼠标划入效果
            ul.addEventListener("mouseout",e=>this.mouseHandler2(e));// 事件委托,li的鼠标划出效果
            ul.addEventListener("mouseleave",e=>this.mouseHandler2(e));// 鼠标离开ul时,ul列表隐藏
            div.setAttribute("class",`div${i}`);// 设置菜单容器的类名,用于区别三个菜单
            div1.appendChild(span1);
            div1.appendChild(span2);
            div.appendChild(div1);
            div.appendChild(ul);
            parent.appendChild(div);
        }
    }
    mouseHandler1(e){ 
        if(e.type==="mouseover"){
            Object.assign(e.currentTarget.style,{
                backgroundColor:'#d4d4d4',
                borderColor: '#8c8c8c',
            })
            e.currentTarget.lastElementChild.style.display='block';
        }else{
            Object.assign(e.currentTarget.style,{
                backgroundColor: '#fff',
                borderColor: '#ccc',
            })
            e.currentTarget.lastElementChild.style.display='none';
        }
    }
    clickHandler2(e){
        if(e.target.nodeName!=="LI") return;// 判断如果点击的不是li元素,则跳出
        switch(e.currentTarget.parentElement.getAttribute("class")){
            case "div0":
                e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
                this.ajax("city",{city:e.target.textContent})
                break;
            case "div1":
                e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
                this.ajax("county",{city:e.currentTarget.parentElement.previousElementSibling.firstElementChild.firstElementChild.textContent,county:e.target.textContent})
                break;
            case "div2":
                e.currentTarget.previousElementSibling.firstElementChild.textContent=e.target.textContent;
                break;
        }
    }
    mouseHandler2(e){
        if(e.type==="mouseleave") e.currentTarget.style.display="none";
        if(e.target.nodeName!=="LI") return;
        if(e.type==="mouseover"){
            e.target.style.backgroundColor='#f5f5f5';
        }else if(e.type==="mouseout"){
            e.target.style.backgroundColor='#fff';
        }
    }
    loadHanlder(e){ // AJAX请求完成后,触发该事件
        var xhr=e.currentTarget; 
        var type=xhr.responseURL.trim().split("?")[0];// 获取请求的接口类型,判断接下来进行哪步操作
        if(type.slice(-1)==="/") type=type.slice(0,-1); 
        type=type.split("/").pop();
        var o=JSON.parse(xhr.response);// 获取服务器发过来的数据
        switch(type){
            case "province":
                this.createList(o,0);// 创建第一层菜单的li,并添加至第一层菜单的ul下
                this.ajax("city",{city:this.elem.children[0].firstElementChild.textContent})// 创建第二层菜单,将第一层的选项文本内容发送给服务器
                break;
            case "city":
                this.createList(o,1);// 创建第二层菜单的li,并添加至第二层菜单的ul下
                this.ajax("county",{city:this.elem.children[0].firstElementChild.textContent,county:this.elem.children[1].firstElementChild.textContent})// 创建第三层菜单,将第一层和第二层的选项文本内容发送给服务器
                break;
            case "county":
                this.createList(o,2);// 创建第三层菜单的li,并添加至第三层菜单的ul下
                break;
        }
    }
    createList(data,n){
        this.elem.children[n].lastElementChild.innerHTML="";// 清空ul列表下的所有子元素
        let parent=document.createDocumentFragment();// 创建碎片容器,用于存放创建的li
        data.forEach((item,index)=>{ // 根据服务器返回的数据,遍历生成li
            let li=document.createElement("li");
            Object.assign(li.style,{
                padding:'3px 0px 3px 10px',
                color:'#333333',
                whiteSpace:'nowrap',
            })
            li.textContent=item;
            parent.appendChild(li);
            if(index===0) this.elem.children[n].firstElementChild.firstElementChild.textContent=item;// 设置菜单的默认选项
        })
        this.elem.children[n].lastElementChild.appendChild(parent);// 将创建好的li,放到对应菜单的ul中
    }
}

JS代码22行,转换数据用到的组件

export default class QueryString {
    static hasOwnProperty(obj, prop) {
        return Object.prototype.hasOwnProperty.call(obj, prop);
    }
    static parse(qs, sep, eq, options) {
        sep = sep || '&';
        eq = eq || '=';
        var obj = {};
        if (typeof qs !== 'string' || qs.length === 0) {
            return obj;
        }
        var regexp = /\+/g;
        qs = qs.split(sep);
        var maxKeys = 1000;
        if (options && typeof options.maxKeys === 'number') {
            maxKeys = options.maxKeys;
        }
        var len = qs.length;
        // maxKeys <= 0 means that we should not limit keys count
        if (maxKeys > 0 && len > maxKeys) {
            len = maxKeys;
        }
        for (var i = 0; i < len; ++i) {
            var x = qs[i].replace(regexp, '%20'),
                idx = x.indexOf(eq),
                kstr, vstr, k, v;
            if (idx >= 0) {
                kstr = x.substr(0, idx);
                vstr = x.substr(idx + 1);
            } else {
                kstr = x;
                vstr = '';
            }
            k = decodeURIComponent(kstr);
            v = decodeURIComponent(vstr);
            if (!QueryString.hasOwnProperty(obj, k)) {
                obj[k] = v;
            } else if (Array.isArray(obj[k])) {
                obj[k].push(v);
            } else {
                obj[k] = [obj[k], v];
            }
        }
        return obj;
    }
    static stringify(obj, sep, eq, name) {
        sep = sep || '&';
        eq = eq || '=';
        if (obj === null) {
            obj = undefined;
        }
        if (typeof obj === 'object') {
            return Object.keys(obj).map(function (k) {
                var ks = encodeURIComponent(QueryString.stringifyPrimitive(k)) + eq;
                if (Array.isArray(obj[k])) {
                    return obj[k].map(function (v) {
                        return ks + encodeURIComponent(QueryString.stringifyPrimitive(v));
                    }).join(sep);
                } else {
                    return ks + encodeURIComponent(QueryString.stringifyPrimitive(obj[k]));
                }
            }).join(sep);
        }
        if (!name) return '';
        return encodeURIComponent(QueryString.stringifyPrimitive(name)) + eq +
            encodeURIComponent(QueryString.stringifyPrimitive(obj));
    }
    static stringifyPrimitive(v) {
        switch (typeof v) {
            case 'string':
                return v;
            case 'boolean':
                return v ? 'true' : 'false';
            case 'number':
                return isFinite(v) ? v : '';
            default:
                return '';
        }
    };
}

Node.js

var querystring=require("querystring");
var server=http.createServer(function(req,res){
    var data="";
    res.writeHead(200,{
        "content-type":"text/html;charset=utf-8",
        "Access-Control-Allow-Origin":"*",
        "Access-Control-Allow-Headers":"*",// 请求头跨域 如果请求头发生修改并且非同源,就需要申请请求头跨域
    });
    req.on("data",function(_data){
        data+=_data;
    });
    req.on("end",function(){ // 传输数据完成时触发
        // type是接口名称
        var type=req.url.trim().split("?")[0].replace(/\//g,"");
        // reg.url的值即为网址IP+端口后面的内容,例:10.9.72.236:4010/project/?...      reg.url的值即为 /project/?/...
        if(req.method.toLowerCase()==="get"){
            if(req.url.includes("favicon.ico")) return res.end();//如果get请求的是图标直接返回空跳出
            // 如果是get请求,就从url中重新获取数据存入data变量
            data=req.url.includes("?") ? req.url.split("?")[1] : "";
        }
        try{ // 首先判断是否可以通过JSON解析,如果通过JSON直接转换,如果不能就是querystring解析
            data=JSON.parse(data);
        }catch(e){
            data=data ? querystring.parse(data) : {};
        }
        console.log(data);
        var o={}
        switch(type){
            case "province":
                o=Object.keys(obj);
                break;
            case "city":
                o=Object.keys(obj[data["city"]]);
                break;
            case "county":
                o=obj[data["city"]][data["county"]];
                break;
        }
        res.write(JSON.stringify(o));
        res.end();
    })
});
server.listen(4008,"10.9.72.222",function(){
    console.log("服务器开启成功");
})

接口文档

级联式菜单接口
URL :http://10.9.72.222:4010
Method :GET
DataType :
 请求消息: QueryString
 响应消息: JSON
接口名称
1、
?type=province      
?type=city
?type=county
2、
http://10.9.72.222:4010/province/
http://10.9.72.222:4010/city/
http://10.9.72.222:4010/county/
3、当使用Post发送时带入type类型判断
获取所有的省份

接口名 province
请求 :无
响应 :[“北京”,“天津”,“河北”,“山西”,“内蒙古”,…]

获取某省下所有的市

接口名 city
请求 :{city:‘省份名’}
响应 :[“石家庄”,“唐山”,“秦皇岛”,“邯郸”,“邢台”,…]

获取某市下所有的县区

接口名 county
请求 :{city:‘省份名’, county:‘城市名’}
响应 : [“东城区”,“西城区”,“宣武区”,“朝阳区”,…]

JSON文件,因为JSON文件过大,所以截图看下结构是怎样的就行
收货地址之三级联动式菜单(省、市、区),利用AJAX通信,面向对象编程,并且将菜单放入指定容器中