tsx:
import React, { useEffect, useState } from 'react' import { View, Text, ScrollView, Image } from '@tarojs/components' import classnames from 'classnames' import { find, remove } from 'lodash' import api from '@services/api' import app from '@services/request' import NavBar from '@components/navbar/index' import useNavData from '@hooks/useNavData' import { PRICE_TYPE, SALE_STATUS } from '@constants/house' import '@styles/common/house-list.scss' import '@styles/common/search-tab.scss' import './index.scss' interface IFilter { id: string name: string value?: string } interface IConditionState { region?: IFilter unit_price?: IFilter total_price?: IFilter house_type?: IFilter house_property?: IFilter sale_status?: IFilter renovation?: IFilter feature?: IFilter } const initial_value = { id: '', name: '', value: '' } const INIT_CONDITION = { region: { id: 'all000', name: '不限', value: '' }, unit_price: { id: 'all000', name: '不限', value: '' }, total_price: initial_value, house_type: { id: 'all000', name: '不限', value: '' }, house_property: initial_value, sale_status: initial_value, renovation: initial_value, feature: initial_value } const NewHouse = () => { const { appHeaderHeight, contentHeight } = useNavData() const footerBtnHeight = 60 const scrollHeight = contentHeight * 0.5 - footerBtnHeight const scrollMoreHeight = contentHeight * 0.6 - footerBtnHeight const [tab, setTab] = useState<string>('') const [priceType, setPriceType] = useState<string>('unit_price') const [selected, setSelected] = useState<IConditionState>(INIT_CONDITION) const [condition, setCondition] = useState<any>() const [houseList, setHouseList] = useState<any>([]) const tabs = [ { type: 'region', name: '区域', keys: ['region'] }, { type: 'price', name: '价格', keys: ['unit_price', 'total_price'] }, { type: 'house_type', name: '户型', keys: ['house_type'] }, { type: 'more', name: '更多', keys: ['house_property', 'sale_status', 'renovation', 'feature'] }] const priceTabs = [ { id: 'id_01', name: '按单价', value: "unit_price" }, { id: 'id_02', name: '按总价', value: "total_price" } ] useEffect(() => { fetchCondition() fetchHouseList() }, []) const fetchCondition = () => { app.request({ url: api.getHouseCondition, data: { type: 'newHouse' } }, { isMock: true, loading: false }).then((result: any) => { setCondition(result || {}) }) } const fetchHouseList = () => { app.request({ url: api.getHouseNew, data: {} }, { isMock: true, loading: false }).then((result: any) => { setHouseList(result || []) }) } const switchCondition = (item) => { if (tab === item.type) { setTab('') return } setTab(item.type) } const handleSingleClick = (key: string, item: any) => { setTab('') if (key === 'unit_price') { setSelected({ ...selected, total_price: initial_value, [key]: item }) } else if (key === 'total_price') { setSelected({ ...selected, unit_price: initial_value, [key]: item }) } else { setSelected({ ...selected, [key]: item }) } } const handleMultiClick = (key: string, item: any) => { let selectedValue = selected[key] if (selectedValue instanceof Object) { if (selectedValue.id === item.id) { setSelected({ ...selected, [key]: initial_value }) } else { setSelected({ ...selected, [key]: item }) } } if (selectedValue instanceof Array) { let target = find(selectedValue, { id: item.id }) if (target) { remove(selectedValue, { id: item.id }) setSelected({ ...selected, [key]: selectedValue }) } else { setSelected({ ...selected, [key]: [...selectedValue, item] }) } } } const handleReset = () => { setSelected({ ...selected, house_property: initial_value, renovation: initial_value, sale_status: initial_value, feature: initial_value }) } const handleConfirm = () => { setTab('') } const renderSplitItem = (key: string) => { return ( <ScrollView className="split-list flex-item" scrollY style={{ height: scrollHeight }}> { condition && condition[key].map((item: any, index: number) => ( <View key={index} className={classnames("split-item", selected[key].id === item.id && 'actived')} onClick={() => handleSingleClick(key, item)} >{item.name} </View> )) } </ScrollView> ) } const renderMultiItem = (key: string, title: string = '') => { return ( <View className="search-multi-item"> {title && <View className="title">{title}</View>} <View className="options"> { condition && condition[key].map((item: any, index: number) => ( <View key={index} className={classnames("options-item", selected[key].id === item.id && 'actived')} onClick={() => handleMultiClick(key, item)} > {item.name} </View> )) } </View> </View> ) } const renderShowName = (item: any) => { let showList: string[] = [] for (const key of item.keys) { if (selected[key] instanceof Object) { let showName: string = selected[key].name if (!showName || ['不限', '全部'].includes(showName)) { continue } showList.push(showName) } } if (showList.length > 1) { showList = ['多选'] } return showList.join(',') } return ( <View className="newhouse"> <NavBar title="新房" back={true} /> <View className="fixed-top" style={{ top: appHeaderHeight }}> <View className="newhouse-header view-content"> <View className="newhouse-search"> <Text className="iconfont iconsearch"></Text> <Text className="newhouse-search-text placeholder">请输入楼盘名称或地址</Text> </View> <View className="newhouse-nav-right"> <Text className="iconfont iconmap"></Text> <Text className="text">地图找房</Text> </View> </View> <View className="search-tab"> { tabs.map((item: any, index: number) => { let showName = renderShowName(item) return ( <View key={index} className={classnames('search-tab-item', showName && 'actived')} onClick={() => switchCondition(item)} > <Text className="text">{showName ? showName : item.name}</Text> <Text className="iconfont iconarrow-down-bold"></Text> </View> ) }) } </View> <View className={classnames('search-container', tab === 'region' && 'actived')}> <View className="search-content"> <View className="search-split"> <View className="split-type flex-item"> <View className="split-item actived">区域</View> </View> {renderSplitItem('region')} </View> </View> </View> <View className={classnames('search-container', tab === 'price' && 'actived')}> <View className="search-content"> <View className="search-split"> <View className="split-type flex-item"> { priceTabs.map((item: any) => ( <View key={item.id} className={classnames("split-item", item.value === priceType && 'actived')} onClick={() => setPriceType(item.value)}> {item.name} </View> )) } </View> {renderSplitItem(priceType)} </View> </View> {/* <View className="search-footer"> <Input className="search-input" placeholder="最低价" />- <Input className="search-input" placeholder="最高价" /> <View className="btn confirm-btn single-btn">确定</View> </View> */} </View> <View className={classnames('search-container', tab === 'house_type' && 'actived')}> <View className="search-content"> <View className="search-split"> {renderSplitItem('house_type')} </View> </View> </View> <View className={classnames('search-container', 'search-multi-container', tab === 'more' && 'actived')}> <ScrollView className="search-content search-content-scroll" scrollY style={{ maxHeight: scrollMoreHeight }}> {renderMultiItem('house_property', '类型')} {renderMultiItem('renovation', '装修')} {renderMultiItem('sale_status', '状态')} {renderMultiItem('feature', '特色')} </ScrollView> <View className="search-footer"> <View className="btn reset-btn" onClick={handleReset}>重置</View> <View className="btn confirm-btn" onClick={handleConfirm}>确定</View> </View> </View> </View> <View className={classnames('mask', tab && 'show')} onClick={() => setTab('')}></View> <View className="newhouse-content"> <ScrollView className="house-list view-content" scrollY style={{ height: contentHeight - 85 }}> <View className="house-list-ul"> { houseList.length > 0 && houseList.map((item: any) => ( <View className="house-list-li" key={item.id}> <View className="li-image"> <Image src={item.image_path}></Image> </View> <View className="li-text"> <View className="title mb10"> <Text>{item.house_name}</Text> </View> <View className="small-desc mb10"> <Text>{item.area && item.area.name}</Text> <Text className="line-split"></Text> <Text>建面{item.building_area}平米</Text> </View> <View className="mb10"> <Text className="price">{item.price}</Text> <Text className="price-unit">{PRICE_TYPE[item.price_type]}</Text> </View> <View className="tags"> <Text className={classnames('tags-item', `sale-status-${item.sale_status}`)}>{SALE_STATUS[item.sale_status]}</Text> </View> </View> </View> )) } </View> </ScrollView> </View> </View> ) } export default NewHouse
图例:
scss:
.search-tab { display: flex; justify-content: center; align-items: center; height: 80px; line-height: 80px; font-size: $font-basic; border-bottom: $border; background-color: $white; z-index: 99; &-item { flex: 1; text-align: center; &.actived { color: $primary-color; .iconfont { color: $primary-color; } } .text { display: inline-block; max-width: 70%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; } .iconfont { color: $desc-color; margin-left: 10px; vertical-align: middle; } } } .search-container { position: absolute; width: 100%; max-height: 50vh; z-index: -10; background-color: $white; font-size: $font-basic; transform: translateY(-100%); transition: 0.2s; &.actived { transform: translateY(0); } .search-content-scroll { margin-bottom: 120px; } .search-content { .search-split { display: flex; .flex-item { flex: 1; border-right: $border; } .flex-item:last-child { border-right: 0; } .split-item { height: 80px; line-height: 80px; padding-left: 30px; border-bottom: $border; &.actived { color: $primary-color; background-color: $bg-color; } } } .search-multi-item { padding: 16px 24px; .title { font-size: 30px; color: $title-color; padding: 10px; } .options { display: flex; flex-wrap: wrap; &-item { width: 19.5%; margin: 8px; text-align: center; font-size: 24px; padding: 14px 10px; border: 0.5px solid $bg2-color; border-radius: 6px; background-color: $bg2-color; color: $text-color; &.actived { color: $primary-color; border-color: $primary-color; background-color: $white; } } } } } .search-footer { position: absolute; bottom: 0; display: flex; width: 100%; justify-content: space-around; align-items: center; padding: 20px 0; background-color: #fff; .btn { width: 40%; line-height: 80px; text-align: center; border-radius: 6px; &.reset-btn { color: $text-color; background-color: $bg-color; } &.confirm-btn { color: $white; background-color: $primary-color; } &.single-btn { width: 25%; } } .search-input { width: 25%; height: 80px; line-height: 80px; text-align: center; background-color: $bg-color; } } } .search-multi-container { max-height: 60vh; }