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 相关文章推荐
URL编码转换,escape() encodeURI() encodeURIComponent()
Dec 27 Javascript
用javascript控制iframe滚动的代码
Apr 10 Javascript
html5+javascript制作简易画板附图
Apr 25 Javascript
node.js中使用socket.io制作命名空间
Dec 15 Javascript
JavaScript 性能优化小结
Oct 12 Javascript
js前端日历控件(悬浮、拖拽、自由变形)
Mar 02 Javascript
详解Angular 4.x 动态创建组件
Apr 25 Javascript
Angular2仿照微信UI实现9张图片上传和预览的示例代码
Oct 19 Javascript
浅谈MUI框架中加载外部网页或服务器数据的方法
Jan 31 Javascript
layui监听select变化,以及设置radio选中的方法
Sep 24 Javascript
Vue常用的全选/反选的示例代码
Feb 19 Javascript
vue3+typescript实现图片懒加载插件
Oct 26 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获取时间排除周六、周日的两个方法
2014/06/30 PHP
php按字符无乱码截取中文的方法
2015/03/27 PHP
Zend Framework入门之环境配置及第一个Hello World示例(附demo源码下载)
2016/03/21 PHP
thinkphp5引入公共部分header、footer的方法详解
2018/09/14 PHP
Javascript入门学习资料收集整理篇
2008/07/06 Javascript
判断iframe是否加载完成的完美方法
2010/01/07 Javascript
基于jquery的仿百度的鼠标移入图片抖动效果
2010/09/17 Javascript
一行代码告别document.getElementById
2012/06/01 Javascript
jquery插件制作 手风琴Panel效果实现
2012/08/17 Javascript
node.js中的fs.fchmod方法使用说明
2014/12/16 Javascript
三种AngularJS中获取数据源的方式
2016/02/02 Javascript
输入法的回车与消息发送快捷键回车的冲突解决方法
2016/08/09 Javascript
JavaScript变量类型以及变量作用域详解
2017/08/14 Javascript
微信小程序之GET请求的实例详解
2017/09/29 Javascript
Vue 2.5 Level E 发布了: 新功能特性一览
2017/10/24 Javascript
Vuejs学习笔记之使用指令v-model完成表单的数据双向绑定
2019/04/29 Javascript
微信小程序实现日历签到
2020/09/21 Javascript
解决vue prop传值default属性如何使用,为何不生效的问题
2020/09/21 Javascript
详解JavaScript编程中的window与window.screen对象
2015/10/26 Python
一个基于flask的web应用诞生 用户注册功能开发(5)
2017/04/11 Python
详解Python3操作Mongodb简明易懂教程
2017/05/25 Python
python对DICOM图像的读取方法详解
2017/07/17 Python
python 上下文管理器及自定义原理解析
2019/11/19 Python
Python 打印自己设计的字体的实例讲解
2021/01/04 Python
欧缇丽英国官方网站:Caudalie英国
2016/08/17 全球购物
美国五金商店:Ace Hardware
2018/03/27 全球购物
德国苹果商店:MacTrade
2020/05/18 全球购物
厨师岗位职责
2013/11/12 职场文书
医疗纠纷协议书
2014/04/16 职场文书
人才市场接收函
2015/01/30 职场文书
2015新学期家长寄语
2015/02/26 职场文书
2015年保洁员工作总结
2015/05/04 职场文书
pytorch fine-tune 预训练的模型操作
2021/06/03 Python
Mac电脑OS系统下安装Nginx的详细教程
2022/04/14 Servers
前端canvas中物体边框和控制点的实现示例
2022/08/05 Javascript
ECharts transform数据转换和dataZoom在项目中使用
2022/12/24 Javascript