diff --git a/app/api/news/route.ts b/app/api/news/route.ts index 469d3bd..82cd736 100644 --- a/app/api/news/route.ts +++ b/app/api/news/route.ts @@ -3,11 +3,24 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { + // Extract query parameters for pagination + const url = new URL(request.url); + const _pageNum = url.searchParams.get("pageNum"); + const _pageSize = url.searchParams.get("pageSize"); + if (!_pageNum || !_pageSize) { + throw new Error("pageNum and pageSize required"); + } + const pageNum = parseInt(_pageNum, 10); + const pageSize = parseInt(_pageSize, 10); + const offset = pageNum * pageSize; + const { rows } = await sql` - SELECT users.username, news.id, users."avatarId", news.content, news.pictures, News."createdTime" AT TIME ZONE 'UTC' AS "createdTime" - FROM news - JOIN users ON News."ownerId" = users.Id - ORDER BY news."createdTime" DESC + SELECT users.username, news.id, users."avatarId", news.content, news.pictures, news."createdTime" AT TIME ZONE 'UTC' AS "createdTime", news.longitude, news.latitude + FROM news + JOIN users ON News."ownerId" = users.Id + ORDER BY news."createdTime" DESC + LIMIT ${pageSize} + OFFSET ${offset} `; if (rows.length > 0) { return new NextResponse( diff --git a/app/home/Cobe/index.tsx b/app/home/Cobe/index.tsx index 056cfe4..18f8af1 100644 --- a/app/home/Cobe/index.tsx +++ b/app/home/Cobe/index.tsx @@ -8,6 +8,7 @@ import { observer } from "mobx-react-lite"; export const Cobe = observer(() => { const { news, location } = useStores(); + const newsList = news.newsList; const focusRef = useRef([-1, -1]); const canvasRef = useRef(null); const pointerInteracting = useRef<{ x: number; y: number } | null>(null); @@ -43,6 +44,14 @@ export const Cobe = observer(() => { canvasRef.current && (width = canvasRef.current.offsetWidth); window.addEventListener("resize", onResize); onResize(); + console.log( + newsList.map((newsItem) => { + return { + location: [newsItem.longitude, newsItem.latitude], + size: 0.1, + }; + }) + ); const globe = createGlobe(canvasRef.current, { devicePixelRatio: 2, width: width * 2, @@ -56,10 +65,12 @@ export const Cobe = observer(() => { baseColor: [1, 1, 1], markerColor: [251 / 255, 100 / 255, 21 / 255], glowColor: [1.2, 1.2, 1.2], - markers: [ - { location: [37.7595, -122.4367], size: 0.03 }, - { location: [40.7128, -74.006], size: 0.1 }, - ], + markers: newsList.map((newsItem) => { + return { + location: [newsItem.latitude, newsItem.longitude], + size: 0.1, + }; + }), onRender: (state) => { const isAutoRotation = focusRef.current[0] === -1 && focusRef.current[1] === -1; @@ -107,7 +118,8 @@ export const Cobe = observer(() => { window.removeEventListener("resize", onResize); }; } - }, []); + }, [newsList]); + useEffect(() => { if (news.isPosting) { const { longitude, latitude } = location; @@ -116,6 +128,7 @@ export const Cobe = observer(() => { focusRef.current = [-1, -1]; } }, [news.isPosting]); + return (
{ const { news } = useStores(); + const [pageNum, setPageNum] = useState(0); + const hasLoadedFirstPageDataRef = useRef(false); + const newsList = news.newsList; - useEffect(() => { + const loadMore = useCallback(() => { (async () => { - const res = await fetch("/api/news", { - cache: "no-store", - method: "GET", - }); - const { result } = await res.json(); - if (result && result.length) { - news.updateNewsList(result); - } + try { + const res = await fetch(`/api/news?pageNum=${pageNum}&pageSize=10`, { + cache: "no-store", + method: "GET", + }); + const { result } = await res.json(); + if (result && result.length) { + news.updateNewsList(result); + } + } catch (error) {} })(); - }, []); + }, [pageNum]); + + useEffect(() => { + if (pageNum === 0) { + // 默认reactStrictMode为true,React会执行两次,这里用ref处理一下首页数据 + if (hasLoadedFirstPageDataRef.current === false) { + loadMore(); + hasLoadedFirstPageDataRef.current = true; + } + } else { + loadMore(); + } + }, [pageNum]); return ( { 带着Mortal看世界 - {news.newsList.length === 0 ? ( + {newsList.length === 0 ? ( ) : ( - - {news.newsList.map((news) => { - return ; - })} + + { + setPageNum(pageNum + 1); + }} + itemContent={(index) => { + const newsItem = newsList[index]; + return ( + + ); + }} + /> )} diff --git a/app/home/NewsCard/index.tsx b/app/home/NewsCard/index.tsx index f293b23..b34a1b1 100644 --- a/app/home/NewsCard/index.tsx +++ b/app/home/NewsCard/index.tsx @@ -17,10 +17,18 @@ import timezone from "dayjs/plugin/timezone"; dayjs.extend(utc); dayjs.extend(timezone); -export function NewsCard(props: NewsData) { +interface NewsCardProps extends NewsData { + isLast?: boolean; +} + +export const NewsCard = React.memo(function NewsCard(props: NewsCardProps) { + const hasPicture = props.pictures.length > 0; return ( - - + +

{props.username}

@@ -29,18 +37,23 @@ export function NewsCard(props: NewsData) {

- -

{props.content}

+ +

+ {props.content} +

{props.pictures[0] && ( - +
+ +
)}
- + {!props.isLast && }
); -} +}); diff --git a/app/stores/NewsStore.ts b/app/stores/NewsStore.ts index 4ad879a..7e2c512 100644 --- a/app/stores/NewsStore.ts +++ b/app/stores/NewsStore.ts @@ -15,8 +15,7 @@ export class NewsStore { makeAutoObservable(this); } updateNewsList = (list: NewsData[]) => { - // TODO: 多页 - this.newsList = [...list]; + this.newsList = this.newsList.concat(list); }; updateDraft = (draft: Partial) => { this.draft = { diff --git a/app/types.ts b/app/types.ts index d133096..48814d2 100644 --- a/app/types.ts +++ b/app/types.ts @@ -18,6 +18,8 @@ export interface NewsData { content: string; pictures: string[]; createdTime: number; + longitude: number; + latitude: number; } export interface DraftData { diff --git a/package-lock.json b/package-lock.json index 81403a6..7fb2fb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,8 @@ "radash": "^12.1.0", "react": "^18", "react-dom": "^18", - "react-spring": "^9.7.3" + "react-spring": "^9.7.3", + "react-virtuoso": "^4.7.11" }, "devDependencies": { "@release-it/conventional-changelog": "^5.0.0", @@ -16398,6 +16399,18 @@ "react-dom": ">=16.13" } }, + "node_modules/react-virtuoso": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.7.11.tgz", + "integrity": "sha512-Kdn9qEtQI2ulEuBMzW2BTkDsfijB05QUd6lpZ1K36oyA3k65Cz4lG4EKrh2pCfUafX4C2uMSZOwzMOhbrMOTFA==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16 || >=17 || >= 18", + "react-dom": ">=16 || >=17 || >= 18" + } + }, "node_modules/react-zdog": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/react-zdog/-/react-zdog-1.2.2.tgz", diff --git a/package.json b/package.json index 983893c..3af5bbb 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "radash": "^12.1.0", "react": "^18", "react-dom": "^18", - "react-spring": "^9.7.3" + "react-spring": "^9.7.3", + "react-virtuoso": "^4.7.11" }, "devDependencies": { "@release-it/conventional-changelog": "^5.0.0",