- A+
所属分类:Web前端
uniapp自定义picker城市多级联动组件
支持多端——h5、app、微信小程序、支付宝小程序...
支持自定义配置picker插件级数
支持无限级
注意事项:插件传入数据格式为children树形格式,内部包含:id、name
参数 | 类型 | 描述 | 默认值 | 必选 |
---|---|---|---|---|
title | string | 标题 | '' | 否 |
layer | number | 控制几级联动 | 1 | 否 |
data | arr | 数据 如:[{text: '', adcode: '', children: [{text: '', adcode: ''}]}] | [] | 否 |
组件运行图示:
组件选择后返回数据如:
引用示例:
<template> <view class="content"> <view class="aui-content" :style="{height: contentHeight}"> <view class="aui-btn aui-btn-blue" @click.stop="showPicker($event)">picker无限级联动</view> </view> <aui-picker ref="picker" :title="auiPicker.title" :layer="auiPicker.layer" :data="auiPicker.data" @callback="pickerCallback" ></aui-picker> </view> </template> <script> import auiPicker from '@/components/aui-picker/aui-picker.vue'; export default { components: { auiPicker }, data() { return { auiPicker: { title: 'picker多级联动', layer: null, data: [] }, } }, created(){ }, mounted() { }, methods: { //显示picker多级联动弹窗 showPicker(e){ const _this = this; _this.auiPicker.data=[{ id: "1001", name: "一级菜单1", children: [{ id: "1002", name: "二级菜单1-1", children: [{ id: "1003", name: "三级菜单1-1", children: [{ id: "1004", name: "四级菜单1-1" }] }] }] }, { id: "1005", name: "一级菜单2", children: [{ id: "1006", name: "二级菜单2-1", children: [{ id: "1007", name: "三级菜单2-1", children: [{ id: "1008", name: "四级菜单2-1" }] }] }] }]; _this.$refs.picker.open().then(function(){ console.log('picker打开'); }); }, //picker多级联动回调 pickerCallback(e){ const _this = this; console.log(e); let result = ''; e.data.forEach(function(item, index){ result += item.name + ' '; }); uni.showModal({ title: '提示', content: result, success: function (res) { if (res.confirm) { console.log('用户点击确定'); } else if (res.cancel) { console.log('用户点击取消'); } } }); } } } </script> <style> .aui-content{padding: 15px 0 0 0;} </style>
aui-picker组件完整代码:
项目components文件夹下创建aui-picker夹,此文件夹下创建aui-picker.vue——多级联动组件
<template name="aui-picker"> <view class="aui-picker" v-if="SHOW" :class="{ 'aui-picker-in': FADE==1, 'aui-picker-out': FADE==0}" > <view class="aui-mask" @click.stop="close"></view> <view class="aui-picker-main"> <view class="aui-picker-header"> <view class="aui-picker-title" v-if="title">{{title}}</view> <view class="aui-picker-close iconfont iconclose" @click.stop="close"></view> </view> <view class="aui-picker-nav"> <view class="aui-picker-navitem" v-if="nav.length>0" v-for="(item, index) in nav" :key="index" :data-index="index" :class="[index==navCurrentIndex ? 'active' : '', 'aui-picker-navitem-'+index]" :style="{margin: nav.length>2 ? '0 10px 0 0' : '0 30px 0 0'}" @click.stop="_changeNav($event)" >{{item.name}}</view> <view class="aui-picker-navitem" :key="nav.length" :data-index="nav.length" :class="[nav.length==navCurrentIndex ? 'active' : '', 'aui-picker-navitem-'+nav.length]" :style="{margin: nav.length>2 ? '0 10px 0 0' : '0 30px 0 0'}" @click.stop="_changeNav($event)" >请选择</view> <view class="aui-picker-navborder" :style="{left: navBorderLeft+'px'}"></view> </view> <view class="aui-picker-content"> <view class="aui-picker-lists"> <view class="aui-picker-list" v-for="(list, index) in queryItems.length + 1" :key="index" :data-index="index" :class="[index==navCurrentIndex ? 'active' : '']" > <view class="aui-picker-list-warp" v-if="index == 0"> <view class="aui-picker-item" v-for="(item, key) in items" v-if="item.pid=='0'" :key="key" :data-pindex="index" :data-index="key" :data-id="item.id" :data-pid="item.pid" :data-name="item.name" :class="{'active': result.length>index && result[index].id==item.id}" :style="{'background': touchConfig.index==key && touchConfig.pindex==index ? touchConfig.style.background : ''}" @click.stop="_chooseItem($event)" @touchstart="_btnTouchStart($event)" @touchmove="_btnTouchEnd($event)" @touchend="_btnTouchEnd($event)" >{{item.name}}</view> </view> <view class="aui-picker-list-warp" v-else> <view class="aui-picker-item" v-for="(item, key) in queryItems[index-1]" :key="key" :data-pindex="index" :data-index="key" :data-id="item.id" :data-pid="item.pid" :data-name="item.name" :class="{'active': result.length>index && result[index].id==item.id}" :style="{'background': touchConfig.index==key && touchConfig.pindex==index ? touchConfig.style.background : ''}" @click.stop="_chooseItem($event)" @touchstart="_btnTouchStart($event)" @touchmove="_btnTouchEnd($event)" @touchend="_btnTouchEnd($event)" >{{item.name}}</view> </view> </view> </view> </view> </view> </view> </template> <script> export default { name: 'aui-picker', props: { title: { //标题 type: String, default: '' }, layer: { //控制几级联动,默认无限级(跟随数据有无下级) type: Number, default: null }, data: { //数据 如:[{id: '', name: '', children: [{id: '', name: ''}]}] type: Array, default (){ return [ // [{id: '', name: '', children: [{id: '', name: ''}]}] ] } } }, data(){ return { SHOW: false, FADE: -1, nav: [], items: [], queryItems: [], navCurrentIndex: 0, navBorderLeft: 40, result: [], touchConfig: { index: -1, pindex: -1, style: { color: '#197DE0', background: '#EFEFEF' } } } }, created(){ const _this = this; }, watch:{ data(){ const _this = this; const data = _this.data; _this.items = _this._flatten(data, '0') } }, mounted(){ }, methods:{ // 打开 open(){ const _this = this; _this.reset(); //打开时重置picker return new Promise(function(resolve, reject){ _this.SHOW = true; _this.FADE = 1; resolve(); }); }, // 关闭 close(){ const _this = this; return new Promise(function(resolve, reject){ _this.FADE = 0; const _hidetimer = setTimeout(()=>{ _this.SHOW = false; _this.FADE = -1; clearTimeout(_hidetimer); resolve(); },100) }); }, //重置 reset(){ const _this = this; _this.queryItems = []; _this.nav = []; _this.navBorderLeft = 40; _this.navCurrentIndex = 0; _this.result = []; }, //导航栏切换 _changeNav(e){ const _this = this; const index = Number(e.currentTarget.dataset.index); _this.navCurrentIndex = index; const _el = uni.createSelectorQuery().in(this).select(".aui-picker-navitem-"+index); _el.boundingClientRect(data => { _this.navBorderLeft = data.left + 20; }).exec(); }, //数据选择 _chooseItem(e){ const _this = this; const id = e.currentTarget.dataset.id; const name = e.currentTarget.dataset.name; const pid = e.currentTarget.dataset.pid; const _arr = []; _this.result[_this.navCurrentIndex] = {id: id, name: name, pid: pid}; if( (!_this._isDefine(_this.layer) && _this._isDefine(_this._deepQuery(_this.data, id).children)) || (_this.navCurrentIndex < (Number(_this.layer) - 1) && _this._isDefine(_this._deepQuery(_this.data, id).children)) ) { //有下级数据 _this._deepQuery(_this.data, id).children.forEach(function(item, index){ _arr.push({id: item.id, name: item.name, pid: id}); }); if(_this.navCurrentIndex == _this.queryItems.length) { //选择数据 _this.queryItems.push(_arr); _this.nav.push({name: name}); } else { //重新选择数据 _this.queryItems.splice(_this.navCurrentIndex+1, 1); _this.nav.splice(_this.navCurrentIndex+1, 1); _this.queryItems.splice(_this.navCurrentIndex, 1, _arr); _this.nav.splice(_this.navCurrentIndex, 1, {name: name}); } _this.navCurrentIndex = _this.navCurrentIndex + 1; const _el = uni.createSelectorQuery().in(this).select(".aui-picker-navitem-"+_this.navCurrentIndex); setTimeout(()=>{ _el.boundingClientRect(data => { _this.navBorderLeft = data.left + 20; }).exec(); },100) } else { //无下级数据 _this.close().then(()=>{ _this.$emit("callback", {status: 0, data: _this.result}); }); } }, //递归遍历——将树形结构数据转化为数组格式 _flatten(tree, pid) { return tree.reduce((arr, {id, name, children = []}) => arr.concat([{id, name, pid}], this._flatten(children, id)), []) }, //根据id查询对应的数据(如查询id=10100对应的对象) _deepQuery(tree, id) { let isGet = false; let retNode = null; function deepSearch(tree, id){ for(let i = 0; i < tree.length; i++) { if(tree[i].children && tree[i].children.length > 0) { deepSearch(tree[i].children, id); } if(id === tree[i].id || isGet) { isGet||(retNode = tree[i]); isGet = true; break; } } } deepSearch(tree, id); return retNode; }, /***判断字符串是否为空 @param {string} str 变量 @example: aui.isDefine("变量"); */ _isDefine(str){ if (str==null || str=="" || str=="undefined" || str==undefined || str=="null" || str=="(null)" || str=='NULL' || typeof (str)=='undefined'){ return false; }else{ str = str + ""; str = str.replace(/s/g, ""); if (str == ""){return false;} return true; } }, _btnTouchStart(e){ const _this = this, index = Number(e.currentTarget.dataset.index), pindex = Number(e.currentTarget.dataset.pindex); _this.touchConfig.index = index; _this.touchConfig.pindex = pindex; }, _btnTouchEnd(e){ const _this = this, index = Number(e.currentTarget.dataset.index), pindex = Number(e.currentTarget.dataset.pindex); _this.touchConfig.index = -1; _this.touchConfig.pindex = -1; }, } } </script> <style scoped> /* ==================== 多级联动弹窗 =====================*/ .aui-picker{ width: 100vw; height: 100vh; opacity: 0; position: fixed; top: 0; left: 0; z-index: 999; /* display: none; */ } .aui-picker.aui-picker-in{ -moz-animation: aui-fade-in .1s ease-out forwards; -ms-animation: aui-fade-in .1s ease-out forwards; -webkit-animation: aui-fade-in .1s ease-out forwards; animation: aui-fade-in .1s ease-out forwards; } .aui-picker.aui-picker-out{ -moz-animation: aui-fade-out .1s ease-out forwards; -ms-animation: aui-fade-out .1s ease-out forwards; -webkit-animation: aui-fade-out .1s ease-out forwards; animation: aui-fade-out .1s ease-out forwards; } .aui-picker-main{ width: 100vw; height: 50vh; background: #FFF; border-radius: 15px 15px 0 0; position: absolute; left: 0px; bottom: -50vh; z-index: 999; } .aui-picker.aui-picker-in .aui-picker-main{ -moz-animation: aui-slide-up-screen .2s ease-out forwards; -ms-animation: aui-slide-up-screen .2s ease-out forwards; -webkit-animation: aui-slide-up-screen .2s ease-out forwards; animation: aui-slide-up-screen .2s ease-out forwards; } .aui-picker.aui-picker-out .aui-picker-main{ -moz-animation: aui-slide-down-screen .2s ease-out forwards; -ms-animation: aui-slide-down-screen .2s ease-out forwards; -webkit-animation: aui-slide-down-screen .2s ease-out forwards; animation: aui-slide-down-screen .2s ease-out forwards; } .aui-picker-header{ width: 100%; min-height: 50px; position: relative; z-index: 999; background: #F2F2F2; border-radius: 15px 15px 0 0; } .aui-picker-header::after{ content: ''; width: 100%; height: 1px; background: rgba(100,100,100,.3); -moz-transform: scaleY(.3); -ms-transform: scaleY(.3); -webkit-transform: scaleY(.3); transform: scaleY(.3); position: absolute; left: 0; bottom: 0; z-index: 999; } .aui-picker-title{ line-height: 20px; text-align: center; font-size: 17px; color: #333; padding: 15px; box-sizing: border-box; position: absolute; left: 50px; right: 50px; top: 0; } .aui-picker-close.iconfont{ width: 50px; height: 50px; line-height: 50px; text-align: center; font-size: 20px; color: #aaa; border-radius: 0 10px 0 0; position: absolute; right: 0; top: 0; } .aui-picker-content{ width: 100%; height: -webkit-calc(100% - 100px); height: calc(100% - 100px); } .aui-picker-nav{ width: 100%; height: 50px; text-align: left; padding: 0 20px; margin: 0 0 1px 0; justify-content: flex-start; white-space: nowrap; box-sizing: border-box; position: relative; } .aui-picker-nav::after{ content: ''; width: 100%; height: 1px; background: rgba(100,100,100,.3); -moz-transform: scaleY(.3); -ms-transform: scaleY(.3); -webkit-transform: scaleY(.3); transform: scaleY(.3); position: absolute; left: 0; bottom: 0; z-index: 999; } .aui-picker-navitem{ width: 80px; line-height: 50px; font-size: 16px; margin: 0 30px 0 0; text-align: center; display: inline-block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .aui-picker-navitem.active{ color: #197DE0; } .aui-picker-navborder{ width: 40px; height: 3px; background: #197DE0; border-radius: 5px; transition: left .15s; position: absolute; left: 40px; bottom: 0; } .aui-picker-lists{ width: 100%; height: 100%; justify-content: space-around; white-space: nowrap; } .aui-picker-list{ width: 100%; height: 100%; overflow: hidden; overflow-y: scroll; display: none; vertical-align: top; } .aui-picker-list.active{ display: inline-block; } .aui-picker-list-warp{ width: 100%; height: auto; box-sizing: border-box; padding: 15px 0; display: inline-block; } .aui-picker-item{ width: 100%; height: 50px; line-height: 50px; padding: 0 15px; box-sizing: border-box; font-size: 15px; color: #333; position: relative; } .aui-picker-item.active{ color: #197DE0; } .aui-picker-item.active::after{ content: '✔'; font-size: 15px; color: #197DE0; position: absolute; top: 0px; right: 10px; } </style>