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 中的 网格分布 就属于这种的,大家也可以先去了解下。
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);
}
}