React项目实战之租房app项目(七)列表找房模块之条件筛选

前言

一、列表找房模块-条件筛选(下)

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
      }
    });
  };

总结

猜你喜欢

转载自blog.csdn.net/qq_40652101/article/details/128518587