前言
目录
一、列表找房模块-条件筛选(下)
1.1 前三个菜单对应的内容组件FilterPicker实现
1.1.1 思路分析
1、点击前三个标题展示该组件,点击取消的时候隐藏
2、使用PickerView组件来实现页面效果
3、获取到PickerView组件中,选中的筛选条件值
4、点击确定按钮,隐藏该组件,将获取到的筛选条件值传递给父组件
5、由父组件提供展示或隐藏对话框的状态,通过props传递给子组件
6、由父组件提供通过筛选条件获取到的数据(因为所有筛选条件是通过后端接口来获取的),通过props传递给子组件
1.1.2 实现步骤
在Filter组件中需要完成的步骤:
1、提供组件展示或隐藏的状态:openType
2、在render方法中判断当openType的值为 area/mode/price 时,就显示 FilterPicker组件,以及遮罩层
3、在onTitleClick方法中,修改状态 openType为当前 type,展示对话框
4、提供onCancel方法(作为取消按钮和遮罩层的事件)
5、在onCancel方法中,修改状态 openType为空,隐藏对话框
6、将onCancel通过props传递给FilterPicker组件
7、提供onSave方法,作为确定按钮的事件处理
8、发送请求,获取所有筛选条件数据,将数据保存为状态:filtersData
9、封装方法 renderFilterPicker 来渲染FilterPicker组件
10、在方法中,根据openType的类型,从filtersData中获取需要的数据
11、将数据通过props传递给FilterPicker组件
在FilterPicker组件中需要完成的步骤:
1、接收到数据后,将其作为PickerView组件的data
2、在取消按钮的单击事件中调用父组件传过来的方法
1.1.3 代码示例
在src/pages/HouseList/components/Filter/index.js中添加如下代码:
首先,提供组件展示或隐藏的状态和筛选条件数据:
state = {
// 控制 FilterPicker 或 FilterMore 组件的展示或隐藏
openType: '',
// 所有筛选条件数据
filtersData: {
}
}
然后,在render方法中判断openType的值为area/mode/price 时,就显示FilterPicker组件,以及遮罩层:
{
/* 前三个菜单的遮罩层 */}
{
openType === "area" || openType === "mode" || openType === "price" ? (<div className={
styles.mask}></div>) : ("")}
...
{
/* 前三个菜单对应的内容: */}
{
openType === "area" || openType === "mode" || openType === "price" ? (<FilterPicker />) : ("")}
然后,在标题点击事件中,修改状态openType为当前type,展示对话框:
onTitleClick = type => {
this.setState(prevState => {
return {
titleSelectedStatus: {
// 获取当前对象中所有属性的值
...prevState.titleSelectedStatus,
[type]: true
},
// 展示对话框
openType: type
}
})
}
然后,新建onCancel方法,作为取消按钮和遮罩层的事件:
// 取消(隐藏对话框)
onCancel = () => {
this.setState({
openType: ''
})
}
然后,提供onSave方法,作为确定按钮的事件处理
// 保存,隐藏对话框
onSave = () => {
this.setState({
openType: ""
});
};
// 传递给FilterPicker
<FilterPicker
onCancel={
this.onCancel}
onSave={
this.onSave}
/>
//在FilterPicker里面进行一次中转,最后这个按钮是在FilterFooter里面
render() {
let {
onCancel ,onSave} = this.props;
return (
<>
{
/* 选择器组件: */}
<PickerView data={
province} value={
null} cols={
3} />
{
/* 底部按钮 */}
<FilterFooter onCancel={
onCancel} onOk={
onSave}/>
</>
);
}
// 在FilterFooter里面调用
function FilterFooter({
cancelText = '取消',
okText = '确定',
onCancel,
onOk,
className
}) {
return (
<Flex className={
[styles.root, className || ''].join(' ')}>
{
/* 取消按钮 */}
<span
className={
[styles.btn, styles.cancel].join(' ')}
onClick={
onCancel}
>
{
cancelText}
</span>
{
/* 确定按钮 */}
<span className={
[styles.btn, styles.ok].join(' ')} onClick={
onOk}>
{
okText}
</span>
</Flex>
)
}
然后,发送请求,获取所有筛选条件数据:
// 封装获取所有筛选条件的方法
async getFiltersData() {
// 获取当前定位城市id
const {
value } = JSON.parse(localStorage.getItem('hkzf_city'))
const res = await API.get(`/houses/condition?id=${
value}`)
this.setState({
filtersData: res.data.body
})
}
然后,封装方法渲染FilterPicker组件:
// 渲染 FilterPicker 组件的方法
renderFilterPicker() {
const {
openType,
filtersData: {
area, subway, rentType, price }
} = this.state
if (openType !== 'area' && openType !== 'mode' && openType !== 'price') {
return null
}
// 根据 openType 来拿到当前筛选条件数据
let data = []
let cols = 3
switch (openType) {
case 'area':
// 获取到区域数据
data = [area, subway]
cols = 3
break
case 'mode':
data = rentType
cols = 1
break
case 'price':
data = price
cols = 1
break
default:
break
}
return (
<FilterPicker
onCancel={
this.onCancel}
onSave={
this.onSave}
data={
data}
cols={
cols}
/>
)
}
在src/pages/HouseList/components/FilterPicker/index.js中添加如下代码:
首先,接收到数据后,将其作为PickerView组件的data:
export default class FilterPicker extends Component {
render() {
const {
onCancel, onSave, data, cols } = this.props
return (
<>
{
/* 选择器组件: */}
<PickerView data={
data} value={
null} cols={
cols} />
{
/* 底部按钮 */}
<FilterFooter onCancel={
() => onCancel()} onOk={
() => onSave()} />
</>
)
}
}
然后,添加状态value用于获取PickerView组件的选中值:
state = {
value: null
}
然后,给PickerView组件添加配置项onChange,通过参数获取到选中值,并更新状态 value:
<PickerView
data={
data}
// 我们一旦监听了 onChange事件,同步了value值,那么这个组件成了受控组件,所以我们需要同步value的值
value={
this.state.value}
cols={
cols}
onChange={
val => {
this.setState({
value: val });
}}
/>
然后,在确定按钮的事件处理程序中,将type 和 value 作为参数传递给父组件:
{
/* 底部按钮 */}
<FilterFooter onCancel={
onCancel} onOk={
() => onSave(type,this.state.value)} />
1.2 设置默认选中值
1.2.1 需求
如果是之前已经选中某项,当我们再次显示FilterPicker的时候,应该展示默认选中项
1.2.2 实现步骤
1、在Filter组件中,提供选中值状态: selectedValues
2、通过openType获取到当前类型的选中值(defaultValue),通过props传递给FilterPicker组件
3、在FilterPicker组件中,将当前defaultValue设置为状态value的默认值
4、在点击确定按钮后,在Filter组件中更新当前type对应的selectedValues状态值
1.2.3 代码示例
在src/pages/HouseList/components/Filter/index.js中添加如下代码:
首先,提供选中值状态:
// 默认选中的状态
const selectedValues = {
area: ["area", null],
mode: ["null"],
price: ["null"],
more: []
};
...
state = {
...
// 筛选默认选中的状态值
selectedValues
};
然后,获取到当前类型的选中值,通过props传递给FilterPicker组件:
const {
...,
selectedValues
} = this.state;
// 默认选中值
let defaultValue = selectedValues[openType];
...
<FilterPicker
...
defaultValue={
defaultValue}
/>
然后,在点击确定按钮后,更新当前type对应的selectedValues状态值:
// 保存,隐藏对话框
onSave = (type, value) => {
this.setState({
openType: '',
selectedValues: {
...this.state.selectedValues,
[type]: value
}
});
};
1.2.4 默认值不生效问题
问题描述:在前面三个标签之间来回切换时候,默认选中值不会生效,只有当点击确定,重新打开FilterPicker组件时,才会生效
分析:两种操作方式的区别在于有没有重新创建FilterPicker组件,重新创建的时候,会生效,不重新创建,不会生效
原因:不重新创建FilterPicker组件时,不会再次执行state初始化,也就拿不到最新的props
解决方式:给FilterPicker组件添加key值为openType,这样,在不同标题之间切换时候,key值都不相同,React内部会在key不同时候,重新创建该组件
1.3 完善FilterTitle高亮功能
1.3.1 实现思路
1、点击标题时,遍历标题高亮数据
2、如果是当前标题,直接设置为高亮
3、分别判断每个标题对应的筛选条件有没有选中值(判断每个筛选条件的选中值与默认值是否相同,相同表示没有选中值,不同,表示选中了值)
* selectedVal 表示当前type的选中值
* 如果type为area,此时,selectedVal.length !== 2 || selectedVal[0] !== 'area',就表示已经有选中值
* 如果 type 为 mode,此时,selectedVal[0] !== 'null',就表示已经有选中值
* 如果 type 为 price,此时,selectedVal[0] !== 'null',就表示有选中值
4、如果有,就让该标题保持高亮
5、如果没有,就让该标题取消高亮
在之前的初始化组件时,我们已经完成了前两步,现在来继续实现后面三步
1.3.2 实现步骤
1、在标题点击事件 onTitleClick事件里面,获取到两个状态:标题选中状态对象和筛选条件的选中值对象
2、根据当前标题选中状态对象,获取到一个新的标题选中状态对象(newTitleSelectedStatus)
3、使用Object.keys(),遍历标题选中状态对象
4、先判断是否为当前标题,如果是,直接让该标题选中状态为true(高亮)
5、否则,分别判断每个标题的选中值是否与默认值相同
6、如果不同,则设置该标题的选中状态为true
7、如果相同,则设置该标题的选中状态为false
8、更新状态 titleSelectedStatus的值为: newTitleSelectedStatus
1.3.3 代码示例
在src/pages/Houselist/components/Filter/index.js中添加如下代码:
onTitleClick = type => {
const {
titleSelectedStatus, selectedValues } = this.state
// 创建新的标题选中状态对象
const newTitleSelectedStatus = {
...titleSelectedStatus }
// 遍历标题选中状态对象
// Object.keys() => ['area', 'mode', 'price', 'more']
Object.keys(titleSelectedStatus).forEach(key => {
// key 表示数组中的每一项,此处,就是每个标题的 type 值。
if (key === type) {
// 当前标题
newTitleSelectedStatus[type] = true
return
}
// 其他标题:
const selectedVal = selectedValues[key]
if (
key === 'area' &&
(selectedVal.length !== 2 || selectedVal[0] !== 'area')
) {
// 高亮
newTitleSelectedStatus[key] = true
} else if (key === 'mode' && selectedVal[0] !== 'null') {
// 高亮
newTitleSelectedStatus[key] = true
} else if (key === 'price' && selectedVal[0] !== 'null') {
// 高亮
newTitleSelectedStatus[key] = true
} else if (key === 'more') {
// 更多选择项 FilterMore 组件
} else {
newTitleSelectedStatus[key] = false
}
})
this.setState({
// 展示对话框
openType: type,
// 使用新的标题选中状态对象来更新
titleSelectedStatus: newTitleSelectedStatus
})
}
1.4 列表找房模块-FilterMore组件实现
1.4.1 渲染组件数据
实现步骤:
1、封装renderFilterMore方法,渲染FilterMore组件
2、从filtersData中,获取数据(roomType,oriented,floor,characteristic),通过props传递给FilterMore组件
3、FilterMore组件中,通过props获取到数据,分别将数据传递给renderFilters方法
4、在renderFilters方法中,通过参数接收数据,遍历数据,渲染标签
代码示例:
在src/pages/Houselist/components/Filter/index.js中添加如下代码:
renderFilterMore() {
// 获取对应数据 roomType,oriented,floor,characteristic
const {
openType,
filtersData: {
roomType, oriented, floor, characteristic }
} = this.state;
// 把数据封装到一个对象中,方便传递
const data = {
roomType,
oriented,
floor,
characteristic
};
if (openType !== "more") {
return null;
}
// 传递给子组件
return <FilterMore data={
data}/>;
}
在src/pages/Houselist/components/FilterMore/index.js中添加如下代码:
// 渲染标签
renderFilters(data) {
// 高亮类名: styles.tagActive
return data.map(item => {
return (
<span key={
item.value} className={
[styles.tag].join(" ")}>{
item.label}</span>
);
});
}
render() {
const {
data: {
roomType, oriented, floor, characteristic }
} = this.props;
return (
<div className={
styles.root}>
...
<div className={
styles.tags}>
<dl className={
styles.dl}>
<dt className={
styles.dt}> 户型 </dt>
<dd className={
styles.dd}> {
this.renderFilters(roomType)} </dd>
<dt className={
styles.dt}> 朝向 </dt>
<dd className={
styles.dd}> {
this.renderFilters(oriented)} </dd>
<dt className={
styles.dt}> 楼层 </dt>
<dd className={
styles.dd}> {
this.renderFilters(floor)} </dd>
<dt className={
styles.dt}> 房屋亮点 </dt>
<dd className={
styles.dd}>
...
</div>
...
</div>
);
}
1.4.2 获取选中值并且高亮显示
实现步骤:
1、在state中添加状态 selectedValues
2、给标签绑定单击事件,通过参数获取到当前项的value
3、判断selectedValues中是否包含当前value值
4、如果不包含,就将当前项的value添加到selectedValues数组中
5、如果包含,就从selectedValues数组中移除(使用数组的splice方法,根据索引号删除)
6、在渲染标签时,判断selectedValues数组中,是否包含当前项的value,包含,就添加高亮类
代码示例:
在src/pages/Houselist/components/FilterMore/index.js中添加如下代码:
state = {
selectedValues: []
}
onTagClick(value) {
const {
selectedValues } = this.state
// 创建新数组
const newSelectedValues = [...selectedValues]
if (newSelectedValues.indexOf(value) <= -1) {
// 没有当前项的值
newSelectedValues.push(value)
} else {
// 有
const index = newSelectedValues.findIndex(item => item === value)
newSelectedValues.splice(index, 1)
}
this.setState({
selectedValues: newSelectedValues
})
}
// 渲染标签
renderFilters(data) {
const {
selectedValues } = this.state
// 高亮类名: styles.tagActive
return data.map(item => {
const isSelected = selectedValues.indexOf(item.value) > -1
return (
<span
key={
item.value}
className={
[styles.tag, isSelected ? styles.tagActive : ''].join(' ')}
onClick={
() => this.onTagClick(item.value)}
>
{
item.label}
</span>
)
})
}
1.4.3 清除和确定按钮的逻辑处理
实现步骤:
1、设置FilterFooter组件的取消按钮文字为: 清除
2、点击取消按钮时,清空所有选中的项的值(selectedValues:[])
3、点击确定按钮时,将当前选中项的值和type,传递给Filter父组件
4、在Filter组件中的onSave方法中,接收传递过来的选中值,更新状态selectedValues
代码示例:
在src/pages/Houselist/components/FilterMore/index.js中添加如下代码:
// 取消按钮的事件处理程序
onCancel = () => {
this.setState({
selectedValues: []
})
}
// 确定按钮的事件处理程序
onOk = () => {
const {
type, onSave } = this.props
// onSave 是父组件中的方法
onSave(type, this.state.selectedValues)
}
<FilterFooter
className={
styles.footer}
cancelText="清除"
onCancel={
this.onCancel}
onOk={
this.onOk}
/>
在src/pages/Houselist/components/Filter/index.js中添加如下代码:
//传递type跟onSave
<FilterMore data={
data} type={
openType} onSave={
this.onSave} defaultValues={
defaultValues}/>;
1.4.4 设置默认选中值
实现步骤:
1、在渲染FilterMore组件时,从selectedValues中,获取到当前选中值more
2、通过props讲选中值传递给FilterMore组件
3、在FilterMore组件中,讲获取到的选中值,设置为子组件状态selectedValues的默认值
4、给遮罩层绑定事件,在事件中,调用父组件的onCancel关闭FilterMore组件
代码示例:
在src/pages/Houselist/components/Filter/index.js中添加如下代码:
renderFilterMore() {
...
let defaultValues = selectedValues.more
// 传递给子组件
return <FilterMore data={
data} type={
openType} onSave={
this.onSave} defaultValues={
defaultValues} onCancel={
this.onCancel}/>;
}
在src/pages/Houselist/components/FilterMore/index.js中添加如下代码:
state = {
selectedValues: this.props.defaultValues
};
{
/* 遮罩层 */}
<div className={
styles.mask} onClick={
this.props.onCancel}/>
1.4.5 完善FilterTitle高亮功能
实现步骤:
1、在Filter组件的onTitleClick方法中,添加type为more的判断条件
2、当选中值数组长度不为0的时候,表示FilterMore组件中有选中项,此时,设置选中状态高亮
3、点击确定按钮时,根据参数type和value,判断当前菜单是否高亮
4、在关闭对话框时(onCancel),根据type和当前type的选中值,判断当前菜单是否高亮
代码示例:
在src/pages/Houselist/components/Filter/index.js中添加如下代码:
// 保存,隐藏对话框
onSave = (type, value) => {
const {
titleSelectedStatus } = this.state;
let newTitleSelectedStatus = {
...titleSelectedStatus };
let selectedVal = value;
if (
type === "area" &&
(selectedVal.length !== 2 || selectedVal[0] !== "area")
) {
newTitleSelectedStatus[type] = true;
} else if (type === "mode" && selectedVal[0] !== "null") {
newTitleSelectedStatus[type] = true;
} else if (type === "price" && selectedVal[0] !== "null") {
newTitleSelectedStatus[type] = true;
} else if (type === "more" && selectedVal.length !== 0) {
// 更多选择
newTitleSelectedStatus[type] = true;
} else {
newTitleSelectedStatus[type] = false;
}
this.setState({
openType: "",
titleSelectedStatus: newTitleSelectedStatus,
selectedValues: {
...this.state.selectedValues,
[type]: value
}
});
};