React<移動端自定義地區(qū)選擇組件>

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

一般大部分組件都會有對應(yīng)的地區(qū)選擇組件菱鸥,類似于 Ant Design Mobile 中的地區(qū)選擇。當然抚垄,如果你們的產(chǎn)品和設(shè)計沒有過多的要求蜕窿,完全可以直接用,而且穩(wěn)定性也很好呆馁。
然而我就沒這么幸運~ ??

?? 產(chǎn)品需求

本片的代碼組件只適用于此種產(chǎn)品需求

省份 城市 區(qū)域 為3個接口

//獲取省份
export async function getProvinces() {
  return request('/user/provinces', {
    method: 'GET',
  });
}
//根據(jù)省份id獲取城市
export function getCitys(provinceId) {
  return request(`/user/province/cities/${provinceId}`, {
    method: 'GET',
  });
}
//根據(jù)城市id獲取區(qū)域
export function getDistricts(cityId) {
  return request(`/user/city/districts/${cityId}`, {
    method: 'GET',
  });
}

?? 插件 Swiper

為了想要自己實現(xiàn)這種滑動拖拽的效果桐经,自己也查閱了很多文章,最后選用了 swiper浙滤。
你可能會懷疑次询,這不是做輪播圖的嗎,這也能搞瓷叫?屯吊!
沒錯,它確實能搞摹菠,swiper 中的 網(wǎng)格分布 就屬于這種的,大家也可以先去了解下盒卸。

安裝
Swiper 在 React 中如何使用

 npm i swiper

你可以跟著官方說的搞,但是我這邊并沒有引入它所說的 import 'swiper/swiper.scss'; 因為這里根本也就用不到次氨。也可能會發(fā)現(xiàn)我的寫法和官方提供的有點不一致蔽介,有需要的可以在我代碼上改造。
??注意:多個 swiper 的創(chuàng)建方法,可以添加不同的 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>

?? 拓展組件

本品文章只會給大家提供基礎(chǔ)的地區(qū)選擇組件,可能代碼有點不完美虹蓄,但是學會了也就能自己隨便搞了犀呼,后續(xù)也希望大家提出寶貴意見和代碼優(yōu)化。
這是我們產(chǎn)品最終的地區(qū)選擇效果圖薇组,大家也能基于這個自行擴展~

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


?? 組件使用
import AreaSelection from '@/components/AreaSelection';
...
//show 組件的顯示狀態(tài)
//isShow 子組件控制組件的顯示方法
//value 默認回顯數(shù)據(jù) 格式為['上海','上海市','靜安區(qū)']
//onChange 返回數(shù)據(jù) [{name:'上海',id:600003},{name:'上海市',id:6003},{name:'靜安區(qū)',id:1036}]
        <AreaSelection
          show={顯示狀態(tài)}
          value={回顯數(shù)據(jù)}
          isShow={(val) => {
          //顯示方法
          }}
          onChange={(val) => {
          //返回數(shù)據(jù)
          }}
        />

?? 組件代碼
import React, { Component } from 'react';
import { getProvinces, getCitys, getDistricts } from '@/api/user';
import styles from './index.less';
import Swiper from 'swiper';
//根據(jù)地區(qū)名字篩選下標
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 };
}
//地區(qū)選擇組件
export default class AreaSelection extends Component {
  state = {
    value: this.props.value,
    activeIndex: [], //回顯時候的默認數(shù)據(jù)
    provinces: [], //省份
    citys: [], //城市
    districts: [], //區(qū)縣
  };
  initArea = async () => {
    //異步方法  獲取省份 - 根據(jù)省份查城市 - 根據(jù)城市查區(qū)域
    //因為選中的數(shù)據(jù)為 [北京市,北京市,東城區(qū)] ,所以回顯數(shù)據(jù)時要根據(jù)名稱去匹配對應(yīng)的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));
        }
      });
      //獲取區(qū)域
      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, //網(wǎng)格分布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: {
        //初始化時候執(zhí)行 只執(zhí)行一次
        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);
          });
        },
        //切換的時候執(zhí)行
        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);
        },
      },
    };
    //地區(qū)配置
    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 樣式的修改,所以只能在全局樣式里做了調(diào)整

//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;
//地區(qū)選擇樣式
.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 內(nèi)容撐開問題
    text-align: center;
    background-color: white;
    height: 14rem;
  }
}
@keyframes showUp {
  0% {
    opacity: 0;
    transform: translateY(100%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炭菌,一起剝皮案震驚了整個濱河市罪佳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌黑低,老刑警劉巖赘艳,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異克握,居然都是意外死亡蕾管,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門玛荞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娇掏,“玉大人,你說我怎么就攤上這事勋眯∮の啵” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵客蹋,是天一觀的道長塞蹭。 經(jīng)常有香客問我,道長讶坯,這世上最難降的妖魔是什么番电? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮辆琅,結(jié)果婚禮上漱办,老公的妹妹穿的比我還像新娘。我一直安慰自己婉烟,他們只是感情好娩井,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著似袁,像睡著了一般洞辣。 火紅的嫁衣襯著肌膚如雪咐刨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天扬霜,我揣著相機與錄音定鸟,去河邊找鬼。 笑死著瓶,一個胖子當著我的面吹牛联予,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蟹但,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躯泰,長吁一口氣:“原來是場噩夢啊……” “哼谭羔!你這毒婦竟也來了华糖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘟裸,失蹤者是張志新(化名)和其女友劉穎客叉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體话告,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡兼搏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沙郭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佛呻。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖病线,靈堂內(nèi)的尸體忽然破棺而出吓著,到底是詐尸還是另有隱情,我是刑警寧澤送挑,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布绑莺,位于F島的核電站,受9級特大地震影響惕耕,放射性物質(zhì)發(fā)生泄漏纺裁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一司澎、第九天 我趴在偏房一處隱蔽的房頂上張望欺缘。 院中可真熱鬧,春花似錦挤安、人聲如沸谚殊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽络凿。三九已至骡送,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間絮记,已是汗流浹背摔踱。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怨愤,地道東北人派敷。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像撰洗,于是被迫代替她去往敵國和親篮愉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容