使用React封装移动端H5地址选择器

2021-04-02  本文已影响0人  爱吃美食不爱长胖

示例

image.png
image.png

使用插件:swiper

一、创建 AddressSelectModal文件夹,并分别创建 addressInfo.tsx 、 index.less、index.tsx

index.tsx

import React, { useEffect, useState } from "react";
import styles from "./index.less";
import { addressInfo } from "./addressInfo";
import closeIcon from "@/assets/icons/closeIcon.png";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/swiper.less";

interface AddressProps {
  prefixCls?: string;
  closeModel?: () => void;
  getAddress?: (arg1: { city?: string; district?: string; streetName?: string }) => void;
  addressTitle?: string;
  hierarchy?: number; //层级,表示该地址有几层
  visible?: boolean;
}
interface AddressListType {
  id: number | string;
  code?: string;
  name: string;
  engName?: string;
  isLetter?: boolean;
}
interface titleTab {
  name: string;
  isCurrentSelected: boolean;
  title: string;
  id: number | string;
}
const Address: React.FC<AddressProps> = ({
  prefixCls,
  closeModel,
  getAddress,
  addressTitle,
  hierarchy = 3,
  visible = false,
}) => {
  const [addressSwipe, setAddressSwipe] = useState<any>(null);
  let tabObj = {
    name: "",
    isCurrentSelected: true,
    title: "",
    id: -999,
  };
  const [selectOne, setSelectOne] = useState<titleTab>({ ...tabObj });
  const [selectTwo, setSelectTwo] = useState<titleTab>({ ...tabObj });
  const [selectThree, setSelectThree] = useState<titleTab>({ ...tabObj });

  const [selectOneList, setSelectOneList] = useState<AddressListType[] | null>();
  const [selectTwoList, setSelectTwoList] = useState<AddressListType[] | null>();
  const [selectThreeList, setSelectThreeList] = useState<AddressListType[] | null>();

  const [selectOneListLetter, setSelectOneListLetter] = useState<string[] | null>();
  const [selectTwoListLetter, setSelectTwoListLetter] = useState<string[] | null>();
  const [selectThreeListLetter, setSelectThreeListLetter] = useState<string[] | null>();

  useEffect(() => {
    let handleResult = handleSelectList(addressInfo);
    setSelectOneList(handleResult.resultTabList);
    setSelectOneListLetter(handleResult.letterList);
    initTab();
  }, []);

  const initTab = () => {
    let defaultTabObj = {
      name: "",
      isCurrentSelected: false,
      id: -999,
    };
    let obj1 = Object.assign({ ...defaultTabObj }, { isCurrentSelected: true, title: "选择省" });
    let obj2 = Object.assign({ ...defaultTabObj }, { title: "选择市" });
    let obj3 = Object.assign({ ...defaultTabObj }, { title: "选择区" });
    setSelectOne(obj1);
    setSelectTwo(obj2);
    setSelectThree(obj3);
    setSelectTwoList([]);
    setSelectThreeList([]);
    setSelectTwoListLetter([]);
    setSelectThreeListLetter([]);
  };

  const changeTab = (num: number) => {
    let obj = {
      isCurrentSelected: false,
    };
    let titleTabObj: titleTab = {
      name: "",
      isCurrentSelected: false,
      title: "",
      id: -999,
    };
    let obj1 = { ...titleTabObj },
      obj2 = { ...titleTabObj },
      obj3 = { ...titleTabObj };
    if (num === 1) {
      obj1 = Object.assign({ ...selectOne }, { isCurrentSelected: true });
      obj2 = Object.assign({ ...selectTwo }, { ...obj });
      obj3 = Object.assign({ ...selectThree }, { ...obj });
    } else if (num === 2) {
      obj1 = Object.assign({ ...selectOne }, { ...obj });
      obj2 = Object.assign({ ...selectTwo }, { isCurrentSelected: true });
      obj3 = Object.assign({ ...selectThree }, { ...obj });
    } else if (num === 3) {
      obj1 = Object.assign({ ...selectOne }, { ...obj });
      obj2 = Object.assign({ ...selectTwo }, { ...obj });
      obj3 = Object.assign({ ...selectThree }, { isCurrentSelected: true });
    }
    setSelectOne(obj1);
    setSelectTwo(obj2);
    setSelectThree(obj3);
    addressSwipe.slideTo(num - 1);
  };

  const handleSelectList = (tabList: AddressListType[]): { resultTabList: AddressListType[]; letterList: string[] } => {
    let currentTabList: AddressListType[] = JSON.parse(JSON.stringify(tabList));
    let resultTabList: AddressListType[] = [];
    let letterObj: any = {};
    currentTabList.sort(function(a: { name: string }, b: { name: string }) {
      if (a.name > b.name) {
        return 1;
      } else if (a.name === b.name) {
        return 0;
      } else {
        return -1;
      }
    });
    currentTabList.map((item: AddressListType) => {
      let obj: AddressListType = {
        id: item.id,
        code: item.code || "",
        name: item.name || "",
        engName: item.engName || "",
        isLetter: false,
      };
      let firstLetter = item.name ? item.name[0] : "";
      if (firstLetter && !letterObj[firstLetter]) {
        letterObj[firstLetter] = 1;
        resultTabList.push({
          name: firstLetter,
          id: item.id + firstLetter,
          isLetter: true,
        });
      }
      resultTabList.push(obj);
    });
    let letterList = Object.keys(letterObj);
    return { resultTabList, letterList };
  };

  const getSelectedChildrenList = (num: number, currentId: number | string) => {
    if (num === 1) {
      let currentSelectTwoList: AddressListType[] = [];
      for (let item of addressInfo) {
        if (item.id === currentId) {
          currentSelectTwoList = JSON.parse(JSON.stringify(item.city));
          break;
        }
      }
      let handleResult = handleSelectList(currentSelectTwoList);
      setSelectTwoList(handleResult.resultTabList);
      setSelectTwoListLetter(handleResult.letterList);
    } else if (num === 2) {
      let currentSelectThreeList: AddressListType[] = [];
      let parentList = [];
      for (let item of addressInfo) {
        if (item.id === selectOne.id) {
          parentList = JSON.parse(JSON.stringify(item.city));
          break;
        }
      }
      for (let item of parentList) {
        if (item.id === currentId) {
          currentSelectThreeList = item.country;
          break;
        }
      }
      console.log(selectOne, currentSelectThreeList, "currentSelectThreeList");
      let handleResult = handleSelectList(currentSelectThreeList);
      setSelectThreeList(handleResult.resultTabList);
      setSelectThreeListLetter(handleResult.letterList);
    }
  };

  const checkItem = (num: number, item: AddressListType) => {
    if (item.isLetter) true;
    if (num < hierarchy) {
      getSelectedChildrenList(num, item.id);
    }
    let defaultTabObj = {
      name: "",
      isCurrentSelected: false,
      id: -999,
    };
    switch (num) {
      case 1:
        {
          let obj1 = Object.assign(
            { ...selectOne },
            {
              name: item.name,
              isCurrentSelected: false,
              id: item.id,
            },
          );
          setSelectOne({ ...obj1 });
          let obj2 = Object.assign({ ...selectTwo }, { ...defaultTabObj }, { isCurrentSelected: true });
          setSelectTwo({ ...obj2 });
          let obj3 = Object.assign({ ...selectThree }, { ...defaultTabObj });
          setSelectThree({ ...obj3 });
          setSelectThreeList([]);
        }
        break;
      case 2:
        {
          let obj2 = Object.assign(selectTwo, {
            name: item.name,
            isCurrentSelected: false,
            id: item.id,
          });
          setSelectTwo({ ...obj2 });
          let obj3 = Object.assign({ ...selectThree }, { ...defaultTabObj }, { isCurrentSelected: true });
          setSelectThree({ ...obj3 });
        }
        break;
      case 3:
        {
          setSelectThree(
            Object.assign(selectThree, {
              name: item.name,
              isCurrentSelected: true,
              id: item.id,
            }),
          );
        }
        break;
    }
    if (hierarchy === num) {
      // 关闭窗口
      getAddress && getAddress({ city: selectOne.name, district: selectTwo.name, streetName: selectThree.name });
      handleClose();
    } else {
      setTimeout(() => {
        addressSwipe.slideTo(num);
      }, 0);
    }
  };

  const getTopTab = (tabIndex: number, selectTab: titleTab) => {
    return (
      <span
        className={`${selectTab.isCurrentSelected ? styles.currentTab : ""}`}
        onClick={() => {
          changeTab(tabIndex);
        }}
      >
        {selectTab.name ? selectTab.name : selectTab.title}
      </span>
    );
  };

  const getSwiperContent = (currentTab: number, tabList: AddressListType[], letterList: string[], tab: titleTab) => {
    return (
      <div className={`${styles[prefixCls + "-wrapper-tab"]}`}>
        <div className={styles.listContent}>
          {(tabList || []).map(item => {
            return (
              <div
                key={item.id}
                className={`${item.isLetter ? styles.letter : styles.item} ${
                  item.name === tab.name ? styles.checked : ""
                }`}
                id={item.isLetter ? currentTab + "-selected-" + item.name : undefined}
                onClick={() => {
                  checkItem(currentTab, item);
                }}
              >
                {item.name}
              </div>
            );
          })}
        </div>
        <div className={styles.rightLetter}>
          {(letterList || []).map((item, index) => {
            return (
              <span
                key={index}
                onClick={() => {
                  const id = document.getElementById(currentTab + "-selected-" + item);
                  if (id) {
                    id.scrollIntoView({ block: "start", behavior: "auto" });
                  }
                }}
              >
                {item}
              </span>
            );
          })}
        </div>
      </div>
    );
  };

  const handleClose = () => {
    closeModel && closeModel();
    initTab();
  };

  return (
    <div className={`${styles[prefixCls || ""]} ${visible ? "" : styles[prefixCls + "-hidden"]}`}>
      <div className={`${styles[prefixCls + "-content"]}`}>
        <div className={`${styles[prefixCls + "-wrapper"]}`}>
          <div className={`${styles[prefixCls + "-title"]}`}>
            <div className={`${styles[prefixCls + "-title-left"]}`}>{addressTitle || "请选择您的现居地址"}</div>
            <div onClick={handleClose}>
              <img src={closeIcon} className={`${styles[prefixCls + "-title-close"]}`} />
            </div>
            <div className={`${styles[prefixCls + "-title-sub"]}`}>
              {getTopTab(1, selectOne)}

              {selectTwo.name || selectTwo.isCurrentSelected || (selectTwoList || []).length > 0
                ? getTopTab(2, selectTwo)
                : null}
              {selectThree.name || selectThree.isCurrentSelected || (selectThreeList || []).length > 0
                ? getTopTab(3, selectThree)
                : null}
            </div>
          </div>

          <div className={`${styles[prefixCls + "-select"]}`}>
            <Swiper
              spaceBetween={0}
              slidesPerView={1}
              onSlideChange={() => {
                console.log("onSlideChange");
              }}
              onSwiper={swiper => {
                setAddressSwipe(swiper);
              }}
              observeParents={true}
              observer={true}
            >
              <SwiperSlide>
                {getSwiperContent(1, selectOneList || [], selectOneListLetter || [], selectOne)}
              </SwiperSlide>
              {selectTwoList && selectTwoList.length > 0 ? (
                <SwiperSlide>{getSwiperContent(2, selectTwoList, selectTwoListLetter || [], selectTwo)}</SwiperSlide>
              ) : null}
              {selectThreeList && selectThreeList.length > 0 ? (
                <SwiperSlide>
                  {getSwiperContent(3, selectThreeList, selectThreeListLetter || [], selectThree)}
                </SwiperSlide>
              ) : null}
            </Swiper>
          </div>
        </div>
      </div>
    </div>
  );
};

Address.defaultProps = {
  prefixCls: "s-address",
  closeModel: () => {},
  getAddress: () => {},
  hierarchy: 3,
  visible: false,
};
export default Address;

index.less

@bc-white: #fff;
@primaryColor: #3682ff;
@prefixCls: s-address;
.@{prefixCls} {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  z-index: 999;
  background: rgba(0,0,0,0.7);
  .@{prefixCls}-content {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 540 * @vw;
    background: @bc-white;
    .@{prefixCls}-wrapper{
      height: 100%;
      width: 100%;
      display: flex;
      flex-direction: column;
      .@{prefixCls}-title{
        display: flex;
        flex-direction: row;
        align-items: flex-start;
        flex-wrap: wrap;
        height: 98 * @vw;
        .@{prefixCls}-title-left{
          width: 303 * @vw;
          height: 25 * @vw;
          color: #333333;
          font-size: 18 * @vw;
          font-weight: 500;
          margin-top: 20 * @vw;
          margin-left: 25 * @vw;
          line-height: 25 * @vw;
        }
        .@{prefixCls}-title-close{
          width: 15 * @vw;
          height: 15 * @vw;
          margin-top: 12 * @vw;
          margin-left: 20 * @vw;
        }
        .@{prefixCls}-title-sub {
          display: flex;
          flex-direction: row;
          width: 100%;
          padding-left: 25 * @vw;
          span{
            height: 22 * @vw;
            color: @primaryColor;
            font-size: 16 * @vw;
            font-weight: 400;
            margin-right: 20 * @vw;
            line-height: 22 * @vw;
          }
          span.currentTab{
            color: #E3302F;
          }
        }

      }

      .@{prefixCls}-select {
        flex: 1;
        height: 442 * @vw;
        :global{
          .swiper-container{
            height: 100%;
            overflow-y: auto;
          }
        }
        .@{prefixCls}-wrapper-tab{
          position: relative;
          height: 100%;
          .listContent{
            height: 100%;
            overflow-y: auto;
            .letter{
              height: 26 * @vw;
              padding: 0 25 * @vw;
              background: #F9F9FE;
              line-height: 26 * @vw;
            }
            .item{
              height: 58 * @vw;
              margin: 0 15 * @vw;
              padding: 0 10 * @vw;
              line-height: 58 * @vw;
              color: #333;
              font-size: 16 * @vw;
              font-weight: 400;
              border-top: 1px solid #EBEBF1;
            }
            .letter + .item {
              border-top: 0;
            }
            .checked{
              color: @primaryColor;
            }
          }

          .rightLetter{
            position: absolute;
            top: 0;
            right: 0;
            max-height: 442 * @vw;
            display: flex;
            flex-direction: column;
            // justify-content: space-between;
            align-items: center;
            text-align: center;
            width: 17 * @vw;
            span{
              line-height: 20 * @vw;
            }
          }
        }
      }
    }
  }
}
.@{prefixCls}.@{prefixCls}-hidden {
  display: none;
  height: 0;
  width: 0;
  overflow: hidden;
}

addressInfo.tsx ,数据量较大,请前往github获取(https://github.com/XiaoYouEr/Address/blob/main/AddressSelectModal/addressInfo.tsx

示例

index.less
.selectBox{
  border: 1px solid #dcdcdc;
  height: 60 * @vw;
}

index.tsx
import React, { useState } from "react";
import styles from "./index.less";
import Address  from '../AddressSelectModal';

export default function IndexPage() {
  const [address, setAddress] = useState("");
  const [city, setCity] = useState(props.city || "");
  const [district, setDistrict] = useState(props.district || "");
  const [streetName, setStreetName] = useState(props.streetName || "");
  const [addressVisible, setAddressVisible] = useState(false);
  const handleAddress = (result: { city?: string; district?: string; streetName?: string }) => {
    setAddress([result.city || "", result.district || "", result.streetName || ""].join(" "));
    setCity(result.city || "");
    setDistrict(result.district || "");
    setStreetName(result.streetName || "");
  };
  return (
    <div>
      点击选择
      <div className={styles.selectBox} onClick={() => {setAddressVisible(true)}}>
        {currentValue ? currentValue : "请选择您的地址"}
      </div>
      <Address hierarchy={3} getAddress={handleAddress} closeModel={() => {setAddressVisible(false)}} visible={addressVisible}/>
    </div>
  );
}

说明: 该地址使用了泰国地址,可根据需要更换为所需要国家的地址;地址选择框请自行封装,此处只是示例代码,未做样式处理
github地址: https://github.com/XiaoYouEr/Address.git

上一篇 下一篇

猜你喜欢

热点阅读