Antd使用
npm install antd --save- 版本
"antd": "^5.7.0",
- 在index.js文件中配置中文,不用引入css文件
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
- 组件中使用,按需引入即可
import { Carousel } from "antd";
const contentStyle = {
height: "160px",
color: "#fff",
lineHeight: "160px",
textAlign: "center",
background: "#364d79",
};
{/* antd */}
<Carousel autoplay>
<div>
<h3 style={contentStyle}>1</h3>
</div>
<div>
<h3 style={contentStyle}>2</h3>
</div>
<div>
<h3 style={contentStyle}>3</h3>
</div>
<div>
<h3 style={contentStyle}>4</h3>
</div>
</Carousel>
效果:
room-item中轮播图组件使用
import PropTypes from "prop-types";
import React, { memo, useRef } from "react";
import { ItemWrapper } from "./style";
import Rating from "@mui/material/Rating";
import { Carousel } from "antd";
import IconArrowLeft from "@/assets/svg/icon_arrow_left";
import IconArrow from "@/assets/svg/icon_arrow";
const RoomItem = memo((props) => {
const { itemData, itemWidth = "25%" } = props;
const swiperRef = useRef(); //Carousel 组件实例
// 事件处理逻辑
function controlClickHandle(isRight = true) {
isRight ? swiperRef.current.next() : swiperRef.current.prev();
}
return (
<ItemWrapper itemWidth={itemWidth} verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
<div className="inner">
{/* <div className="cover">
<img src={itemData.picture_url} alt="" />
</div> */}
<div className="swiper">
<div className="control">
<div className="btn left" onClick={(e) => controlClickHandle(false)}>
<IconArrowLeft width="30" height="30" />
</div>
<div className="btn right" onClick={(e) => controlClickHandle(true)}>
<IconArrow width="30" height="30" />
</div>
</div>
<Carousel dots={false} autoplay ref={swiperRef}>
{itemData?.picture_urls?.map((item) => {
return (
<div className="cover" key={item}>
<img src={item} alt="" />
</div>
);
})}
</Carousel>
</div>
<div className="desc">{itemData.verify_info.messages.join("·")}</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
<div className="bottom">
<Rating
value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{ fontSize: "12px", color: "#00848A" }}
/>
<span className="count">{itemData.reviews_count}</span>
{itemData?.bottom_info && <span className="extra">·{itemData?.bottom_info?.content}</span>}
</div>
</div>
</ItemWrapper>
);
});
RoomItem.propTypes = {
itemData: PropTypes.object,
};
export default RoomItem;
import styled from "styled-components";
export const ItemWrapper = styled.div`
flex-shrink: 0;
/* width: 25%; */
/* width: 33.33%; */
width: ${(props) => props.itemWidth};
padding: 8px;
box-sizing: border-box;
.inner {
width: 100%;
}
.swiper {
position: relative;
cursor: pointer;
&:hover {
.control {
display: flex;
}
}
.control {
position: absolute;
z-index: 1;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
justify-content: space-between;
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%);
}
}
}
}
.cover {
position: relative;
box-sizing: border-box;
padding: 66.66% 8px 0;
overflow: hidden;
img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
.desc {
margin: 10px 0 5px;
font-size: 12px;
font-weight: 700;
/* color: #39576a; */
color: ${(props) => props.verifycolor};
}
.name {
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
}
.price {
margin: 8px 0;
}
.bottom {
display: flex;
align-items: flex-start;
font-size: 12px;
font-weight: 600;
color: ${(props) => props.theme.text.primary};
.count {
margin: 0 2px 0 4px;
}
.MuiRating-decimal {
margin-right: -3px;
}
}
`;
效果:点击箭头可切换图片
指示器封装
先看效果:点击箭头,指示器规律滚动
import PropTypes from "prop-types";
import React, { memo, useEffect, useRef } from "react";
import { IndicatorWrapper } from "./style";
const Indicator = memo((props) => {
const { selectIndex = 0 } = 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;
// 3.获取selectItemEl要滚动的距离
let distance = itemLeft + itemWidth * 0.5 - contentWidth * 0.5;
// 4.特殊情况的处理
if (distance < 0) distance = 0; //左边的特殊情况处理
const totalDistance = contentScroll - contentWidth;
if (distance > totalDistance) distance = totalDistance; //右边的特殊情况处理
// 5.改变位置
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;
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;
}
}
`;
组件中使用:
import PropTypes from "prop-types";
import React, { memo, useRef, useState } from "react";
import { ItemWrapper } from "./style";
import Rating from "@mui/material/Rating";
import { Carousel } from "antd";
import IconArrowLeft from "@/assets/svg/icon_arrow_left";
import IconArrow from "@/assets/svg/icon_arrow";
import Indicator from "@/base-ui/indicator";
import classNames from "classnames";
const RoomItem = memo((props) => {
const { itemData, itemWidth = "25%" } = props;
const [selectIndex, setSelectIndex] = useState(0);
const swiperRef = useRef(); //Carousel 组件实例
// 事件处理逻辑
function controlClickHandle(isRight = true) {
// 上一个面板/下一个面板
isRight ? swiperRef.current.next() : swiperRef.current.prev();
// 最新的索引
let newIndex = isRight ? selectIndex + 1 : selectIndex - 1;
const length = itemData.picture_urls.length;
if (newIndex < 0) newIndex = length - 1;
if (newIndex > length - 1) newIndex = 0;
setSelectIndex(newIndex);
}
const pictureElement = (
<div className="cover">
<img src={itemData.picture_url} alt="" />
</div>
);
const swiperElement = (
<div className="swiper">
<div className="control">
<div className="btn left" onClick={(e) => controlClickHandle(false)}>
<IconArrowLeft width="30" height="30" />
</div>
<div className="btn right" onClick={(e) => controlClickHandle(true)}>
<IconArrow width="30" height="30" />
</div>
</div>
{/* 指示器 */}
<div className="indictor">
<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} autoplay ref={swiperRef}>
{itemData?.picture_urls?.map((item) => {
return (
<div className="cover" key={item}>
<img src={item} alt="" />
</div>
);
})}
</Carousel>
</div>
);
return (
<ItemWrapper itemWidth={itemWidth} verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
<div className="inner">
{!itemData.picture_urls ? pictureElement : swiperElement}
<div className="desc">{itemData.verify_info.messages.join("·")}</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
<div className="bottom">
<Rating
value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{ fontSize: "12px", color: "#00848A" }}
/>
<span className="count">{itemData.reviews_count}</span>
{itemData?.bottom_info && <span className="extra">·{itemData?.bottom_info?.content}</span>}
</div>
</div>
</ItemWrapper>
);
});
RoomItem.propTypes = {
itemData: PropTypes.object,
};
export default RoomItem;
.indictor {
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: purple;
}
}
}
}
item点击跳转到详情
如果在子组件中绑定点击事件,在home页,也会跳转,所以我们可以传递一个函数(使用useCallback()包裹,进行性能优化)到子组件中,让父组件控制点击子组件跳转不跳转,子组件可以传递参数到父组件。
子组件:
function itemClickHandle(){
if(itemClick) itemClick(itemData)
}
<ItemWrapper itemWidth={itemWidth} verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
<div className="inner" onClick={itemClickHandle}>
{!itemData.picture_urls ? pictureElement : swiperElement}
<div className="desc">{itemData.verify_info.messages.join("·")}</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
<div className="bottom">
<Rating
value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{ fontSize: "12px", color: "#00848A" }}
/>
<span className="count">{itemData.reviews_count}</span>
{itemData?.bottom_info && <span className="extra">·{itemData?.bottom_info?.content}</span>}
</div>
</div>
</ItemWrapper>
子组件全部代码
import PropTypes from "prop-types";
import React, { memo, useRef, useState } from "react";
import { ItemWrapper } from "./style";
import Rating from "@mui/material/Rating";
import { Carousel } from "antd";
import IconArrowLeft from "@/assets/svg/icon_arrow_left";
import IconArrow from "@/assets/svg/icon_arrow";
import Indicator from "@/base-ui/indicator";
import classNames from "classnames";
const RoomItem = memo((props) => {
const { itemData, itemWidth = "25%",itemClick } = props;
const [selectIndex, setSelectIndex] = useState(0);
const swiperRef = useRef(); //Carousel 组件实例
// 事件处理逻辑
function controlClickHandle(isRight = true) {
// 上一个面板/下一个面板
isRight ? swiperRef.current.next() : swiperRef.current.prev();
// 最新的索引
let newIndex = isRight ? selectIndex + 1 : selectIndex - 1;
const length = itemData.picture_urls.length;
if (newIndex < 0) newIndex = length - 1;
if (newIndex > length - 1) newIndex = 0;
setSelectIndex(newIndex);
}
function itemClickHandle(){
if(itemClick) itemClick(itemData)
}
const pictureElement = (
<div className="cover">
<img src={itemData.picture_url} alt="" />
</div>
);
const swiperElement = (
<div className="swiper">
<div className="control">
<div className="btn left" onClick={(e) => controlClickHandle(false)}>
<IconArrowLeft width="30" height="30" />
</div>
<div className="btn right" onClick={(e) => controlClickHandle(true)}>
<IconArrow width="30" height="30" />
</div>
</div>
{/* 指示器 */}
<div className="indictor">
<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} autoplay ref={swiperRef}>
{itemData?.picture_urls?.map((item) => {
return (
<div className="cover" key={item}>
<img src={item} alt="" />
</div>
);
})}
</Carousel>
</div>
);
return (
<ItemWrapper itemWidth={itemWidth} verifycolor={itemData?.verify_info?.text_color || "#39576a"}>
<div className="inner" onClick={itemClickHandle}>
{!itemData.picture_urls ? pictureElement : swiperElement}
<div className="desc">{itemData.verify_info.messages.join("·")}</div>
<div className="name">{itemData.name}</div>
<div className="price">¥{itemData.price}/晚</div>
<div className="bottom">
<Rating
value={itemData.star_rating ?? 5}
precision={0.1}
readOnly
sx={{ fontSize: "12px", color: "#00848A" }}
/>
<span className="count">{itemData.reviews_count}</span>
{itemData?.bottom_info && <span className="extra">·{itemData?.bottom_info?.content}</span>}
</div>
</div>
</ItemWrapper>
);
});
RoomItem.propTypes = {
itemData: PropTypes.object,
};
export default RoomItem;
父组件:
import React, { memo, useCallback } from "react";
import { RoomsWrapper } from "./style";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import RoomItem from "@/components/room-item";
import { useNavigate } from "react-router-dom";
import { changeDetailInfoAction } from "@/store/modules/detail";
const EntireRooms = memo((props) => {
// 从redux中获取数据
const { roomList, totalCount, isLoading } = useSelector(
(state) => ({
roomList: state.entire.roomList,
totalCount: state.entire.totalCount,
isLoading: state.entire.isLoading,
}),
shallowEqual
);
const navigate = useNavigate();
const dispatch = useDispatch();
// 事件处理
const itemClickHandle = useCallback(
(itemData) => {
console.log("123=> ", itemData);
dispatch(changeDetailInfoAction(itemData));
navigate("/detail");
},
[navigate, dispatch]
);
return (
<RoomsWrapper>
<div className="title">{totalCount}多处住所</div>
<div className="list">
{roomList?.map((item) => {
return <RoomItem itemClick={itemClickHandle} itemData={item} itemWidth="20%" key={item._id} />;
})}
</div>
{/* 蒙层 */}
{isLoading && <div className="cover"></div>}
</RoomsWrapper>
);
});
export default EntireRooms;
父组件中派发action(将itemData存储到redux中,并跳转到详情页)
changeDetailInfoAction:
import { createSlice } from "@reduxjs/toolkit";
const detailSlice = createSlice({
name: "detail",
initialState: {
detailInfo: {},
},
reducers: {
changeDetailInfoAction(state, { payload }) {
state.detailInfo = payload;
},
},
});
export const { changeDetailInfoAction } = detailSlice.actions;
export default detailSlice.reducer;
详情页获取数据展示:
import React, { memo } from "react";
import { useSelector } from "react-redux";
const Detail = memo(() => {
const { detailInfo } = useSelector((state) => ({
detailInfo: state.detail.detailInfo,
}));
return <div>{detailInfo.name}</div>;
});
export default Detail;
detail页效果:
至此,项目告一段落了。