参考Ajaxguan的文章:https://blog.csdn.net/Ajaxguan/article/details/82906705
因为公司项目需要一个省市区三级联动的选择框,而且因为不想项目体积太大,不给引入大型的组件库;在网上找了一些例子样式都不太合适,直到看到上面的文章,因为公司项目用到省市区的地方比较多,理解了一下原理就改成插件形式并且添加了一些注释,有需要的可以看一下(如果有什么错误或者更简洁的写法欢迎指出来)。
因为css用到了rem,所以要记得引入lib-flexible
<template>
<div class="address" v-if="showModal">
<div class="addressboxbg">div>
<div class="addressbox slideInUp">
<div class="text_btn" flex="main:justify cross:center"><span class="area_btn" @click="cancel">取消span>选择省市区<span class="area_btn" @click="complete">完成span>div>
<div class="addressSelect">
<div class="selectbox">div>
<ul @touchstart="touchStart($event,'province')" @touchmove="touchMove($event,'province')" @touchend="touchEnd($event,'province')" :style="provinceStyle" :class="[{'selectAni':addSelect}]">
<li v-for="(item,index) in provinceArray" :class="[{'addSelectActive':index == provinceIndex}]" :key="index">{{item.name}}li>
ul>
<ul @touchstart="touchStart($event,'city')" @touchmove="touchMove($event,'city')" @touchend="touchEnd($event,'city')" :style="cityStyle" :class="[{'selectAni':addSelect}]">
<li v-for="(item,index) in cityArray" :class="[{'addSelectActive':index == cityIndex}]" :key="index">{{item.name}}li>
ul>
<ul @touchstart="touchStart($event,'district')" @touchmove="touchMove($event,'district')" @touchend="touchEnd($event,'district')" :style="districtStyle" :class="[{'selectAni':addSelect}]">
<li v-for="(item,index) in areaArray" :class="[{'addSelectActive':index == districtIndex}]" :key="index">{{item.name}}li>
ul>
div>
div>
div>
template>
<script>
export default {
data() {
return {
provinceStyle: {
WebkitTransform: 'translate3d(0px,0px,0px)'
},//省平移距离
cityStyle: {
WebkitTransform: 'translate3d(0px,0px,0px)'
},//市平移距离
districtStyle: {
WebkitTransform: 'translate3d(0px,0px,0px)'
},//区平移距离
startTop: 0,//滑动开始时鼠标距离顶部的距离
provinceIndex: 0,//选择的省的下标
cityIndex: 0,//选择的市的下标
districtIndex: 0,//选择的区的下标
translateY: 0,//滑动开始时省市区平移的距离
maxScroll: 0,//最大滑动距离
addHeight: 0,//单个li框的高度
addSelect: false,//控制滑动后执行动画
provinceVal: '',//选中的省的code
cityVal: '',//选中的市的code
areaVal: '',//选中的区的code
val: {
provinceVal: '',//选中的省的对象
cityVal: '',//选中的市的对象
areaVal: ''//选中的区的对象
},
showModal: false,//是否显示省市区弹框
provinceArrayAll: [],//默认全部省列表
cityArrayAll: [],//默认全部市列表
areaArrayAll: [],//默认全部区列表
provinceArray: [],//联动后的省列表
cityArray: [],//联动后的市列表
areaArray: []//联动后的区列表
};
},
watch: {
// 监听省滑动
provinceVal(code) {
let list = [];
this.cityArrayAll.forEach(item => {
if (item.parentCode == code) {
list.push(item);
}
});
this.cityArray = list;
this.cityVal = this.cityArray[0].code;
this.val.cityVal = this.cityArray[0];
},
// 监听市滑动
cityVal(code) {
if (code) {
let list = [];
this.areaArrayAll.forEach(item => {
if (item.parentCode == code) {
list.push(item);
}
});
this.areaArray = list;
this.areaVal = this.areaArray[0].code;
this.val.areaVal = this.areaArray[0];
} else {
this.areaVal = '';
this.val.areaVal = [];
}
}
},
created() {},
methods: {
// 滑动开始
touchStart(e, val) {
e.preventDefault();
this.addSelect = false;
this.addHeight = e.currentTarget.children[0].offsetHeight;
this.maxScroll = this.addHeight * e.currentTarget.children.length;
this.startTop = e.targetTouches[0].pageY;
switch (val) {
case 'province':
this.translateY = parseInt(
this.provinceStyle.WebkitTransform.slice(
this.provinceStyle.WebkitTransform.indexOf(',') + 1,
this.provinceStyle.WebkitTransform.lastIndexOf(',')
)
);
break;
case 'city':
this.translateY = parseInt(
this.cityStyle.WebkitTransform.slice(
this.cityStyle.WebkitTransform.indexOf(',') + 1,
this.cityStyle.WebkitTransform.lastIndexOf(',')
)
);
break;
case 'district':
this.translateY = parseInt(
this.districtStyle.WebkitTransform.slice(
this.districtStyle.WebkitTransform.indexOf(',') + 1,
this.districtStyle.WebkitTransform.lastIndexOf(',')
)
);
break;
default:
break;
}
},
// 滑动进行中
touchMove(e, val) {
e.preventDefault();
switch (val) {
case 'province':
if (e.targetTouches[0].pageY - this.startTop + this.translateY > 0) {
//鼠标滑动的距离加上子元素平移的距离大于0,即表示滑动已经到头
this.provinceStyle.WebkitTransform = 'translate3d(0px,0px,0px)';
} else if (
e.targetTouches[0].pageY - this.startTop + this.translateY <
-(this.maxScroll - this.addHeight)
) {
//鼠标滑动的距离加上子元素平移的距离小于子元素最大平移距离,即表示滑动已经到底
this.provinceStyle.WebkitTransform =
'translate3d(0px,' +
-(this.maxScroll - this.addHeight) +
'px,0px)';
} else {
this.provinceStyle.WebkitTransform =
'translate3d(0px,' +
(e.targetTouches[0].pageY - this.startTop + this.translateY) +
'px,0px)';
}
break;
case 'city':
if (e.targetTouches[0].pageY - this.startTop + this.translateY > 0) {
this.cityStyle.WebkitTransform = 'translate3d(0px,0px,0px)';
} else if (
e.targetTouches[0].pageY - this.startTop + this.translateY <
-(this.maxScroll - this.addHeight)
) {
this.cityStyle.WebkitTransform =
'translate3d(0px,' +
-(this.maxScroll - this.addHeight) +
'px,0px)';
} else {
this.cityStyle.WebkitTransform =
'translate3d(0px,' +
(e.targetTouches[0].pageY - this.startTop + this.translateY) +
'px,0px)';
}
break;
case 'district':
if (e.targetTouches[0].pageY - this.startTop + this.translateY > 0) {
this.districtStyle.WebkitTransform = 'translate3d(0px,0px,0px)';
} else if (
e.targetTouches[0].pageY - this.startTop + this.translateY <
-(this.maxScroll - this.addHeight)
) {
this.districtStyle.WebkitTransform =
'translate3d(0px,' +
-(this.maxScroll - this.addHeight) +
'px,0px)';
} else {
this.districtStyle.WebkitTransform =
'translate3d(0px,' +
(e.targetTouches[0].pageY - this.startTop + this.translateY) +
'px,0px)';
}
break;
default:
break;
}
},
// 滑动结束
touchEnd(e, val) {
e.preventDefault();
this.addSelect = true;
//根据平移距离判断在哪个li并返回下标
switch (val) {
case 'province':
let provinceTranslateY = parseInt(
this.provinceStyle.WebkitTransform.slice(
this.provinceStyle.WebkitTransform.indexOf(',') + 1,
this.provinceStyle.WebkitTransform.lastIndexOf(',')
)
);
this.provinceIndex = -Math.round(provinceTranslateY / this.addHeight);
this.provinceStyle.WebkitTransform =
'translate3d(0px,' +
Math.round(provinceTranslateY / this.addHeight) * this.addHeight +
'px,0px)';
this.cityStyle.WebkitTransform = this.districtStyle.WebkitTransform =
'translate3d(0px,0px,0px)';
this.cityIndex = this.districtIndex = 0;
break;
case 'city':
let cityTranslateY = parseInt(
this.cityStyle.WebkitTransform.slice(
this.cityStyle.WebkitTransform.indexOf(',') + 1,
this.cityStyle.WebkitTransform.lastIndexOf(',')
)
);
this.cityIndex = -Math.round(cityTranslateY / this.addHeight);
this.cityStyle.WebkitTransform =
'translate3d(0px,' +
Math.round(cityTranslateY / this.addHeight) * this.addHeight +
'px,0px)';
this.districtStyle.WebkitTransform = 'translate3d(0px,0px,0px)';
this.districtIndex = 0;
break;
case 'district':
let districtTranslateY = parseInt(
this.districtStyle.WebkitTransform.slice(
this.districtStyle.WebkitTransform.indexOf(',') + 1,
this.districtStyle.WebkitTransform.lastIndexOf(',')
)
);
this.districtIndex = -Math.round(districtTranslateY / this.addHeight);
this.districtStyle.WebkitTransform =
'translate3d(0px,' +
Math.round(districtTranslateY / this.addHeight) * this.addHeight +
'px,0px)';
break;
default:
break;
}
// 滑动结束后 处理数据
this.dataProcessing();
},
// 数据处理
dataProcessing() {
// 滑动数据传输 数据处理
this.val.provinceVal = this.provinceArray[this.provinceIndex];
this.provinceVal = this.provinceArray[this.provinceIndex].code;
this.val.cityVal = this.cityArray[this.cityIndex];
this.cityVal = this.cityArray[this.cityIndex].code;
this.val.areaVal = this.areaArray[this.districtIndex];
this.areaVal = this.areaArray[this.districtIndex].code;
// this.val.cityVal = this.addressData[this.provinceIndex].city[this.cityIndex].name
// this.val.areaVal = this.addressData[this.provinceIndex].city[this.cityIndex].area[this.districtIndex]
// this.$emit('getAddress', this.val)
// this.test([this.val.provinceVal, this.cityIndex, this.districtIndex])
}
}
};
script>
<style>
.address {
position: fixed;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
.address .addressbox {
height: 200px;
position: absolute;
z-index: 101;
width: 100%;
max-height: 100%;
overflow: hidden;
background: #fff;
bottom: 0px;
}
.address .addressbox .text_btn {
background-color: #fff;
border-bottom: 0.16rem solid #e7ecf2;
font-size: 0.427rem;
position: relative;
color: #999;
}
.address .addressbox .text_btn .area_btn {
color: #0099d9;
font-size: 0.4rem;
line-height: 1em;
text-align: center;
padding: 0.8em 1em;
}
.addressSelect .selectbox {
width: 100%;
height: 0.8rem;
border-top: 1px solid #ddecfa;
border-bottom: 1px solid #ddecfa;
margin-top: 58px;
background: transparent;
}
.address .addressboxbg {
position: absolute;
left: 0;
top: 0;
z-index: 100;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
}
.addressSelect {
width: 100%;
position: relative;
background: #fff;
height: 150px;
overflow: hidden;
-webkit-mask-box-image: linear-gradient(
0deg,
transparent,
transparent 5%,
#fff 20%,
#fff 80%,
transparent 95%,
transparent
);
font-size: 14px;
}
.addressSelect ul {
width: 33.333333%;
position: absolute;
left: 0;
top: 60px;
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
text-align: center;
padding-left: 0;
color: #999;
}
.addressSelect ul li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: rgba(0, 0, 0, 0.54);
padding: 0 0 !important;
height: 30px;
line-height: 30px;
}
.addressSelect ul:nth-of-type(2) {
left: 33.333333%;
}
.addressSelect ul:nth-of-type(3) {
left: 66.666666%;
}
.addressSelect ul li.addSelectActive {
color: #999;
transform: scale(1.1);
transition: 0.5s;
}
.selectAni {
transition: 0.8s;
}
.slideInUp {
-webkit-animation: slideInUp 0.3s;
animation: slideInUp 0.3s;
}
@-webkit-keyframes slideInUp {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes slideInUp {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
style>
import SelectAddressComponent from './SelectAddress.vue';
const SelectAddress = {};
// 注册SelectAddress
SelectAddress.install = function(Vue) {
const SelectAddressConstructor = Vue.extend(SelectAddressComponent);
const instance = new SelectAddressConstructor();
instance.$mount(document.createElement('div'));
document.body.appendChild(instance.$el);
Vue.prototype.$selectAddress = (config, showModal) => {
//创建一个以$selectAddress为名的vue方法,config为调用时传进来的配置,showModal控制显示隐藏
return new Promise((resolve, reject) => {
if(showModal!=='false'){
//每次调用都用config重置一下省市区列表、下标等的参数
instance.provinceArrayAll = config.provinceArray
? JSON.parse(JSON.stringify(config.provinceArray))
: [];
instance.cityArrayAll = config.cityArray
? JSON.parse(JSON.stringify(config.cityArray))
: [];
instance.areaArrayAll = config.areaArray
? JSON.parse(JSON.stringify(config.areaArray))
: [];
let provinceCode =
config.provinceCode ||
(instance.provinceArrayAll.length > 0
? instance.provinceArrayAll[0].code
: '');
let areaCode = config.areaCode || 0;
instance.provinceArray = instance.provinceArrayAll;
let list = [];
instance.cityArrayAll.forEach(item => {
if (item.parentCode == provinceCode) {
list.push(item);
}
});
instance.cityArray = list;
let list2 = [];
let cityCode =
config.cityCode ||
(instance.cityArray.length > 0 ? instance.cityArray[0].code : '');
instance.areaArrayAll.forEach(item => {
if (item.parentCode == cityCode) {
list2.push(item);
}
});
instance.areaArray = list2;
//重置状态
// 获取默认省市区下标
instance.provinceArray.forEach((item, index) => {
if (item.code == provinceCode) {
instance.provinceIndex = index;
}
});
instance.cityArray.forEach((item, index) => {
if (item.code == cityCode) {
instance.cityIndex = index;
}
});
instance.areaArray.forEach((item, index) => {
if (item.code == areaCode) {
instance.districtIndex = index;
}
});
instance.provinceStyle.WebkitTransform =
'translate3d(0px,-' + 30 * instance.provinceIndex + 'px,0px)';
instance.cityStyle.WebkitTransform =
'translate3d(0px,-' + 30 * instance.cityIndex + 'px,0px)';
instance.districtStyle.WebkitTransform =
'translate3d(0px,-' + 30 * instance.districtIndex + 'px,0px)';
instance.val.areaVal = list2[instance.districtIndex];
instance.val.cityVal = list[instance.cityIndex];
instance.val.provinceVal = instance.provinceArray[instance.provinceIndex];
//重置状态
instance.showModal = true;
instance.complete = () => {
//点击完成时,配置返回的参数
instance.showModal = false;
var res = instance.val;
resolve(res);
};
instance.cancel = () => {
//点击取消时,配置返回的参数
instance.showModal = false;
reject(false);
};
}else{
instance.showModal=false
}
});
};
};
export default SelectAddress;
import selectAddress from './components/SelectAddress/SelectAddress.js'; //选择地址组件
Vue.use(selectAddress);
省市区必须要有code字段,市区必须要有parentCode字段;也可以改字段名,不过插件里的字段名也要对应更改
<div class="df_input_title" @click="selectAddress()">选择地址div>
<script>
data () {
return {
provinceList: [{
"code": "11",
"name": "北京市",
},
{
"code": "12",
"name": "天津市",
}...],
cityList: [{
"code": "1101",
"parentCode": "11",
"name": "北京市",
}, {
"code": "1201",
"parentCode": "12",
"name": "天津市",
}...],
areaList: [{
"code": "110101",
"parentCode": "1101",
"name": "东城区",
}, {
"code": "110102",
"parentCode": "1101",
"name": "西城区",
}...],
addressPriv:'',
addressCity:'',
addressArea:'',
}
}
methods: {
selectAddress() {
let that = this;
that
.$selectAddress({
provinceArray: that.provinceList,//省列表
cityArray: that.cityList,//市列表
areaArray: that.areaList,//区列表
provinceCode: that.addressPriv,//默认省code
cityCode: that.addressCity,//默认市code
areaCode: that.addressArea//默认区code
})
.then(res => {
//点击确定回调
this.addressValue =
res.provinceVal.name +
' ' +
res.cityVal.name +
' ' +
res.areaVal.name;
//广东省 汕尾市 海丰县
that.addressPriv = res.provinceVal.code;
that.addressCity = res.cityVal.code;
that.addressArea = res.areaVal.code;
})
.catch(() => {
//点击取消回调
});
},
}
script>
因为vue插件不存在于App.vue中,所以会出现插件框显示时切换vue页面导致插件框没消失的情况;我的操作是直接在router.beforeEach中直接清除插件,在main.js中写一下router.beforeEach的方法就行
/**初始化Vue */
let vm = new Vue();
router.beforeEach((to, from, next) => {
vm.$selectAddress({}, 'false');
next();
}