uniapp自定义picker城市多级联动组件

  • A+
所属分类:Web前端
摘要

注意事项:插件传入数据格式为children树形格式,内部包含:id、name组件运行图示:


uniapp自定义picker城市多级联动组件

  • 支持多端——h5、app、微信小程序、支付宝小程序...
  • 支持自定义配置picker插件级数
  • 支持无限级

注意事项:插件传入数据格式为children树形格式,内部包含:id、name

参数 类型 描述 默认值 必选
title string 标题 ''
layer number 控制几级联动 1
data arr 数据 如:[{text: '', adcode: '', children: [{text: '', adcode: ''}]}] []

组件运行图示:
uniapp自定义picker城市多级联动组件

组件选择后返回数据如:
uniapp自定义picker城市多级联动组件

引用示例:

<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>