房间列表
首先来实现,房间列表的实现,代码如下:
// views/entire/index.jsx
import React,{memo, useEffect} from "react";
import { EntireWrapper } from "./style";
import EntireFilter from "./entire-filter";
import EntireRooms from "./entire-rooms";
import EntirePagination from "./entire-pagination";
import { useDispatch } from "react-redux";
import { fetchRoomListAction } from "@/store/modules/entire/actionCreators";
const Entire = memo(()=>{
// 发送网络请求,获取数据,并且保存当前的页面等等...
const dispatch = useDispatch();
useEffect(()=>{
dispatch(fetchRoomListAction())
},[dispatch])
return (
<EntireWrapper>
<EntireFilter></EntireFilter>
<EntireRooms></EntireRooms>
<EntirePagination></EntirePagination>
</EntireWrapper>
)
})
export default Entire
// views/entire/style.js
import styled from "styled-components";
export const EntireWrapper = styled.div`
`
下面是筛选按钮列表:
// views/entire/entire-filter/index.jsx
import React,{memo, useState} from "react";
import { FilterWrapper } from "./style";
import classNames from "classnames";
import filterData from "@/assets/data/filter_data.json"
const EntireFilter = memo(()=>{
const [selectItems,setSelectItems] = useState([]);
function itemClickHandle(item){
const newItems = [...selectItems]
if(newItems.includes(item)){
// 移除操作
const itemIndex = newItems.findIndex(filterItem=>filterItem === item)
newItems.splice(itemIndex,1)
}else{
newItems.push(item)
}
setSelectItems(newItems);
}
return (
<FilterWrapper>
<div className="filter">
{
filterData.map((item,index)=>{
return (
<div onClick={e=>itemClickHandle(item)} className={classNames("item",{active:selectItems.includes(item)})} key={item}>{item}</div>
)
})
}
</div>
</FilterWrapper>
)
})
export default EntireFilter
// views/entire/entire-filter/style.js
import styled from "styled-components";
export const FilterWrapper = styled.div`
// position:fixed;
z-index:99;
left:0;
right:0;
top:80px;
display:flex;
align-items:center;
height:48px;
padding-left:16px;
border-bottom:1px solid #f2f2f2;
background-color:#fff;
.filter{
display:flex;
.item{
margin:0 4px 0 8px;
padding:6px 12px;
border:1px solid #dce0e0;
border-radius:4px;
color:#484848;
cursor:pointer;
&.active{
background:#008489;
border:1px solid #008489;
color:#ffffff;
}
}
}
`
下面就是列表组件了:
// views/entire/entire-rooms/index.jsx
import React,{ memo } from "react";
import { RoomsWrapper } from "./style";
import { useSelector } from "react-redux";
import RoomItem from "@/components/room-item";
const EntireRooms = memo((props)=>{
// 从redux中获取room List数据
const { roomList,totalCount } = useSelector((state)=>({
roomList:state.entire.roomList,
totalCount:state.entire.totalCount
}))
return (
<RoomsWrapper>
<h2 className="title">{totalCount}多处住所</h2>
<div className="list">
{
roomList.map(item=>{
return (
<RoomItem itemData={item} itemWidth="20%" key={item.id}></RoomItem>
)
})
}
</div>
</RoomsWrapper>
)
})
export default EntireRooms;
// views/entire/entire-rooms/style.js
import styled from "styled-components";
export const RoomsWrapper = styled.div`
padding:40px 20px;
.title{
font-size:22px;
font-weight:700;
color:#222;
margin:0 0 10px 10px;
}
.list{
display:flex;
flex-wrap:wrap;
}
`
然后就是我们的网络请求文件:
// services/modules/entire.js
import hyRequest from ".."
export function getEntireRoomList(offset=0,size=20){
return hyRequest.get({
url:"entire/list",
params:{
offset,
size
}
})
}
这里我们的store模块是用原始的方式写的,也就是没有@reduxjs/toolkit插件,代码如下:
// sotre/modules/entire/actionCreators.js
import { getEntireRoomList } from "@/services/modules/entire"
import * as actionTypes from "./constants"
export const changeCurrentPageAction = (currentPage) =>({
type:actionTypes.CHANGE_CURRENT_PAGE,
currentPage
})
export const changeRoomListAction = (roomList) =>({
type:actionTypes.CHANGE_ROOM_LIST,
roomList
})
export const changeTotalCountAction = (totalCount) =>({
type:actionTypes.CHANGE_TOTAL_COUNT,
totalCount
})
export const fetchRoomListAction = () => {
// 新的函数
return async (dispatch,getState)=>{
// 1.根据页码获取最新的数据
const currentPage = getState().entire.currentPage
const res = await getEntireRoomList(currentPage * 20);
// 2.获取到最新的数据,保存redux的store中
const roomList = res.list
const totalCount = res.totalCount
console.log(res);
console.log(roomList,totalCount);
dispatch(changeRoomListAction(roomList))
dispatch(changeTotalCountAction(totalCount))
}
}
// sotre/modules/entire/constants.js
export const CHANGE_CURRENT_PAGE = "entire/change_current_page"
export const CHANGE_ROOM_LIST = "entire/change_room_list"
export const CHANGE_TOTAL_COUNT = "entire/change_total_count"
// sotre/modules/entire/index.js
import reducer from "./reducer";
export default reducer;
// sotre/modules/entire/reducer.js
import * as actionTypes from "./constants"
const initialState = {
currentPage:0, // 当前页码
roomList:[], // 房间列表
totalCount:0// 总数据个数
}
function reducer(state = initialState,action){
switch(action.type){
case actionTypes.CHANGE_CURRENT_PAGE:
return {...state,currentPage:action.currentPage}
case actionTypes.CHANGE_ROOM_LIST:
return { ...state,roomList:action.roomList}
case actionTypes.CHANGE_TOTAL_COUNT:
return { ...state,totalCount:action.totalCount }
default:
return state
}
}
export default reducer
记得别忘了在index.js中导出,这里我就不贴多余的代码了。
还用一个就是我们的点击选择按钮数据,这个我给大家贴出来。
[
"人数",
"可免费取消",
"房源类型",
"价格",
"位置区域",
"闪定",
"卧室/床数",
"促销/优惠",
"更多筛选条件"
]
房间列表就完成了,我们下面接着写分页器。
分页器
废话不多说,直接贴分页器代码
// base-ui/indicator/index.jsx
import React,{ memo, useEffect,useRef } from "react";
import PropTypes from 'prop-types';
import { IndicatorWrapper } from "./style"
const Indicator = memo((props)=>{
const { selectIndex } = props
const contentRef = useRef()
useEffect(()=>{
// 1.获取selectIndex对应的item
const selectItemEl = contentRef.current.children[selectIndex]
const itemLeft = selectItemEl.offsetLeft
const itemWidth = selectItemEl.clientWidth
// 2.content的宽度
const contentWidth = contentRef.current.clientWidth;
const contentScroll = contentRef.current.scrollWidth;
// 获取selectIndex要滚动的距离
let distance = itemLeft + itemWidth * 0.5 - contentWidth * 0.5;
if(distance < 0) distance = 0
const totalDistane = contentScroll - contentWidth;
if(distance > totalDistane) distance = totalDistane;
contentRef.current.style.transform = `translate(${-distance}px)`;
},[selectIndex])
return (
<IndicatorWrapper>
<div className="i-content" ref={contentRef}>
{
props.children
}
</div>
</IndicatorWrapper>
)
})
Indicator.propTypes = {
selectIndex:PropTypes.number
}
export default Indicator
// base-ui/indicator/style.js
import styled from "styled-components";
export const IndicatorWrapper = styled.div`
overflow:hidden;
.i-content{
display:flex;
position:relative;
transition:transform 200ms ease;
> * {
flex-shrink:0;
}
}
`
轮播图
// components/room-item/index.jsx
import PropTypes from "prop-types"
import React,{ memo, useRef,useState } from "react"
import { Rating } from "@mui/material"
import { ItemWrapper } from "./style"
import { Carousel } from 'antd';
import classNames from 'classnames';
import IconArrowLeft from "@/assets/svg/icon-arrow-left";
import IconArrowRight from "@/assets/svg/icon-arrow-right";
import Indicator from "@/base-ui/indicator"
const RoomItem = memo((props)=>{
const { itemData,itemWidth="25%",itemClick } = props
const sliderRef = useRef();
const [selectIndex,setSelectIndex] = useState(0);
function controlClickHandle(isRight = true){
// 上一个面板、下一个面板
isRight ? sliderRef.current.next() : sliderRef.current.prev()
// 最新的索引
let newIndex = isRight ? selectIndex + 1 : selectIndex - 1;
const piclength = itemData.picture_urls.length;
console.log(piclength);
if(newIndex < 0) newIndex = piclength - 1
if(newIndex > piclength - 1) newIndex = 0
setSelectIndex(newIndex)
}
function itemClickHandle(){
if(itemClick) itemClick(itemData)
}
// 子元素的赋值
const pictureElement = (<div className="cover">
<img src={itemData.picture_url} alt="" />
</div>
);
const sliderElement = (
<div className="slider">
<div className="control">
<div className="btn left" onClick={e => controlClickHandle(false)}>
<IconArrowLeft width="30" height="30"></IconArrowLeft>
</div>
<div className="btn right" onClick={e => controlClickHandle(true)}>
<IconArrowRight width="30" height="30"></IconArrowRight>
</div>
</div>
<div className="indicator">
<Indicator selectIndex={selectIndex}>
{
itemData?.picture_urls?.map((item, index) => {
return (
<div className="item" key={item}>
<span className={classNames("dot", { active: selectIndex === index })}></span>
</div>
)
})
}
</Indicator>
</div>
<Carousel dots={false} ref={sliderRef}>
{
itemData?.picture_urls?.map(item => {
return (
<div className="cover" key={item}>
<img src={item} alt="" />
</div>
)
})
}
</Carousel>
</div>
)
return (
<ItemWrapper verifycolor={itemData?.verify_info?.text_color || "#39576a"} onClick={itemClickHandle} itemwidth={itemWidth}>
<div className="inner">
{ !itemData.picture_urls ? pictureElement : sliderElement}
<div className="desc">
{itemData.verify_info.messages.join(" · ")}
</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
</div>
{/* 评分 */}
<div className="bottom">
<Rating value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{fontSize:"12px",color:"#00848A",marginRight:"-1px"}}
></Rating>
<span className="count">{itemData.reviews_count}</span>
{
itemData.bottom_info && <span className="extra">
·{itemData.bottom_info?.content}
</span>
}
</div>
</ItemWrapper>
)
})
RoomItem.propTypes = {
itemData: PropTypes.object
}
export default RoomItem
import styled from "styled-components";
export const ItemWrapper = styled.div`
flex-shrink: 0;
box-sizing: border-box;
width: ${props => props.itemwidth};
padding: 8px;
.inner {
width: 100%;
}
.cover {
position: relative;
box-sizing: border-box;
padding: 66.66% 8px 0;
border-radius: 3px;
overflow: hidden;
img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit:cover;
}
}
.slider{
position:relative;
cursor:pointer;
&:hover{
.control{
display:flex;
}
}
.control{
position:absolute;
z-index:1;
left:0;
right:0;
top:0;
display:none;
justify-content:space-between;
bottom:0;
color:#fff;
.btn{
display:flex;
justify-content:center;
align-items:center;
width:83px;
height:100%;
background:linear-gradient(to left, transparent 0%,rgba(0,0,0,0.25) 100%);
&.right{
background:linear-gradient(to right, transparent 0%,rgba(0,0,0,0.25) 100%);
}
}
}
.indicator{
position:absolute;
z-index:9;
width:30%;
left:0;
right:0;
bottom:10px;
margin:0 auto;
.item{
display:flex;
justify-content:center;
align-items:center;
width:14.29%;
.dot{
width:6px;
height:6px;
background-color:#fff;
border-radius:50%;
&.active{
width:8px;
height:8px;
background-color:red;
}
}
}
}
}
.desc {
margin: 10px 0 5px;
font-size: 12px;
font-weight: 700;
color: ${props => props.verifyColor};
}
.name {
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.price {
margin: 8px 0;
}
.bottom {
display: flex;
align-items: center;
font-size: 12px;
font-weight: 600;
color: ${props => props.theme.text.primaryColor};
.count {
margin: 0 2px 0 4px;
}
.MuiRating-decimal {
margin-right: -2px;
}
}
`
自定义分页器
// views/entire/entire-filter/index.jsx
import React,{memo, useState} from "react";
import { FilterWrapper } from "./style";
import classNames from "classnames";
import filterData from "@/assets/data/filter_data.json"
const EntireFilter = memo(()=>{
const [selectItems,setSelectItems] = useState([]);
function itemClickHandle(item){
const newItems = [...selectItems]
if(newItems.includes(item)){
// 移除操作
const itemIndex = newItems.findIndex(filterItem=>filterItem === item)
newItems.splice(itemIndex,1)
}else{
newItems.push(item)
}
setSelectItems(newItems);
}
return (
<FilterWrapper>
<div className="filter">
{
filterData.map((item,index)=>{
return (
<div onClick={e=>itemClickHandle(item)} className={classNames("item",{active:selectItems.includes(item)})} key={item}>{item}</div>
)
})
}
</div>
</FilterWrapper>
)
})
export default EntireFilter
// views/entire/entire-filter/style.js
import styled from "styled-components";
export const FilterWrapper = styled.div`
// position:fixed;
z-index:99;
left:0;
right:0;
top:80px;
display:flex;
align-items:center;
height:48px;
padding-left:16px;
border-bottom:1px solid #f2f2f2;
background-color:#fff;
.filter{
display:flex;
.item{
margin:0 4px 0 8px;
padding:6px 12px;
border:1px solid #dce0e0;
border-radius:4px;
color:#484848;
cursor:pointer;
&.active{
background:#008489;
border:1px solid #008489;
color:#ffffff;
}
}
}
`
暂时就更新到这了,我们以后再更新其他的。谢谢大家观看
十天看一部剧,还可以吧
@梦不见的梦 行,谢谢提醒,我优化一下
网站的速度有待提升,每次打开都要转半天还进不来呢
@React实战爱彼迎项目(二) - 程序员鸡皮 哪里有问题了,报错了吗?
@Teacher Du 那是怕你们毕不了业,我大学那会儿给小礼品
我们大学那会,献血还给学分~
@ab 我想去学网安,比如网警,但分也贼高😕
@夜 加油,你一样也可以成为程序员的,需要学习资料可以V我
佬发布的好多技术文章似乎我只能评论这篇,真正的程序员!!哇塞 我也想去献血,过两年就成年了可以去献血了