程序员鸡皮

文章 分类 评论
61 3 9

站点介绍

一名PHP全栈程序员的日常......

React实战爱彼迎项目(四)

abzzp 2024-09-17 293 0条评论 前端

首页 / 正文
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......

发布于2024-07-04

房间列表

首先来实现,房间列表的实现,代码如下:

// 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;
            }
        }
    }
`

暂时就更新到这了,我们以后再更新其他的。谢谢大家观看

评论(0)

最新评论

  • abzzp

    十天看一部剧,还可以吧[[呲牙]]

  • ab

    @梦不见的梦 行,谢谢提醒,我优化一下

  • 梦不见的梦

    网站的速度有待提升,每次打开都要转半天还进不来呢

  • abzzp

    @React实战爱彼迎项目(二) - 程序员鸡皮 哪里有问题了,报错了吗?[[微笑]]

  • abzzp

    @Teacher Du 那是怕你们毕不了业,我大学那会儿给小礼品[[发呆]]

  • Teacher Du

    我们大学那会,献血还给学分~

  • @ab 我想去学网安,比如网警,但分也贼高😕

  • ab

    @夜 加油,你一样也可以成为程序员的,需要学习资料可以V我

  • 佬发布的好多技术文章似乎我只能评论这篇,真正的程序员!!哇塞 我也想去献血,过两年就成年了可以去献血了

日历

2025年01月

   1234
567891011
12131415161718
19202122232425
262728293031 

文章目录

推荐关键字: vue JavaScript React Golang 观后感 ES6 SEO 读后感

站点公告
本站是作为记录一名北漂程序员编程学习以及日常的博客,欢迎添加微信BmzhbjzhB咨询交流......
点击小铃铛关闭
配色方案