Vue移动端省市区三级联动插件

前言

参考Ajaxguan的文章:https://blog.csdn.net/Ajaxguan/article/details/82906705
因为公司项目需要一个省市区三级联动的选择框,而且因为不想项目体积太大,不给引入大型的组件库;在网上找了一些例子样式都不太合适,直到看到上面的文章,因为公司项目用到省市区的地方比较多,理解了一下原理就改成插件形式并且添加了一些注释,有需要的可以看一下(如果有什么错误或者更简洁的写法欢迎指出来)。

目录

Vue移动端省市区三级联动插件_第1张图片
插件放在vue项目的components目录下

SelectAddress.vue

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

SelectAddress.js

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;

main.js里引入

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();
}

效果图

Vue移动端省市区三级联动插件_第2张图片

你可能感兴趣的:(vue.js)