uniapp自定义picker城市多级联动组件
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>