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的时候写的学习笔记
Dec 30 Javascript
jquery post方式传递多个参数值后台以数组的方式进行接收
Jan 11 Javascript
JS自调用匿名函数具体实现
Feb 11 Javascript
什么是Node.js?Node.js详细介绍
Jun 01 Javascript
js 获取元素在页面上的偏移量的方法汇总
Apr 13 Javascript
JQuery实现鼠标滚轮滑动到页面节点
Jul 28 Javascript
Extjs实现下拉菜单效果
Apr 01 Javascript
AngularJS全局scope与Isolate scope通信用法示例
Nov 22 Javascript
改变vue请求过来的数据中的某一项值的方法(详解)
Mar 08 Javascript
JS轮播图的实现方法
Aug 24 Javascript
手把手教你实现 Promise的使用方法
Sep 02 Javascript
VUE中鼠标滚轮使div左右滚动的方法详解
Dec 14 Vue.js
使用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
利用文件属性结合Session实现在线人数统计
2006/10/09 PHP
PHP缓冲区用法总结
2016/02/14 PHP
php简单实现短网址(短链)还原的方法(测试可用)
2016/05/09 PHP
Aster vs KG BO3 第一场2.18
2021/03/10 DOTA
Javascript中Eval函数的使用说明
2008/10/11 Javascript
JS 添加网页桌面快捷方式的代码详细整理
2012/12/27 Javascript
jquerymobile局部渲染的各种刷新方法小结
2014/03/05 Javascript
JQuery中使用on方法绑定hover事件实例
2014/12/09 Javascript
《JavaScript DOM 编程艺术》读书笔记之JavaScript 简史
2015/01/09 Javascript
jquery实现聚光灯效果的方法
2015/02/06 Javascript
javascript每日必学之封装
2016/02/23 Javascript
javascript使用闭包模拟对象的私有属性和方法
2016/10/05 Javascript
jQuery Mobile和HTML5开发App推广注册页
2016/11/07 Javascript
jQuery实现简单的手风琴效果
2020/04/17 jQuery
vue几个常用跨域处理方式介绍
2018/02/07 Javascript
layui实现图片虚拟路径上传,预览和删除的例子
2019/09/25 Javascript
小程序实现多个选项卡切换
2020/06/19 Javascript
JS实现电脑虚拟键盘打字测试
2020/06/24 Javascript
[01:10]DOTA2次级职业联赛 - U5战队宣传片
2014/12/01 DOTA
基础的十进制按位运算总结与在Python中的计算示例
2016/06/28 Python
Python实现将MySQL数据库表中的数据导出生成csv格式文件的方法
2018/01/11 Python
Django 大文件下载实现过程解析
2019/08/01 Python
Django中的session用法详解
2020/03/09 Python
linux centos 7.x 安装 python3.x 替换 python2.x的过程解析
2020/12/14 Python
python爬虫基础之urllib的使用
2020/12/31 Python
css3 iphone玻璃透明气泡完美实现
2013/03/20 HTML / CSS
阿迪达斯墨西哥官方网站:adidas墨西哥
2017/11/03 全球购物
以实惠的价格轻松租车,免费取消:Easyrentcars
2019/07/16 全球购物
HOTEL INFO英国:搜索全球酒店
2019/08/08 全球购物
opencv实现图像平移效果
2021/03/24 Python
青年文明号事迹材料
2014/01/18 职场文书
医院护士见习期自我鉴定
2014/04/10 职场文书
团队会宣传标语
2014/10/09 职场文书
大学生团日活动总结
2015/05/06 职场文书
Mongo服务重启异常问题的处理方法
2021/07/01 MongoDB
Python加密技术之RSA加密解密的实现
2022/04/08 Python