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 相关文章推荐
JavaScript入门教程(7) History历史对象
Jan 31 Javascript
初识javascript 文档碎片
Jul 13 Javascript
jQuery中parents()和parent()的区别分析
Oct 28 Javascript
基于bootstrap3和jquery的分页插件
Jul 31 Javascript
php简单数据库操作类的封装
Jun 08 Javascript
基于js原生和ajax的get和post方法以及jsonp的原生写法实例
Oct 16 Javascript
JS基于for语句编写的九九乘法表示例
Jan 04 Javascript
element-ui 的el-button组件中添加自定义颜色和图标的实现方法
Oct 26 Javascript
javaScript实现游戏倒计时功能
Nov 17 Javascript
vue-cli3+ts+webpack实现多入口多出口功能
May 30 Javascript
详解js创建对象的几种方式和对象方法
Mar 01 Javascript
Javascript设计模式之原型模式详细
Oct 05 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模拟SQL Server的两个日期处理函数
2006/10/09 PHP
PHP乱码问题,UTF-8乱码常见问题小结
2012/04/09 PHP
PHP实现的登录页面信息提示功能示例
2017/07/24 PHP
基于jquery的页面划词搜索JS
2010/09/14 Javascript
jQuery获取Radio,CheckBox选择的Value值(示例代码)
2013/12/12 Javascript
js重写alert控件(适合学习js的新手朋友)
2014/08/24 Javascript
推荐10个2014年最佳的jQuery视频插件
2014/11/12 Javascript
jQuery遍历json中多个map的方法
2015/02/12 Javascript
jquery渐隐渐显的图片幻灯闪烁切换实现方法
2015/02/26 Javascript
Bootstrap源码解读导航条(7)
2016/12/23 Javascript
JavaScript正则表达式exec/g实现多次循环用法示例
2017/01/17 Javascript
nodejs 终端打印进度条实例代码
2017/04/22 NodeJs
详解angularjs利用ui-route异步加载组件
2017/05/21 Javascript
浅谈vue项目重构技术要点和总结
2018/01/23 Javascript
node.js监听文件变化的实现方法
2019/04/17 Javascript
详解使用WebPack搭建React开发环境
2019/08/06 Javascript
微信小程序 自定义弹窗实现过程(附代码)
2019/12/05 Javascript
JS window对象简单操作完整示例
2020/01/14 Javascript
Windows下为Python安装Matplotlib模块
2015/11/06 Python
Python使用pymysql小技巧
2017/06/04 Python
pycham查看程序执行的时间方法
2018/11/29 Python
pyqt5 实现 下拉菜单 + 打开文件的示例代码
2019/06/20 Python
python基于递归解决背包问题详解
2019/07/03 Python
如何基于windows实现python定时爬虫
2020/05/01 Python
python调用百度API实现人脸识别
2020/11/17 Python
Python+Xlwings 删除Excel的行和列
2020/12/19 Python
CSS3 优势以及网页设计师如何使用CSS3技术
2009/07/29 HTML / CSS
香港百佳网上超级市场:PARKNSHOP.com
2020/06/10 全球购物
屈臣氏菲律宾官网:Watsons菲律宾
2020/06/30 全球购物
工作检讨书500字
2014/10/19 职场文书
红歌会主持词
2015/07/02 职场文书
防震减灾主题班会
2015/08/14 职场文书
少先队大队委竞选口号
2015/12/25 职场文书
JavaWeb Servlet实现网页登录功能
2021/07/04 Java/Android
详解pytorch创建tensor函数
2022/03/22 Python
Nginx本地配置SSL访问的实例教程
2022/05/30 Servers