基于ES6面向对象封装Cascader级联选择器组件

以省市区数据为例,组件效果如下:

image

技术亮点:

1.结合前后端通信,实现热更新式动态渲染数据
2.利用setter,getter在组件中设置访问器属性
3.利用事件侦听,抛发实现级联效果
4.实现自定义组件结构及样式

开发思路:

1.ajax请求:
    1)页面初始化请求只请求省级数据,且整个渲染及交互过程中省级数据接口只会请求一次。
    2)回调函数loadHandler中:判断本次ajax请求是哪个接口,如果是请求省级数据,则当省级数据获取后,利用组件实例化对象的setData方法,渲染省级组件,同时级联式去请求市级,县级的数据并渲染;如果本次请求市级接口则只渲染市级,县级数据,省级数据不变。
    3)给组件绑定click侦听事件,当点击组件时抛发数据,携带点击的组件名及当前选中的参数;同理利用上述2)中的方式判断点击的是哪一级的数据,从而选择性请求市级,县级数据或者只请求县级数据
2.setter,getter:给组件中的bool设置为访问器属性,当点击事件触发时,实现bool值取反,同时ul列表样式绑定bool值实现动态改变

组件代码如下:

import Component from "./Component.js";
//该组件的超类,主要作用是声明了elem属性为div元素,并创建appendTo插入目标页面指定的dom元素中的方法
import Utils from "./Utils.js";
// 公共方法,主要调用其中 Utils.addCSS方法将样式加入目标页面的样式表中

export default class Label extends Component{
  list;
  _bool = false;//访问器属性:setter和getter
  styleBool = false;
  name;
  constructor(_name) {
    super();
    this.name = _name;
    this.elem.className = "dropup";
    // 初始化创建组件时,只会给组件设置css样式及侦听元素的点击事件
    this.elem.addEventListener("click", (e) => this.clickHandler(e));
    // 渲染组件样式
    this.setStyle();
  }
  render() {
    // 渲染组件主要DOM结构
    this.elem.innerHTML = "";
    this.elem.innerHTML = `    
        
${this.list[0]}
    ${this.list.reduce(function (value,item) { value += "
  • " + item + "
  • "; return value; },"")}
` } clickHandler(e) { this.bool = !this.bool; if (e.target.nodeName === "LI") { this.elem.children[0].textContent = ""; this.elem.children[0].textContent = e.target.textContent; // 抛发事件时携带数据,当前展示的是哪条数据,以及属于哪一级 var evt=new Event("change"); evt.data = this.elem.children[0].textContent; evt.name = this.name; this.dispatchEvent(evt); } } setData(_list) { this.list = _list; this.render(); this.setStyle(); this.bool = false; } set bool(value){ if (value) { this.elem.children[1].className = "ul1"; } else { this.elem.children[1].className = "ul1 ul2"; } this._bool=value; } get bool(){ return this._bool; } setStyle() { if (this.styleBool) return; Utils.addCSS(".dropup", { width: "120px", height: "30px", lineHeight: "30px", borderRadius: "4px", border: "1px solid rgba(0,0,0,.15)", userSelect: "none", textAlign: "center", float: "left", marginLeft:"10px", }); Utils.addCSS(".ul1", { listStyle: "none", padding: "0", border: "1px solid rgba(0,0,0,.15)", borderRadius: "4px", boxShadow: "0 6px 12px rgba(0,0,0,.175)", margin: "2px 0 0", fontSize: "14px", textAlign: "left", transition: "all 0.2s", transitionProperty:"max-height", maxHeight: "500px", overflow: "auto", }); Utils.addCSS(".ul2", { maxHeight: "0px", border: "none", }); Utils.addCSS("li", { padding: "0px 10px", }); Utils.addCSS("li:hover", { backgroundColor: "rgba(0,0,0,0.15)", }); this.styleBool = true; } }

主页面js部分代码如下:

    import Label from "./js/Label.js"
    // 需要展示的省市县及源数据
    var provinceArr,cityArr,countyArr;
    // 省市区的实例化对象
    var province,city,county;
    init();
    function init(){
      render();//渲染出省市区的组件对象
      ajax("province");//初始化请求省一级数据,从而触发级联反应,loadhandler中请求市,区一级数据
      // 整个过程中province只调用一次
    }
    function render(){
      //创建省市区组件时不添加数据,统一在得到数据后,用setData添加数据渲染组件
      province=new Label("province");
      province.addEventListener("change",changeHandler);
      province.appendTo(".setPos");

      city=new Label("city");
      city.appendTo(".setPos");
      city.addEventListener("change",changeHandler);

      county=new Label("county");
      county.appendTo(".setPos");
      county.addEventListener("change",changeHandler);
    }
    function changeHandler(e){
      switch (e.name){
        case "province":
          // 点击省一栏时,市,县两级都要变
          // 调用ajax请求市的接口,从而触发loadhandler中的联级反应
          ajax("city", {
            province: e.data
          });
          break;
        case "city":
          // 点击市一栏时,县级需要变
          // 当前状态下省一级的展示数据
          var curProvince = province.elem.firstElementChild.textContent;
          // 调用ajax请求县的接口,从而触发loadhandler中的联级反应
          ajax("county", {
            province: curProvince,
            city: e.data
          });
          break;
      }
    }
    function ajax(name,data){
      var xhr = new XMLHttpRequest();
      xhr.addEventListener("load", loadHandler);
      switch (name) {
        case "province"://请求省份,加载第一层的数据
          xhr.open("POST", "http://服务端接口url/province");
          break;
        case "city"://请求市级,加载第二层数据
          xhr.open("POST", "http://服务端接口url/city");
          break;
        case "county"://请求县级,加载第三层数据
          xhr.open("POST", "http://服务端接口url/county");
          break;
      }
      data === undefined ? xhr.send() : xhr.send(JSON.stringify(data));
    }
    function loadHandler(e){
        var xhr=e.currentTarget;
        xhr.removeEventListener("load",loadHandler);
        // 通过剪切responseURL获取请求的type接口类型
        var type=xhr.responseURL.trim().split("?")[0].split("\/").pop();
        var sourceData=JSON.parse(xhr.response);
        switch(type){
          case "province":
            //当发现请求的是省级数据时,将省一级的数据渲染到组件上,同时需要去请求市一级的数据
            provinceArr = sourceData;
            ajax("city", {
              "province": provinceArr[0]
            });
            province.setData(provinceArr);
            break;
          case "city":
            // 当发现请求的是市级数据即市级数据需要更新,则将市一级的数据渲染到组件上,同时需要去请求县一级的数据
            cityArr = sourceData;
            var curProvince=province.elem.firstElementChild.textContent.trim();
            ajax("county", {
              "province": curProvince,
              "city": cityArr[0]
            });
            city.setData(cityArr);
            break;
          case "county":
            // 当发现是请求县一级数据时,直接将得到的数据渲染到组件上
            countyArr = sourceData;
            county.setData(countyArr);
        }
    }

接口文档如下:

省级数据:

请求方式:POST

请求地址:http://服务端接口url/province

请求参数格式:无

返回参数格式:

{
  "res":["北京", "上海", "河北", "山西", "内蒙古"]
}

市级数据:

请求方式:POST

请求地址:http://服务端接口url/city

请求参数格式:

{
  "province": "北京"
}

返回参数格式:

{
  "res":["北京"]
}

县级数据:

请求方式:POST

请求地址:http://服务端接口url/county

请求参数格式:

{
    "province": "北京",
    "city": "北京"
}

返回参数格式:

{
  "res":["东城区", "西城区", "崇文区", "宣武区", "朝阳区", "丰台区", "石景山区", "海淀区", "门头沟区", "房山区", "通州区", "顺义区", "昌平区", "大兴区", "平谷区", "怀柔区", "密云县", "延庆县", "其他"]
}

你可能感兴趣的