react+ts 移动端页面分页,触底加载下一页

react ts 移动端页面分页,触底加载下一页

index.tsx

c 复制代码
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch, useLocation } from "dva";
import styles from './index.less';

const List = () => {
    const location = useLocation();
    // 解析查询参数(需手动处理)
    const searchParams = new URLSearchParams(location.search);
    const dispatch = useDispatch();
    const [loading, setLoading] = useState(false); // 加载状态
    const [finished, setFinished] = useState(false); // 是否加载完成
    const [page, setPage] = useState(1); // 当前页码
    const [pageSize, setPageSize] = useState(10); // 每页显示条数
    const [data, setData] = useState([]); // 当前显示的所有数据
    const [screenHeight, setScreenHeight] = useState(window.innerHeight); // 当前屏幕高度

    // 获取屏幕高度并监听窗口大小变化
    useEffect(() => {
        const handleResize = () => {
            setScreenHeight(window.innerHeight);
        };

        window.addEventListener("resize", handleResize);
        return () => window.removeEventListener("resize", handleResize);
    }, []);

    // 请求接口数据
    const getList = async (page) => {
        if (loading || finished) return;

        setLoading(true);
        try {
            dispatch({
                type: "",
                payload: {
                    url:
                        `你的接口`,
                    type: "POST",
                    body: {
                        page: page,
                        rows: pageSize
                    },
                    header: { "你的请求头参数" },
                },
                callback: res => {
                    if (res.result === "1" && res.rows.length > 0) {
                        setData((prevData) => [...prevData, ...res.rows]); // 追加新数据
                        if (res.rows.length < pageSize) {
                            setFinished(true); // 如果返回数据不足一页,说明没有更多数据
                        }
                    } else {
                        setFinished(true); // 接口无数据时停止加载
                    }
                },
            });

        } catch (error) {
            console.error("数据加载失败:", error);
        } finally {
            setLoading(false);
        }
    };

    // 初始化加载第一页数据
    useEffect(() => {
        getList(1);
    }, []);

    // 监听滚动事件
    useEffect(() => {
        const container = document.querySelector(`.${styles.cardListContainer}`);
        if (!container) return;

        const handleScroll = () => {
            const { scrollTop, clientHeight, scrollHeight } = container;
            if (scrollTop + clientHeight >= scrollHeight - 10 && !loading && !finished) {
                setPage((prevPage) => prevPage + 1); // 触底时分页加 1
            }
        };

        container.addEventListener("scroll", handleScroll);
        return () => container.removeEventListener("scroll", handleScroll);
    }, [page, loading, finished]);

    // 当分页变化时加载数据
    useEffect(() => {
        if (page > 1) {
            getList(page); // 加载对应页的数据
        }
    }, [page]);

    return (
        <div
            className={styles.cardListContainer}
            style={{ height: screenHeight, overflowY: "auto" }} // 动态设置高度
        >
            {/* 卡片列表 */}
            {data.map((item) => (
                <div key={item.id} className={styles.card}>
                    <div className={styles.cardContent}>
                        <div>
                            <span className={styles.phone}>{item.phone}</span>
                        </div>
                        <div>
                            <span className={styles.remark}>   {item.remark || "无备注"}</span>
                        </div>
                    </div>
                </div>
            ))}

            {/* 加载提示 */}
            {loading && <div className={styles.loading}>加载中...</div>}

            {/* 已加载全部数据提示 */}
            {finished && <div className={styles.finished}>已显示全部数据</div>}
        </div>
    );
};

export default List;

index.less

c 复制代码
/* 卡片列表容器 */
.cardListContainer {
    width: 100%;
    margin: 0 auto;
    padding: 0.2rem;
    overflow-y: auto;
    /* 支持内部滚动 */
    background: #FFF;
    padding-top: 0.85rem; // 留出一部分距离给导航栏
}

/* WebKit 内核浏览器补充 */
.cardListContainer::-webkit-scrollbar {
    display: none;
}

/* 卡片样式 */
.card {
    width: 100%;
    padding: 0.2rem;
    margin-bottom: 0.12rem;
    display: flex;
    flex-direction: column;
    border-radius: 0.06rem;
    background: rgba(0, 0, 0, 0.03);
}

.cardContent {
    display: flex;
    justify-content: space-between;
    align-items: center;

    div {
        display: inline-grid;
    }
    .phone {
        color: #101010;
        font-size: 0.15rem;
        font-style: normal;
        font-weight: 300;
        line-height: 0.24rem;
    }

    .remark {
        color: rgba(29, 29, 30, 0.70);
        font-size: 0.12rem;
        font-style: normal;
        font-weight: 300;
        line-height: 0.18rem;
    }
}

/* 加载提示 */
.loading {
    text-align: center;
    font-size: 0.12rem;
    color: rgba(29, 29, 30, 0.20);
    padding: 0.1rem;
}

/* 已加载全部数据提示 */
.finished {
    text-align: center;
    font-size: 0.12rem;
    color: rgba(29, 29, 30, 0.20);
    padding: 0.1rem;
}