/** * @Name: 基于layui的异步无限级联选择器 * @Author: 前端喵 * 创建时间: 2019/05/23 * 使用说明: 在主文件里面使用layui.config设置,具体方法看index.html */ /** * 参数说明: * width(可选) :input框宽度 * height(可选):input框高度 */ layui.define(["jquery"],(exports)=>{ let $ = layui.jquery; // 私有方法,禁止外面调用的方法 function Private(){ //页面初始化默认值 this.param = { width: 220, height: 40, prop: { value: "value", label: "label", children: 'children' }, clicklast: false, time: 250, placeholder: "请选择", search: { show: false, minLabel: 10, placeholder: '请输入搜索词' } }, // 定义全局状态仓库 this.store = { showCascader: false, cascaderDom: null, // 当前elem对象 cascaderAll: null, // 生成的Dom主对象 input: null, // input框dom inputI: null, // input框箭头dom model: null, // 下拉菜单的主dom li: null, // li标签 parentNextAll: null, // 当前操作的li标签元素后面所有ul集合 brother: null, // li标签同级dom集合 data: [], // 所有从后端异步请求的数据集合 chooseData: [], // 已选中的数据集 zIndex: 2000 // 显示顺序 } } // 页面初始化 Private.prototype.init = function(options){ let store = this.store let param = this.param // dom变量初始化 store.cascaderDom = $(options.elem) // 把用户的参数值进行存储 // 开始存储 for (let i in options) { if (options[i].length !== 0) { if (i == "prop" | i == 'search') { for (let x in options[i]) { param[i][x] = options[i][x] } } else { param[i] = options[i] } } } delete param.data if (options.data) { store.data = options.data } param.device = this.checkDevice() // 存储结束 if (store.cascaderDom.next().hasClass('cascader-all')) { store.cascaderDom.next().remove() } param.className = 'cascader-' + this.param.elem.replace('#', '') let phoneName = '' if (param.device === 1) { phoneName = 'cascader-model-phone' } // 渲染主dom store.cascaderDom.after(`
`) // 判断elem是否存在以及是否正确,elem必填 if(!options.elem || options.elem == ""){ layer.msg('请配置有效的elem值 ') }else if($(options.elem).length == 0){ layer.msg('请配置有效的elem值 ') } store.input = store.cascaderDom.nextAll().find('.cascader-input') store.inputI = store.input.next() store.cascaderAll = $(store.cascaderDom.nextAll()[0]) store.model = store.cascaderDom.nextAll().find('.cascader-model') store.li = store.model.find('li') // 全局状态初始化 store.model.hide() if (store.data.length == 0) { param.getChildren(param.value, data => { store.data = data this.liHtml(store.data) if (param.checkData) { if (param.checkData.length > 0) { this.dataToshow(param.checkData) } } }) } else { this.liHtml(store.data) if (param.checkData) { if (param.checkData.length > 0) { this.dataToshow(param.checkData) } } } // 先进入是否禁用判断事件 // 不禁用则执行下面的事件 this.disabled() .then(res => { this.inputClick(options) this.liClick() this.liHover() this.modelHandle() if (param.search.show) { this.handleSearch() } }) } // 判断是否禁用 Private.prototype.disabled = function() { const disabled = this.param.disabled const cascaderAll = this.store.cascaderAll const input = this.store.input return new Promise((resolve, reject) => { if (disabled) { cascaderAll.addClass('cascader-disabled') }else{ resolve() } }) } // li标签赋值方法 // key为string类型 Private.prototype.liHtml = function(data, key, choose){ let lis=[] let param = this.param let store = this.store let position = [] let key1 = '' if (!key || key.length == 0) { key="" } else { key = key.join('-') key1 = key key = key+"-" } if (data !== "") { for (let i in data) { let li = '
  • '+data[i][param.prop.label]+'
  • ' } else { li = li+'>'+data[i][param.prop.label]+'' } lis.push(li) } } lis = lis.join('') if (data && data.length>0){ if (param.search.show && data.length > param.search.minLabel) { lis = '' + lis } } else { lis = '

    暂无数据

    ' console.log('数据为空,无法进行渲染') } let ul = $(` `) ul.fadeIn('fast') store.model.append(ul) this.liPosition(position) this.ModelPosition() } // 当前选中的跳转位置 Private.prototype.liPosition = function(position) { let model = this.store.model.find('ul').last() } Private.prototype.handleSearch = function() { let model = this.store.model let prop = this.param.prop let _this = this let value = '' let flag = true model.on('compositionstart',function(){ flag = false }) model.on('compositionend',function(){ flag = true }) model.on('input', 'input', function() { setTimeout(()=>{ if (flag) { let data = _this.store.data if (value == this.value) { return } value = this.value let key = $(this).attr('key').split('-') let key1 = $(this).attr('key') + '-' if ($(this).attr('key')) { for (i in key) { if (data[key[i]][prop.children]){ data = data[key[i]][prop.children] } } } let renderData = [] let lis = '' for (i in data) { if (data[i][prop.label].indexOf(value) > -1) { if (data[i][prop.children] | data[i].hasChild) { lis += '
  • '+data[i][prop.label]+'
  • ' } else { lis += '
  • '+data[i][prop.label]+'
  • ' } renderData.push(data[i]) } } $(this.parentNode).find('li').remove() $(this.parentNode).append(lis) } }, 0) }) } Private.prototype.modelHandle = function() { $(window).resize(() => { //当浏览器大小变化时 let model = this.store.model this.ModelPosition() }) } let modelWidth = 0 Private.prototype.ModelPosition = function() { let model = this.store.model let input = this.store.input let BodyWidth = document.documentElement.clientWidth let positionLeft = 0, left = 0 if (window.getComputedStyle(model[0]).width !== "auto") { modelWidth = window.getComputedStyle(model[0]).width.replace('px','') } left = input.offset().left - model.position().left if (BodyWidth < modelWidth) { positionLeft = BodyWidth - modelWidth } if (positionLeft < 0) { model.css("left",positionLeft - 30) } else { model.css('left',0) } } // 鼠标hover监听事件[li标签] Private.prototype.liHover = function(){ let store = this.store let param = this.param let _this = this store.model.on('mouseenter', 'li', function() { store.parentNextAll = $(this).parent("ul").nextAll() store.brother = $(this).siblings() if ($(this).find('i').length == 0) { store.parentNextAll.fadeOut('fast', function() { store.parentNextAll.remove() }) } else { let keys=$(this).attr('key') let value = $(this).attr('value') let data = _this.store.data let childrenName = _this.param.prop.children keys = keys.split('-') let goodData = data for (let i in keys) { let key = keys[i] if (goodData) { if (goodData[key]) { goodData = goodData[key][childrenName] } } } if (!goodData) { param.getChildren(value, datax => { goodData = datax let children = data if (goodData && goodData.length != 0) { for (let i in keys) { if (i == keys.length - 1) { children = goodData } else { if (!children[keys[i]][childrenName]) { children[keys[i]][childrenName] = new Array() } children = children[keys[i]][childrenName] } } DataTreeAdd(data,goodData,keys) store.parentNextAll = $(this).parent("ul").nextAll() store.parentNextAll.remove() _this.liHtml(goodData, keys) } else { $(this).find('i').remove() store.parentNextAll.remove() DataTreeChange(data, keys) } }) } else { store.parentNextAll.remove() _this.liHtml(goodData, keys) } // 增加data的树结点 function DataTreeAdd(data, newData, keys) { let array = data for (let k in keys) { if (k < keys.length -1) { array = array[keys[k]][param.prop.children] } else { array = array[keys[k]] } } array.children = newData } function DataTreeChange(data, keys) { let array = data for (let k in keys) { if (k < keys.length -1) { array = array[keys[k]][param.prop.children] } else { array = array[keys[k]] } } array.hasChild = false } } $(this).addClass('cascader-choose-active') store.brother.removeClass('cascader-choose-active') store.parentNextAll.children().removeClass('cascader-choose-active') // 获取所有的已选中的数据,并回显至input选择框中 // _this.getChooseData(); }); } // 鼠标点击监听事件[li标签] Private.prototype.liClick = function() { let _this = this let store = this.store let param = this.param let className = param.className // store.model为一个自定义dom对象 if (param.clicklast == false) { store.model.on('click', 'li', function() { _this.getChooseData() store.showCascader = !store.showCascader if (param.device === 1){ } else { store.model.slideUp(_this.param.time) } store.inputI.removeClass('rotate') }) } else { store.model.on('click', 'li', function() { store.parentNextAll = $(this).parent("ul").nextAll() if (store.parentNextAll.length == 0) { _this.getChooseData() store.showCascader = !store.showCascader if (param.device === 1){ } else { store.model.slideUp(_this.param.time) } store.inputI.removeClass('rotate') _this.getThisData() } }) } } // 判断当前访问客户端是PC还是移动端 // PC端返回1,移动端返回0 Private.prototype.checkDevice = function() { if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) { return 1 } else { return 0 } } // 获取当前层级数据 Private.prototype.getThisData = function() { let value = this.param.prop.value ,children = this.param.prop.children ,chooseData = this.store.chooseData ,data = this.store.data ,currentData = [] for (const i in chooseData) { for (const x in data) { if (chooseData[i] == data[x][value]) { if (Number(i) === chooseData.length - 1) { currentData = JSON.parse(JSON.stringify(data[x])) break } if (data[x][children]) { data = data[x][children] } else { data = data[x] } break } } } return currentData } // 鼠标监听事件[input控件] Private.prototype.inputClick = function(options) { let store = this.store let param = this.param let _this = this $(document).click(function(e) { let className = e.target.className className = className.split(" ") let other = ['cascader-input', 'cascader-model', "cascader-choose-active", "layui-icon-right", "cascader-ul", "cascader-model-input"] for (let i in className) { for (let x in other) { if (className[i] == other[x]) { return } } } store.showCascader = false store.model.slideUp(_this.param.time) store.inputI.removeClass('rotate') }); store.input.click(function() { store.showCascader = !store.showCascader if (store.showCascader == true) { store.inputI.addClass('rotate') let chooseData = _this.store.chooseData let data = _this.store.data if (chooseData.length !== 0) { let key = [] _this.clearModel() for (let i in chooseData) { for (let x in data) { if (data[x][param.prop.value] == chooseData[i]) { _this.liHtml(data,key,x) key.unshift(x) data = data[x][param.prop.children] break } } } } store.model.slideDown(_this.param.time) _this.ModelPosition() } else { store.model.slideUp(_this.param.time) store.inputI.removeClass('rotate') } }) } // 获取页面中选中的数据 Private.prototype.getChooseData = function() { let store = this.store let chooseDom = store.model.find('li.cascader-choose-active') let chooseData = []; let chooseLabel = []; for(let i in chooseDom){ if(chooseDom[i].innerText){ chooseData.push($(chooseDom[i]).attr('value')); chooseLabel.push(chooseDom[i].innerText); }else{ break; } } this.store.chooseData = chooseData; this.inputValueChange(chooseLabel); } Private.prototype.inputValueChange = function(label){ let store = this.store; let param = this.param; if(param.showlast == true){ label = label[label.length-1]; }else{ label = label.join('/'); } store.input.val(label); let fontWidth = store.input.css('font-size').replace('px',''), inputWidth = store.input.width(), labelWidth = label.length; let maxLabelWidth = Math.floor(inputWidth/fontWidth); if(labelWidth>maxLabelWidth){ store.input.attr('title',label); }else{ store.input.attr('title',""); } } // 数据回显 Private.prototype.dataToshow = function(checkData){ let param = this.param; let store = this.store; let backData = []; //后端数据集合 let chooseLabel=[]; //选中的项对应的label值 let keys=[]; //checkData在数据源中的位置大全 backData[0] = store.data; let flag = 1; if(param.getChildren){ if (checkData.length === 1) { for (let i in store.data) { if (store.data[i][param.prop.value] == checkData[0]) { let label = store.data[i][param.prop.label].split(',') this.inputValueChange(label) return } } } for(let i=1; i{ backData[i] = data; flag++; if(flag == checkData.length){ for(let i=checkData.length -1;i>=0;i--){ for(let x in backData[i]){ if(checkData[i] == backData[i][x][param.prop.value]){ keys.unshift(x); chooseLabel.unshift(backData[i][x][param.prop.label]) if(i < checkData.length -1){ backData[i][x][param.prop.children] = backData[i+1]; } } } } store.data = backData[0]; // input框数据回显 this.inputValueChange(chooseLabel); this.clearModel(); // 选择器数据回显 let key = []; for(let i in backData){ if(i !== "0"){ key.push(keys[i-1]); } this.liHtml(backData[i],key,keys[i]); } } }); } } }else{ let storeData = store.data; for(let x in checkData){ for(let i in storeData){ if(storeData[i][param.prop.value] == checkData[x]){ chooseLabel.push(storeData[i][param.prop.label]); keys.push(i) storeData = storeData[i][param.prop.children]; break; } } } // input框数据回显 this.inputValueChange(chooseLabel); this.store.chooseData = checkData; } } // 清空ul标签 Private.prototype.clearModel = function(){ let store = this.store; store.model.html(''); } // 监听下拉菜单的位置 Private.prototype.handlePosition = function(){ // 当前屏幕大小 // let bodyWidth = } let privates = new Array(); let dom_num = 0; // 暴露给外界使用的方法 let cascader = { // 页面初始化 load: function load(options) { let current = null for (let i in privates) { if(privates[i].elem === options.elem){ current = i } } if (!current) { current = dom_num dom_num ++ privates[current] = new Array() privates[current].obj = new Private() } privates[current].elem = options.elem privates[current].obj.store.zIndex -= current privates[current].obj.store.data = [] if (options.chooseData) { privates[current].obj.store.chooseData = options.chooseData } else { privates[current].obj.store.chooseData = [] } privates[current].obj.init(options) }, // 获取页面中选中的数据,数组形式 getChooseData: function(elem){ let obj = this.elemCheck(elem); return obj.store.chooseData; }, // 监听方法 on: function(type,elem,callback){ let obj = this.elemCheck(elem) let className = obj.param.className if(type == "click"){ $(document).on('click','.' + className + ' li',function(){ setTimeout(function(){ let data = obj.getThisData() if(obj.param.clicklast === false){ callback(data) }else{ if(obj.store.parentNextAll.length == 0){ callback(data) } } },50) }); }else if(type == "hover"){ obj.store.model.on('mouseenter','li',function(){ callback(); }); } }, // elem位置判断,禁止外界调用,因为你调也没啥卵用 elemCheck:function(elem){ if(!elem){ return privates[0].obj; } for(let i in privates){ if(privates[i].elem == elem){ return privates[i].obj; } } } } layui.link(layui.cache.base + 'ajaxCascader.css'); exports('ajaxCascader',cascader); });