欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue编写多地区选择组件

程序员文章站 2022-11-26 12:10:30
看看效果图: 效果图 功能点: 支持不限城市,不限地区(这个东西的实现..真心死磕了挺久) – 左右两边数据的同步 地区一次性多选,若是选择了所有地...

看看效果图:

效果图

Vue编写多地区选择组件

功能点:

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

Vue编写多地区选择组件

本来想写个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>

总结

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

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

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

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