React<移动端自定义地区选择组件>

2021-07-12 11-52-28.gif

一般大部分组件都会有对应的地区选择组件,类似于 Ant Design Mobile 中的地区选择。当然,如果你们的产品和设计没有过多的要求,完全可以直接用,而且稳定性也很好。
然而我就没这么幸运~ 😢

💎 产品需求

本片的代码组件只适用于此种产品需求

省份 城市 区域 为3个接口

//获取省份
export async function getProvinces() {
  return request('/user/provinces', {
    method: 'GET',
  });
}
//根据省份id获取城市
export function getCitys(provinceId) {
  return request(`/user/province/cities/${provinceId}`, {
    method: 'GET',
  });
}
//根据城市id获取区域
export function getDistricts(cityId) {
  return request(`/user/city/districts/${cityId}`, {
    method: 'GET',
  });
}

💎 插件 Swiper

为了想要自己实现这种滑动拖拽的效果,自己也查阅了很多文章,最后选用了 swiper
你可能会怀疑,这不是做轮播图的吗,这也能搞?!
没错,它确实能搞,swiper 中的 网格分布 就属于这种的,大家也可以先去了解下。

安装
Swiper 在 React 中如何使用

 npm i swiper

你可以跟着官方说的搞,但是我这边并没有引入它所说的 import 'swiper/swiper.scss'; 因为这里根本也就用不到。也可能会发现我的写法和官方提供的有点不一致,有需要的可以在我代码上改造。
⚠️注意:多个 swiper 的创建方法,可以添加不同的 id或者后坠不同的 class

  new Swiper('#swiper1');
  new Swiper('#swiper2');
  new Swiper('#swiper3');
...
 <div className="swiper-container" id="swiper1"></div>
 <div className="swiper-container" id="swiper2"></div>
 <div className="swiper-container" id="swiper3"></div>

🥦 拓展组件

本品文章只会给大家提供基础的地区选择组件,可能代码有点不完美,但是学会了也就能自己随便搞了,后续也希望大家提出宝贵意见和代码优化。
这是我们产品最终的地区选择效果图,大家也能基于这个自行扩展~

2021-07-13 17-30-41.gif


💎 组件使用
import AreaSelection from '@/components/AreaSelection';
...
//show 组件的显示状态
//isShow 子组件控制组件的显示方法
//value 默认回显数据 格式为['上海','上海市','静安区']
//onChange 返回数据 [{name:'上海',id:600003},{name:'上海市',id:6003},{name:'静安区',id:1036}]
        <AreaSelection
          show={显示状态}
          value={回显数据}
          isShow={(val) => {
          //显示方法
          }}
          onChange={(val) => {
          //返回数据
          }}
        />

💎 组件代码
import React, { Component } from 'react';
import { getProvinces, getCitys, getDistricts } from '@/api/user';
import styles from './index.less';
import Swiper from 'swiper';
//根据地区名字筛选下标
function fliterIndex(n, list) {
  let index = 0;
  let id = null;
  let name = null;
  list.map((val, v) => {
    if (n === val.name) {
      index = v;
      id = val.id;
      name = val.name;
    }
  });
  return { index: index, id: id, name: name };
}
//地区选择组件
export default class AreaSelection extends Component {
  state = {
    value: this.props.value,
    activeIndex: [], //回显时候的默认数据
    provinces: [], //省份
    citys: [], //城市
    districts: [], //区县
  };
  initArea = async () => {
    //异步方法  获取省份 - 根据省份查城市 - 根据城市查区域
    //因为选中的数据为 [北京市,北京市,东城区] ,所以回显数据时要根据名称去匹配对应的id,name和index
    const { value } = this.state;
    let arr = [];
    if (value[0]) {
      //获取省份
      await getProvinces().then((res) => {
        if (res.data) {
          arr.push(fliterIndex(value[0], res.data));
        }
      });
      //获取城市
      await getCitys(arr[0].id).then((res) => {
        if (res.data) {
          arr.push(fliterIndex(value[1], res.data));
        }
      });
      //获取区域
      await getDistricts(arr[1].id).then((res) => {
        if (res.data) {
          arr.push(fliterIndex(value[2], res.data));
        }
      });
      this.setState({
        activeIndex: [...arr],
      });
    }
    const self = this;
    //定义通用的swiper配置
    const options = {
      direction: 'vertical',
      speed: 300, //切换速度
      spaceBetween: 0, //间距
      height: 123, // slide 高度,必要,否则会有滑动偏差问题
      slidesPerView: 3, //网格分布3个 https://www.swiper.com.cn/api/grid/24.html
      centeredSlides: true, //居中
      resistance: true, //边缘抵抗
      observer: true, //修改swiper自己或子元素时,自动初始化swiper 不加有滑动失效问题
      observeParents: true, //修改swiper的父元素时,自动初始化swiper 不加有滑动失效问题
    };
    //获取省份
    getProvinces().then((res) => {
      if (res.data) {
        this.setState({
          provinces: res.data,
        });
        new Swiper('#swiper1', provinces);
      }
    });
    const val = self.state.activeIndex;
    //省份配置
    const provinces = {
      ...options,
      initialSlide: val[0] && val[0].index, //默认显示的下标位置
      on: {
        //初始化时候执行 只执行一次
        init: function () {
          let { provinces, value } = self.state;
          let { name, id } = provinces[this.activeIndex];
          let arr = [...value];
          arr[0] = { name: name, id: id };
          self.setState({ value: arr });
          getCitys(id).then((res) => {
            self.setState({
              citys: res.data,
            });
            new Swiper('#swiper2', citys);
          });
        },
        //切换的时候执行
        slideChange: function () {
          let { provinces, value } = self.state;
          let { name, id } = provinces[this.activeIndex];
          let arr = [...value];
          arr[0] = { name: name, id: id };
          self.setState({ value: arr });
          setTimeout(() => {
            getCitys(id).then((res) => {
              self.setState({
                citys: res.data,
              });
              new Swiper('#swiper2', citys);
            });
          }, 0);
        },
      },
    };
    //城市配置
    const citys = {
      ...options,
      initialSlide: val[1] && val[1].index,
      on: {
        init: function () {
          let { citys, value } = self.state;
          let { name, id } = citys[this.activeIndex];
          let arr = [...value];
          arr[1] = { name: name, id: id };
          self.setState({ value: arr });
          getDistricts(id).then((res) => {
            self.setState({
              districts: res.data,
            });
            new Swiper('#swiper3', districts);
          });
        },
        slideChange: function () {
          let { citys, value } = self.state;
          let { name, id } = citys[this.activeIndex];
          let arr = [...value];
          arr[1] = { name: name, id: id };
          self.setState({ value: arr });
          setTimeout(() => {
            getDistricts(id).then((res) => {
              self.setState({
                districts: res.data,
              });
              new Swiper('#swiper3', districts);
            });
          }, 0);
        },
      },
    };
    //地区配置
    const districts = {
      ...options,
      initialSlide: val[2] && val[2].index,
      on: {
        init: function () {
          let { districts, value } = self.state;
          let { name, id } = districts[this.activeIndex];
          let arr = [...value];
          arr[2] = { name: name, id: id };
          self.setState({ value: arr });
        },
        slideChange: function () {
          let { districts, value } = self.state;
          let { name, id } = districts[this.activeIndex];
          let arr = [...value];
          arr[2] = { name: name, id: id };
          self.setState({ value: arr });
        },
      },
    };
  };
  componentDidMount() {
    this.initArea();
  }
  render() {
    const { value, provinces, citys, districts } = this.state;
    return (
      <>
        <div className={styles.page}>
          <div
            className={styles.cover}
            onClick={() => {
              this.props.isShow(false);
            }}
          ></div>
          <div id="AreaSelection" className={styles.AreaSelection}>
            {/* 此处可加入标题头 */}
            <div className={styles.selectArr}>
              {/* 选中横条框 */}
              <div
                className={styles.containerCover}
                onClick={() => {
                  this.props.isShow(false);
                }}
              ></div>
              <div className={styles.container}>
                <div className="swiper-container" id="swiper1">
                  <div className="swiper-wrapper">
                    {provinces.map((pro, p) => (
                      <div key={p} className="swiper-slide">
                        {pro.name}
                      </div>
                    ))}
                  </div>
                </div>
              </div>
              <div className={styles.container}>
                <div className="swiper-container" id="swiper2">
                  <div className="swiper-wrapper">
                    {citys.map((cit, c) => (
                      <div key={c} className="swiper-slide">
                        {cit.name}
                      </div>
                    ))}
                  </div>
                </div>
              </div>
              <div className={styles.container}>
                <div className="swiper-container" id="swiper3">
                  <div className="swiper-wrapper">
                    {districts.map((cou, c) => (
                      <div key={c} className="swiper-slide">
                        {cou.name}
                      </div>
                    ))}
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div className={styles.bottom}>
            <div
              className="commonPrimaryBotton"
              style={{ width: '13.38rem', fontSize: '.88rem' }}
              onClick={() => {
                this.props.onChange(value);
                this.props.isShow(false);
                console.log(value);
              }}
            >
              确定
            </div>
          </div>
        </div>
      </>
    );
  }
}

样式引用
因为这里有针对 swiper 样式的修改,所以只能在全局样式里做了调整

//swiper 样式
#AreaSelection {
  .swiper-slide {
    width: auto;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #888888;
  }
  .swiper-slide-active {
    color: #2d353a;
    font-size: 1rem;
  }
}

//组件引用的样式
import styles from './index.less';
.page {
  width: 100vw;
  height: 100vh;
  position: fixed;
  z-index: 100;
  left: 0;
  top: 0;
}
.cover {
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: black;
  opacity: 0.5;
  top: 0;
  left: 0;
}
.bottom {
  position: absolute;
  bottom: 0;
  width: 100%;
  left: 0;
  z-index: 10;
  min-height: 3.6rem;
  background-color: white;
  padding: 0.5rem 1rem;
  box-shadow: 0 0 5px #f3f3f3;
}
@slideHeight: 40px;
//地区选择样式
.AreaSelection {
  // background-color: white;
  width: 100%;
  position: absolute;
  bottom: 0;
  left: 0;
  z-index: 10;
  // padding-bottom: 4rem;
  background-color: white;
  animation: showUp 0.3s ease both 1;
  .selectArr {
    display: flex;
    position: relative;
    overflow: hidden;
  }
  .containerCover {
    width: 100%;
    height: @slideHeight;
    border: 1px solid #f3f3f3;
    border-left: none;
    border-right: none;
    position: absolute;
    left: 0;
    top: @slideHeight;
    z-index: 9;
    pointer-events: none;
  }
  .container {
    flex-grow: 1;
    flex-basis: 0; //解决 flex-grow 内容撑开问题
    text-align: center;
    background-color: white;
    height: 14rem;
  }
}
@keyframes showUp {
  0% {
    opacity: 0;
    transform: translateY(100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容