diff --git a/src/components/InfiniteScroll/index.css b/src/components/InfiniteScroll/index.css new file mode 100644 index 0000000..2a2a57c --- /dev/null +++ b/src/components/InfiniteScroll/index.css @@ -0,0 +1,20 @@ +.infinite-scroll-container { + height: 100%; + width: 100%; +} + +.loading-indicator { + display: flex; + justify-content: center; + align-items: center; + padding: 16px; + color: #999; +} + +.no-more-data { + display: flex; + justify-content: center; + align-items: center; + padding: 16px; + color: #999; +} \ No newline at end of file diff --git a/src/components/InfiniteScroll/index.jsx b/src/components/InfiniteScroll/index.jsx new file mode 100644 index 0000000..6bd7e1b --- /dev/null +++ b/src/components/InfiniteScroll/index.jsx @@ -0,0 +1,57 @@ +import { useEffect, useRef, useState } from "react"; +import { Empty } from "@arco-design/web-react"; +import { useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +import { setLoadingTrue, setLoadingFalse } from "@/store/slices/loadingSlice"; + +const InfiniteScroll = ({ + loadMore, + hasMore, + children, + threshold = 50, + className = "", +}) => { + const dispatch = useDispatch(); + const containerRef = useRef(null); + const loading = useSelector((state) => state.loading.value); + + useEffect(() => { + const handleScroll = () => { + if (!containerRef.current || loading || !hasMore) return; + + const { scrollTop, scrollHeight, clientHeight } = containerRef.current; + + if (scrollTop + clientHeight >= scrollHeight - threshold) { + dispatch(setLoadingTrue()); + loadMore().finally(() => { + dispatch(setLoadingFalse()); + }); + } + }; + + const container = containerRef.current; + if (container) { + container.addEventListener("scroll", handleScroll); + // 初始加载时检查是否需要加载更多 + handleScroll(); + } + + return () => { + if (container) { + container.removeEventListener("scroll", handleScroll); + } + }; + }, [loadMore, hasMore, threshold, loading]); + + return ( +
+ {children} + {!hasMore && !loading && } +
+ ); +}; + +export default InfiniteScroll; diff --git a/src/components/Layout/index.jsx b/src/components/Layout/index.jsx index 98952e6..dcac0e0 100644 --- a/src/components/Layout/index.jsx +++ b/src/components/Layout/index.jsx @@ -11,7 +11,7 @@ const Layout = ({ children }) => { return (
- +
{children}
diff --git a/src/pages/Dashboard/index.jsx b/src/pages/Dashboard/index.jsx index 7f7fafe..b2ee272 100644 --- a/src/pages/Dashboard/index.jsx +++ b/src/pages/Dashboard/index.jsx @@ -1,3 +1,4 @@ +import { useState, useEffect } from "react"; import StartClass from "./components/StartClass"; import QuickAccess from "./components/QuickAccess"; import CalendarTaskModule from "./components/CalendarTaskModule"; @@ -8,6 +9,8 @@ import TaskList from "./components/TaskList"; import "./index.css"; const Dashboard = () => { + const [data, setData] = useState({}); + return (
diff --git a/src/pages/ProjectLibraryPage/index.css b/src/pages/ProjectLibraryPage/index.css index 74bda22..fccfe5b 100644 --- a/src/pages/ProjectLibraryPage/index.css +++ b/src/pages/ProjectLibraryPage/index.css @@ -13,6 +13,8 @@ box-sizing: border-box; padding: 20px; overflow: hidden; + display: flex; + flex-direction: column; .user-portfolio-search-area { width: 100%; @@ -31,9 +33,7 @@ } } } - .user-portfolio-list { - width: 100%; overflow-y: auto; box-sizing: border-box; display: flex; @@ -63,7 +63,8 @@ align-items: flex-start; flex-direction: column; - > span { + .user-portfolio-item-title { + width: 100%; border: 1px solid #2c7aff; background-color: #e8f3ff; height: 20px; @@ -73,7 +74,11 @@ color: #2c7aff; font-size: 12px; font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + > div { width: 100%; height: 24px; diff --git a/src/pages/ProjectLibraryPage/index.jsx b/src/pages/ProjectLibraryPage/index.jsx index 92f087d..f300c73 100644 --- a/src/pages/ProjectLibraryPage/index.jsx +++ b/src/pages/ProjectLibraryPage/index.jsx @@ -1,21 +1,29 @@ -import { useState } from "react"; -import { Input } from "@arco-design/web-react"; -import { mockData } from "@/data/mockData"; +import { useState, useEffect } from "react"; +import { Input, Empty } from "@arco-design/web-react"; +import InfiniteScroll from "@/components/InfiniteScroll"; import ProjectCasesModal from "./components/ProjectCasesModal"; +import { getProjectsList } from "@/services/projectLibrary"; import "./index.css"; const InputSearch = Input.Search; -const { projectLibrary } = mockData; +const PAGE_SIZE = 10; const ProjectLibrary = () => { const [modalData, setModalData] = useState(undefined); + const [projectList, setProjectList] = useState([]); const [projectCasesModalVisible, setProjectCasesModalVisible] = useState(false); + const [page, setPage] = useState(1); + const [hasMore, setHasMore] = useState(true); const onSearch = (value) => { - console.log(value); + setProjectList([]); + setHasMore(true); + setPage(1); + fetchProjects(value, 1); }; + const handleProjectClick = (item) => { setModalData(item); setProjectCasesModalVisible(true); @@ -26,6 +34,24 @@ const ProjectLibrary = () => { setModalData(undefined); }; + const fetchProjects = async (searchValue = "", pageNum) => { + try { + // 这里使用真实API替换模拟数据 + const response = await getProjectsList({ + search: searchValue, + page: pageNum ?? page, + pageSize: PAGE_SIZE, + }); + if (response.success) { + setProjectList((prevList) => [...prevList, ...response.data]); + setHasMore(response?.data.length === PAGE_SIZE); + setPage((prevPage) => prevPage + 1); + } + } catch (error) { + console.error("Failed to fetch projects:", error); + } + }; + return (
@@ -36,17 +62,22 @@ const ProjectLibrary = () => { searchButton="搜索" />
-
    - {projectLibrary?.projects?.map((item) => ( + + {projectList.map((item) => (
  • - {item.subtitle} +

    {item.description}

    -

    {item.title}

    - handleProjectClick(item)}>详情 > +

    {item.name}

    + handleProjectClick(item)}>详情 >
  • ))} -
+