当前位置: 移动技术网 > IT编程>脚本编程>vue.js > Vue编写多地区选择组件

Vue编写多地区选择组件

2017年12月12日  | 移动技术网IT编程  | 我要评论

看看效果图:

效果图

这里写图片描述

功能点:

  • 支持不限城市,不限地区(这个东西的实现..真心死磕了挺久) – 左右两边数据的同步
  • 地区一次性多选,若是选择了所有地区会自动转为不限地区
  • 数据迁移箭头的判断..选中数据才会有对应的按钮可以点击
  • 已选位置的数据同步响应调用的地方,当然也可以外部传入…(新增传出,编辑依赖传入再组合)
  • 箭头是iconfont,支持外部传入,默认是我这边的cdn的啦….

!!!这是一个独立的组件,css预处理是用的scss;

写的过程遇到的问题:

因为这个功能会多次需要切换省份城市这些,所以我一次性拉取所有数据存储到localstorage,不然请求接口次数太多了

一开始的不限城市和不限地区我想并入json(重组json),但是发现虽然能降低处理麻烦,但是数据更乱了…非常不好维护,也有其他组件调用这个json的地方(比如其他组件有个地址的三级联动也是依赖这个json)

         *还有一个就是要考虑两边有不限制这东东的时候,要剔除不能存在的数据,比如你不限制城市,那所有该省份的城市都要干掉,不限制地区也是类似

写左右两边数据的对比是最恶心的,为什么这么说呢?

      *左边三级联动的,每个子项都有自己的id和name, 而选择的是组合成的(看gif图),中间是中划线隔开,这对于推入和推出就带来一堆遍历和比较

      *我们这边的后端大佬说不限制的id均为0(城市或者地区),所以这个需要自行组合,最后就是动态图那格式的id就是后台接受的,,多地区再拼接成字符串….'3-13-2,2-44-3,4-0-0'这种提交到后台..

联动json数据格式

regionname: 项的名称
regionid: 项的id
child: 是否包含有子项

本来想写个props映射下regionname,regionid,child; 但是感觉作用不大,就没写了,(一般公司的地区json格式定下来了之后变动的可能性太低)

你能学到什么?

1: 数组的比对,数组的遍历,数组的组合及响应判断
2: vue一些内置指令的使用
3: 组件功能细节的考虑,不限制地区,全部这些按钮在什么情况下能点击
4: 清空数据之后各个状态的恢复和重置等等

代码

manyareaselect.vue

<template>
 <div class="manyareaselect">
 <div class="item">
  <div class="item-title">
  <span> 选择省</span>
  </div>
  <div class="item-content">
  <ul>
   <li v-for="(item,index) in chinaarea" :class="item.selected?'active':''" :key="index" @click="getcitylist(item)">{{item.regionname}}</li>
  </ul>
  </div>
  <div class="item-footer"></div>
 </div>
 <div class="item">
  <div class="item-title">
  <span>选择市</span>
  </div>
  <div class="item-content">
  <ul v-show="citylist.length===0">
   <li>
   <<请选择省份</li>
  </ul>
  <ul v-show="!notlimitbutton.notlimitcity &&citylist.length!==0">
   <li v-for="(item,index) in citylist" :class="item.selected ? 'active':''" :key="index" @click="getdistriclist(item)">{{item.regionname}}</li>
  </ul>
  </div>
  <div class="item-footer">
  <button class="button" :class="notlimitbutton.notlimitcity?'success':''" @click="citynotlitmit({regionname:'不限',regionid:'0'})" size="mini" :disabled="!selectitem.province.regionname">不限城市</button>
  </div>
 </div>
 <div class="item">
  <div class="item-title">
  <span>选择地区</span>
  </div>
  <div class="item-content">
  <ul v-show="districlist.length===0">
   <li>
   <<请选择城市</li>
  </ul>
  <ul v-show="!notlimitbutton.notlimitcity && !notlimitbutton.notlimitdistrict && districlist.length!==0">
   <li v-for="(item,index) in districlist" :class="item.selected?'active':''" :key="index" @click="getareacombineid(item)">{{item.regionname}}</li>
  </ul>
  </div>
  <div class="item-footer">
  <button class="button" :class="notlimitbutton.notlimitdistrict ?'success':''" @click="districnotlitmit({regionname:'不限',regionid:'0'})" :disabled="!selectitem.city.regionname ||!selectitem.province.regionname || notlimitbutton.notlimitcity ">不限地区</button>
  </div>
 </div>
 <div class="trangle">
  <div class="trangle-wrap">
  <div class="left">
   <button class="button" @click="transfertoright" :disabled="direactionstatustoright">
   <i :class="this.icondirection.right"></i>
   </button>
  </div>
  <div class="right">
   <button class="button" @click="transfertoleft" :disabled="direactionstatustoleft">
   <i :class="this.icondirection.left"></i>
   </button>
  </div>
  </div>
 </div>
 <div class=" item ">
  <div class="item-title ">
  <span>已选位置</span>
  </div>
  <div class="item-content ">
  <ul class="selectedcontent">
   <li v-for="(item,index) in selectedlist" :class="item.selected?'active':''" :key="index" @click="selectedareasingle(item)">{{item.regionname}}</li>
  </ul>
  </div>
  <div class="item-footer">
  <button class="button" @click="selectedallarea()" :disabled="rightdatalist.length=== 0" :class="selectedallbuttonstatus?'success':''">{{selectedallbuttonstatus?'反选':'全部'}}</button>
  </div>
 </div>
 </div>
</template>

<script>
 import _ from 'lodash';
 export default {
 name: 'manyareaselect',
 data: function () {
  return {
  chinaarea: json.parse(window.localstorage.getitem('chinaarea')) || [], // 这是地区联动的json
  notlimitbutton: {
   notlimitcity: false, // 城市不限
   notlimitdistrict: false, // 地区不限
  },
  selectedallbuttonstatus: false, // 已选位置列表全部按钮的状态
  selectitem: {
   province: {},
   city: {},
   distric: {}
  },
  citylist: [], // 城市列表
  districlist: [], // 区域列表
  rightdatalist: [], // 选中项目组合成的渲染列表
  rightdata: [], // 选中需要移除的
  leftdata: [], // 左边选中的转发
  }
 },
 props: {
  selecteddata: {
  type: [string, object, array]
  },
  icondirection: {
  type: object,
  default: function () { // 箭头图标
   return {
   left: 'fzicon fz-ad-you',
   right: 'fzicon fz-ad-right'
   }
  }
  }
 },
 computed: {
  selectedlist () { // 已选中列表
  if (this.selecteddata && this.selecteddata !== '') {
   this.rightdatalist = this.selecteddata;
   return this.rightdatalist;
  } else {
   return this.rightdatalist;
  }

  },
  direactionstatustoright () { // 控制可以转移的箭头状态
  if (this.notlimitbutton.notlimitcity || this.notlimitbutton.notlimitdistrict) {
   if (this.notlimitbutton.notlimitcity) {
   this.removeallselected(this.citylist);
   this.removeallselected(this.districlist);
   return false;
   } else {
   if (this.notlimitbutton.notlimitdistrict) {
    this.removeallselected(this.districlist);
    return false;
   }
   }
   return false;
  } else {
   if (this.selectitem.distric.regionname) {
   return false;
   }
   return true;
  }
  },
  direactionstatustoleft () { // 控制可以转移的箭头状态
  if (this.rightdata.length === 0) {
   return true
  } else {
   return false
  }
  }
 },
 methods: {
  mapselect (list, value, type) { // 高亮选中
  if (type) {
   return list.map(pitem => {
   if (pitem.regionid === value.regionid) {
    if (value.selected && value.selected === true) {
    this.$delete(pitem, 'selected');
    } else {
    this.$set(pitem, 'selected', true)
    }
   }
   })
  } else {
   return list.map(pitem => {
   if (pitem.regionid === value.regionid) {
    if (value.selected && value.selected === true) {
    this.$delete(pitem, 'selected');
    } else {
    this.$set(pitem, 'selected', true)
    }
   } else {
    this.$delete(pitem, 'selected');
   }
   })
  }
  },
  resettodefault () {
  this.leftdata = []; // 清空需要转移的数组
  this.notlimitbutton = { // 重置按钮状态
   notlimitcity: false, // 城市不限
   notlimitdistrict: false, // 地区不限
  };
  this.selectitem.city = {};
  this.selectitem.distric = {}
  this.removeallselected(this.citylist); // 清除选中状态
  this.removeallselected(this.districlist); // 清除选中状态
  this.citylist = [];
  this.districlist = [];
  },
  getcitylist (item) {
  this.resettodefault();
  if (item) {
   this.citylist = item.child; // 获取城市列表
   this.selectitem.province = item; // 保存省份对象
   this.mapselect(this.chinaarea, item); // 高亮选择,单选
  }
  },
  getdistriclist (item) {
  this.leftdata = []; // 清空需要转移的数组
  this.notlimitbutton.notlimitdistrict = false; // 重置按钮状态
  this.removeallselected(this.districlist); // 清除选中状态
  this.selectitem.distric = {};
  this.districlist = [];
  if (item) {
   this.districlist = item.child; // 获取区域列表
   this.selectitem.city = item; // 保存省份对象
   this.mapselect(this.citylist, item); // 高亮选择,单选
  }

  },
  getareacombineid (item) { // 获取组合id
  if (item) {
   this.selectitem.distric = item;
   this.mapselect(this.districlist, item, 'manyselect'); // 区域高亮选择,多选

   this.leftdata.push({
   regionname: this.selectitem.province.regionname + '-' + this.selectitem.city.regionname + '-' + item.regionname,
   regionid: this.selectitem.province.regionid + '-' + this.selectitem.city.regionid + '-' + item.regionid
   })
   this.leftdata = _.uniqby(this.leftdata, 'regionid');
   if (this.leftdata.length === this.districlist.length) {
   this.leftdata = [];
   this.notlimitbutton.notlimitdistrict = true; // 转为不限制地区
   this.leftdata.push({
    regionname: this.selectitem.province.regionname + '-' + this.selectitem.city.regionname + '-不限',
    regionid: this.selectitem.province.regionid + '-' + this.selectitem.city.regionid + '-0'
   })
   }
  }

  },
  citynotlitmit (item) { // 城市不限
  this.leftdata = []; // 请空数组
  this.notlimitbutton.notlimitcity = !this.notlimitbutton.notlimitcity; // 不限按钮状态
  this.leftdata.push({
   regionname: this.selectitem.province.regionname + '-不限-不限',
   regionid: this.selectitem.province.regionid + '-0-0'
  })
  },
  districnotlitmit (item) { // 区域不限
  this.leftdata = []; // 请空数组
  this.notlimitbutton.notlimitdistrict = !this.notlimitbutton.notlimitdistrict; // 不限按钮状态
  this.leftdata.push({
   regionname: this.selectitem.province.regionname + '-' + this.selectitem.city.regionname + '-不限',
   regionid: this.selectitem.province.regionid + '-' + this.selectitem.city.regionid + '-0'
  })
  },
  transfertoright () { // 选中推入到已选中列表区域
  if (this.leftdata && this.leftdata.length !== 0) {
   if (this.leftdata.length === 1) { // 长度只有1,那就只有不限城市或者地区了
   let limitid = this.leftdata[0].regionid.split('-'); // 比对比对,切割成数组
   this.rightdatalist.map(item => {
    let id = item.regionid.split('-');
    if (limitid[0] === id[0]) {
    if (limitid[1] === '0') { // 不限城市
     this.rightdatalist = this.rightdatalist.filter(ritem => {
     let rid = ritem.regionid.split('-');
     if (limitid[0] !== rid[0]) {
      return ritem;
     }
     })
    } else {
     if (limitid[2] === '0') { // 不限地区
     this.rightdatalist = this.rightdatalist.filter(ritem => {
      let rid = ritem.regionid.split('-');
      if ((limitid[0] === rid[0] && limitid[1] === rid[1])) {
      if (ritem[2] === '0') {
       return ritem;
      }
      } else {
      if (limitid[0] !== rid[0] || limitid[1] !== rid[1]) {
       return ritem;
      }
      }
     })
     } else {
     this.rightdatalist = this.rightdatalist.filter(ritem => {
      let rid = ritem.regionid.split('-');
      if (limitid[0] === rid[0]) {
      if (limitid[1] === rid[1]) {
       if (!(rid[2] === '0')) {
       return ritem;
       }
      } else {
       if (!(rid[1] === '0')) {
       return ritem
       }
      }
      } else {
      return ritem
      }
     })
     }
    }

    }
   })
   } else {
   let limitid = this.leftdata[0].regionid.split('-'); // 比对比对,切割成数组
   this.rightdatalist = this.rightdatalist.filter(ritem => {
    let rid = ritem.regionid.split('-');
    if (limitid[0] === rid[0]) {
    if (limitid[1] === rid[1]) {
     if (!(rid[2] === '0')) {
     return ritem;
     }
    } else {
     if (!(rid[1] === '0')) {
     return ritem
     }
    }
    } else {
    return ritem
    }
   })
   }
   this.leftdata.map(item => {
   this.rightdatalist.push(item);
   })
   this.rightdatalist = _.uniqby(this.rightdatalist, 'regionid');
   this.resettodefault();
  }


  },
  selectedareasingle (item) { // 已选择区域单个选择
  if (item) {
   this.rightdata = [];
   this.mapselect(this.rightdatalist, item, 'manyselect'); // 区域高亮选择,多选
   this.rightdatalist.map(item => {
   if (item.selected) {
    this.rightdata.push(item)
   }
   })
  }

  },
  selectedallarea () { // 已选中区域全选反选
  if (this.selectedallbuttonstatus) {
   this.removeallselected(this.rightdatalist);
   this.rightdata = [];
  } else {
   this.rightdatalist.map(item => this.$set(item, 'selected', true));
   this.rightdata = this.rightdatalist;
  }
  this.selectedallbuttonstatus = !this.selectedallbuttonstatus;
  },
  transfertoleft () { // 从已选中列表区域退回待转发区域
  if (this.rightdata && this.rightdata.length !== 0) {
   this.rightdatalist = this.rightdatalist.filter(item => {
   if (!item.selected) {
    return item;
   }
   })
   this.rightdata = [];
  }
  },
  removeallselected (list) { // 清空选中状态
  list.map(item => this.$delete(item, 'selected'));
  }
 },
 watch: {
  'rightdatalist' (newvalue, oldvalue) { // 选择列表的值变动响应外部值的变动
  if (newvalue.length !== this.rightdata.length) {
   this.selectedallbuttonstatus = false;
  } else {
   if (newvalue.length === 0) {
   this.selectedallbuttonstatus = false;
   } else {
   this.selectedallbuttonstatus = true;
   }
  }
  this.$emit('update:selecteddata', newvalue);
  }
 }
 }
</script>

<style scoped lang="scss">
 ul {
 padding: 0;
 margin: 0;
 max-height: 100%;
 overflow-y: auto;
 li {
  cursor: pointer;
  text-align: center;
  padding: 5px;
  &.active,
  &:hover {
  background: #e4e8f1;
  color: #48576a;
  }
 }
 }

 .manyareaselect {
 position: relative;
 z-index: 2005;
 .item {
  border: 1px solid #d1dbe5;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  display: inline-block;
  vertical-align: middle;
  min-width: 180px;
  box-sizing: border-box;
  position: relative;
  height: 100%;
  min-height: 260px;
 }
 .item-title {
  height: 36px;
  line-height: 36px;
  background: #fbfdff;
  margin: 0;
  border-bottom: 1px solid #d1dbe5;
  box-sizing: border-box;
  color: #1f2d3d;
  text-align: center;
 }
 .trangle {
  background: transparent;
  display: inline-block;
  vertical-align: middle;
  width: 40px;
  box-sizing: border-box;
  height: 100%;
  position: relative;
  .trangle-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  }
  .left,
  .right {
  margin: 10px 5px;
  }
  ;
 }

 .item-content {
  font-size: 13px;
  height: 190px;
  padding: 8px 2px;
 }
 .item-footer {
  padding: 5px 0;
  height: 40px;
  text-align: center;
 }
 }

 .selectedcontent {
 li {
  text-align: left;
  padding-left: 25px;
 }
 }



 .button {
 display: inline-block;
 line-height: 1;
 white-space: nowrap;
 cursor: pointer;
 background: #fff;
 border: 1px solid #c4c4c4;
 color: #1f2d3d;
 margin: 0;
 border-radius: 4px;
 padding: 4px;
 font-size: 12px;
 border-radius: 4px;
 -webkit-appearance: button;
 outline: none;
 &.success {
  background: #42d885;
  border-color: #42d885;
  color: #fff;
 }

 &:disabled {
  color: #bfcbd9;
  cursor: not-allowed;
  background-image: none;
  background-color: #eef1f6;
  border-color: #d1dbe5;
 }
 }
</style>

用法

<!--selecteddata就是响应的数据.sync是2.3回归的语法糖-->
<!--可以绑定icondirection传入箭头的iconfont,object-->

<many-area-select :selecteddata.sync="manyareavalue"></many-area-select>

总结

这个组件的出炉,折腾了很久..

写的过程推倒了三版(三天三个版本),都是思路没想对和理清..写着写着就写不下去了…

这个组件目前的功能是满足我这边的需求的,若是有更好的实现方式可以留言。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网