移动端微信小程序,省市区选择器

129 阅读1分钟

最近项目中有使用到省市区选择器,因为是二开的。原项目中是直接写在页面中的...emm,我现在将其独立设计成一个组件,并优化了一些动画效果。

<template>
  <view class="selector-box" @tap="close" v-show="showPopup">
    <view class="main" @tap.stop :class="{ 'show-picker': showPicker, 'close-picker': !showPicker }">
      <view class="operator">
        <text class="cancel" @tap="close">取消</text>
        <text class="confirm" @tap="confirm">确定</text>
      </view>
      <picker-view indicator-style="height: 50rpx;" :value="geoIndex" @change="changePicker" @tap.stop>
        <!--省-->
        <picker-view-column>
          <view v-for="(item, index) in provArray" :key="index">{{item.areaName}}</view>
        </picker-view-column>
        <!--地级市-->
        <picker-view-column>
          <view v-for="(item, index) in cityArray" :key="index">{{item.areaName}}</view>
        </picker-view-column>
        <!--区县-->
        <picker-view-column>
          <view v-for="(item, index) in areaArray" :key="index">{{item.areaName}}</view>
        </picker-view-column>
      </picker-view>
    </view>
	</view>
</template>
<script>
const http = require('../../utils/http.js');
export default {
  data() {
    return {
      showPopup: false,
      showPicker: false,
      provArray: [],
      cityArray: [],
      areaArray: [],
      geoIndex: [0, 0, 0]
    }
  },
  created() {
    this.loadProvinces();
  },
  methods: {
    open() {
      this.showPopup = true;
      setTimeout(() => {
        this.showPicker = true;
      })
    },
    close() {
      this.showPicker = false;
      setTimeout(() => {
        this.showPopup = false;
      }, 300)
    },
    changePicker(e) {
      const [pIdx, cIdx, aIdx] = e.detail.value;
      if (pIdx !== this.geoIndex[0]) {
        this.loadCities(this.provArray[pIdx].areaId);
        this.geoIndex[0] = pIdx;
        this.geoIndex[1] = 0;
        this.geoIndex[2] = 0;
      } else if (cIdx !== this.geoIndex[1]) {
        this.loadAreas(this.cityArray[cIdx].areaId);
        this.geoIndex[1] = cIdx;
        this.geoIndex[2] = 0;
      } else if (aIdx !== this.geoIndex[2]) {
        this.geoIndex[2] = aIdx;
      }
    },
    confirm() {
      const addressInfo = {
        province: this.provArray[this.geoIndex[0]].areaName,
        provinceId: this.provArray[this.geoIndex[0]].areaId,
        city: this.cityArray[this.geoIndex[1]].areaName,
        cityId: this.cityArray[this.geoIndex[1]].areaId,
        area: this.areaArray[this.geoIndex[2]].areaName,
        areaId: this.areaArray[this.geoIndex[2]].areaId,
      }
      this.$emit('confirm', addressInfo);
    },
    loadProvinces(provinceId, cityId, areaId) {
      this.loadGeoList({ pid: 0 }).then(res => {
        this.setData({ provArray: res });

        const provId = provinceId === undefined ? res[0].areaId : provinceId;
        this.loadCities(provId, cityId, areaId);
      })
    },
    loadCities(provinceId, cityId, areaId) {
      this.loadGeoList({ pid: provinceId }).then(res => {
        this.setData({ cityArray: res });

        const ciId = cityId === undefined ? res[0].areaId : cityId;
        this.loadAreas(ciId, areaId);
      })
    },
    loadAreas(cityId, areaId) {
      this.loadGeoList({ pid: cityId }).then(res => {
        this.setData({ areaArray: res });
      })
    },
    loadGeoList(params) {
      return new Promise(resolve => {
        http.request({
          url: "/p/area/listByPid",
          method: "GET",
          data: params,
          callBack: res => {
            resolve(res);
          },
          errCallBack: err => {
            console.log(err);
            resolve([]);
          }
        })
      })
    }
  }
}
</script>
<style>
.selector-box {
  position: fixed;
  top: 0;
  width: 100%;
  height: 100vh;
  background-color: rgba(0, 0, 0, .3);
}

.main {
  width: 100%;
  height: 480rpx;
  background-color: #fff;
  border-top-left-radius: 22rpx;
  border-top-right-radius: 22rpx;
  position: absolute;
  transition: all .3s;
  bottom: 0;
}

.close-picker {
  bottom: -100%;
}

.show-picker {
  bottom: 0;
}

.operator {
  width: 100%;
  padding: 0 28rpx;
  box-sizing: border-box;
  position: absolute;
  top: 26rpx;
  display: flex;
  justify-content: space-between;
}

.operator .confirm {
  color: #5b91e0;
}

.operator .cancel {
  color: #847e7e;
}

picker-view {
  width: 100%;
  height: 400rpx;
  position: absolute;
  bottom: 0;
}

picker-view-column {
  background-color: white;
  width: 100vw;
  height: 400rpx;
}

picker-view-column view {
  vertical-align: middle;
  font-size: 28rpx;
  line-height: 28rpx;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>