Vue编写多地区选择组件


Posted in Javascript onAugust 21, 2017

看看效果图:

效果图

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>

总结

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

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

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

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

Javascript 相关文章推荐
在textarea中显示html页面的javascript代码
Apr 20 Javascript
javascript下给元素添加事件的方法与代码
Aug 13 Javascript
复制本贴标题和地址的js代码
Jul 01 Javascript
JQuery的AJAX实现文件下载的小例子
May 15 Javascript
jquery中change()用法实例分析
Feb 06 Javascript
JavaScript中的pow()方法使用详解
Jun 15 Javascript
Bootstrap Table使用整理(五)之分页组合查询
Jun 09 Javascript
JsChart组件使用详解
Mar 04 Javascript
微信小程序分享功能之按钮button 边框隐藏和点击隐藏
Jun 14 Javascript
微信公众号平台接口开发 获取微信服务器IP地址方法解析
Aug 14 Javascript
微信小程序获取用户信息及手机号(后端TP5.0)
Sep 12 Javascript
在vue项目中promise解决回调地狱和并发请求的问题
Nov 09 Javascript
使用vue制作FullPage页面滚动效果
Aug 21 #Javascript
详解Layer弹出层样式
Aug 21 #Javascript
JS数组操作之增删改查的简单实现
Aug 21 #Javascript
JS实现评价的星星功能
Aug 20 #Javascript
详解A标签中href=&quot;&quot;的几种用法
Aug 20 #Javascript
Cropper.js 实现裁剪图片并上传(PC端)
Aug 20 #Javascript
Bootstrap 模态框(Modal)带参数传值实例
Aug 20 #Javascript
You might like
PHP 如何利用phpexcel导入数据库
2013/08/24 PHP
php、java、android、ios通用的3des方法(推荐)
2016/09/09 PHP
PHP中递归的实现实例详解
2017/11/14 PHP
AJAX的跨域与JSONP(为文章自动添加短址的功能)
2010/01/17 Javascript
JQuery1.4+ Ajax IE8 内存泄漏问题
2010/10/15 Javascript
NodeJs中的非阻塞方法介绍
2012/06/05 NodeJs
详解js界面跳转与值传递
2016/11/22 Javascript
基于jQuery和CSS3实现APPLE TV海报视差效果
2017/06/16 jQuery
JS 组件系列之BootstrapTable的treegrid功能
2017/06/16 Javascript
Vue拖拽组件开发实例详解
2018/05/11 Javascript
jQuery常见的遍历DOM操作详解
2018/09/05 jQuery
react项目实践之webpack-dev-serve
2018/09/14 Javascript
Node.js + express实现上传大文件的方法分析【图片、文本文件】
2019/03/14 Javascript
vue element中axios下载文件(后端Python)
2019/05/10 Javascript
基于form-data请求格式详解
2019/10/29 Javascript
精读《Vue3.0 Function API》
2020/05/20 Javascript
微信小程序自定义底部弹出框动画
2020/11/18 Javascript
[45:15]Optic vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.24
2018/08/25 DOTA
[03:48]大碗DOTA
2019/07/25 DOTA
python 生成目录树及显示文件大小的代码
2009/07/23 Python
Python解决鸡兔同笼问题的方法
2014/12/20 Python
Python 编程速成(推荐)
2019/04/15 Python
基于Python批量生成指定尺寸缩略图代码实例
2019/11/20 Python
Pytoch之torchvision.transforms图像变换实例
2019/12/30 Python
Python Numpy库常见用法入门教程
2020/01/16 Python
python orm 框架中sqlalchemy用法实例详解
2020/02/02 Python
python标准库OS模块详解
2020/03/10 Python
Pytorch环境搭建与基本语法
2020/06/03 Python
Python unittest discover批量执行代码实例
2020/09/08 Python
Python实现粒子群算法的示例
2021/02/14 Python
SmartBuyGlasses台湾:名牌眼镜,名牌太阳眼镜及隐形眼镜
2017/01/04 全球购物
番木瓜健康和保健产品第一大制造商:Herbal Papaya
2017/04/25 全球购物
中介公司区域经理岗位职责范本
2014/03/02 职场文书
事业单位鉴定材料
2014/05/25 职场文书
表扬信范文
2015/05/04 职场文书
python生成随机数、随机字符、随机字符串
2021/04/06 Python