diff --git a/package.json b/package.json index 9dc19f9..7745719 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,9 @@ "rc-util": "^5.38.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-fittext": "^1.0.0" + "react-fittext": "^1.0.0", + "react-virtualized-auto-sizer": "^1.0.26", + "react-window": "^1.8.11" }, "devDependencies": { "@ant-design/pro-cli": "^2.1.5", diff --git a/src/components/Hooks/useFetchImageUrl.ts b/src/components/Hooks/useFetchImageUrl.ts index 4f7e8d3..9d5a8c9 100644 --- a/src/components/Hooks/useFetchImageUrl.ts +++ b/src/components/Hooks/useFetchImageUrl.ts @@ -11,7 +11,6 @@ const useFetchImageUrl = (imageInstanceId: string) => { { manual: true, onSuccess: (data) => { - console.log(data); }, }, ); diff --git a/src/pages/story/data.d.ts b/src/pages/story/data.d.ts index a253f82..3194fe2 100644 --- a/src/pages/story/data.d.ts +++ b/src/pages/story/data.d.ts @@ -35,6 +35,9 @@ export interface StoryType { status?: string; createTime?: string; ownerId?: string; + ownerName?: string; + updateName?: string; + itemCount: number, updatedId?: string; updateTime?: string; logo?: string; diff --git a/src/pages/story/detail.tsx b/src/pages/story/detail.tsx index 7c17e4b..9efb61b 100644 --- a/src/pages/story/detail.tsx +++ b/src/pages/story/detail.tsx @@ -1,18 +1,20 @@ +// src/pages/story/detail.tsx import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal'; import TimelineItem from '@/pages/story/components/TimelineItem/TimelineItem'; import { StoryItem } from '@/pages/story/data'; -import { countStoryItem, queryStoryDetail, queryStoryItem } from '@/pages/story/service'; +import { queryStoryDetail, queryStoryItem } from '@/pages/story/service'; import { PageContainer } from '@ant-design/pro-components'; import { history, useIntl, useRequest } from '@umijs/max'; -import { FloatButton, Spin, Timeline } from 'antd'; -import React, { useEffect, useRef, useState } from 'react'; +import { FloatButton, Spin } from 'antd'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList as List } from 'react-window'; import './index.css'; import useStyles from './style.style'; interface TimelineItemProps { - children: React.ReactNode; // 修正:使用 ReactNode 更通用 - // label: string + children: React.ReactNode; } const Index = () => { @@ -20,23 +22,25 @@ const Index = () => { const { styles } = useStyles(); const intl = useIntl(); const containerRef = useRef(null); - const [items, setItems] = useState([]); - + const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); - const [hasMoreNew, setHasMoreNew] = useState(true); - const [hasMoreOld, setHasMoreOld] = useState(true); + const [hasMore, setHasMore] = useState(true); const [openAddItemModal, setOpenAddItemModal] = useState(false); const [currentItem, setCurrentItem] = useState(); - const [currentOption, setCurrentOption] = useState<'add' | 'edit' | 'addSubItem' | 'editSubItem'>(); + const [currentOption, setCurrentOption] = useState< + 'add' | 'edit' | 'addSubItem' | 'editSubItem' + >(); + const [pagination, setPagination] = useState({ current: 1, pageSize: 10 }); - const { data: storyItemList, run } = useRequest( + const { data: response, run } = useRequest( () => { - return queryStoryItem(lineId ?? ''); + return queryStoryItem({ storyInstanceId: lineId, ...pagination }); }, { manual: true, }, ); + const { data: detail, run: queryDetail, @@ -44,56 +48,153 @@ const Index = () => { } = useRequest(() => { return queryStoryDetail(lineId ?? ''); }); - const { data: count, run: queryCount } = useRequest(() => { - return countStoryItem(lineId ?? ''); - }); + // 初始化加载数据 useEffect(() => { + setItems([]); + setPagination({ current: 1, pageSize: 10 }); + setHasMore(true); run(); }, [lineId]); - useEffect(() => { - if (!storyItemList?.length) return; - console.log(storyItemList); - let timelineItems = storyItemList; //handleStoryItemList(storyItemList); - // 转换为 Timeline 组件需要的格式 - const formattedItems = timelineItems.map((item: StoryItem) => ({ - children: ( - { - setCurrentItem(item); - setCurrentOption(option) - setOpenAddItemModal(true); - }} - refresh={() => { - run(); - queryCount(); - queryDetail(); - }} - /> - ), - })); - setItems(formattedItems); - }, [storyItemList]); + // 处理响应数据 + useEffect(() => { + if (!response) return; + + if (pagination.current === 1) { + // 首页数据 + setItems(response.list || []); + } else { + // 追加数据 + setItems(prev => [...prev, ...(response.list || [])]); + } + + // 检查是否还有更多数据 + setHasMore(response.list && response.list.length === pagination.pageSize); + setLoading(false); + }, [response, pagination]); + + // 滚动到底部加载更多 + const loadMore = useCallback(() => { + if (loading || !hasMore) return; + + setLoading(true); + setPagination(prev => ({ + ...prev, + current: prev.current + 1 + })); + }, [loading, hasMore]); + + // 当分页变化时重新请求数据 + useEffect(() => { + if (pagination.current > 1) { + console.log('分页变化') + run(); + } + }, [pagination, run]); + + // 渲染单个时间线项的函数 + const renderTimelineItem = useCallback( + ({ index, style }: { index: number; style: React.CSSProperties }) => { + const item = items[index]; + if (!item) return null; + + return ( +
+ { + setCurrentItem(item); + setCurrentOption(option); + setOpenAddItemModal(true); + }} + refresh={() => { + // 刷新当前页数据 + setPagination(prev => ({ ...prev, current: 1 })); + run(); + queryDetail(); + }} + /> +
+ ); + }, + [items, run, queryDetail], + ); + + // 处理滚动事件 + const handleScroll = useCallback((e: React.UIEvent) => { + const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; + // 当滚动到底部时加载更多 + if (scrollTop + clientHeight >= scrollHeight - 10) { + loadMore(); + } + }, [loadMore]); return ( history.push('/story')} - title={queryDetailLoading ? '加载中' : `${detail?.title} ${count ? `共${count}个时刻` : ``}`} + title={ + queryDetailLoading ? '加载中' : `${detail?.title} ${`共${detail?.itemCount ?? 0}个时刻`}` + } > -
- {!hasMoreOld &&
没有更老的内容
} - {loading && } - - {loading && } - {!hasMoreNew &&
没有更新的内容
} +
+ {items.length > 0 ? ( + + {({ height, width }) => ( +
+ + {renderTimelineItem} + + + {/* 加载更多指示器 */} + {loading && ( +
+ +
+ )} + + {!hasMore && items.length > 0 && ( +
+ 没有更多内容了 +
+ )} +
+ )} +
+ ) : ( +
+ {loading ? : '暂无时间线数据'} +
+ )}
- { - setCurrentOption('add'); - setCurrentItem(); - setOpenAddItemModal(true); - }} /> + + { + setCurrentOption('add'); + setCurrentItem(); + setOpenAddItemModal(true); + }} + /> + { }} onOk={() => { setOpenAddItemModal(false); + // 添加新项后刷新数据 + setPagination(prev => ({ ...prev, current: 1 })); run(); + queryDetail() }} storyId={lineId} /> diff --git a/src/pages/story/index.css b/src/pages/story/index.css index 7f9b36b..148b9fb 100644 --- a/src/pages/story/index.css +++ b/src/pages/story/index.css @@ -138,3 +138,11 @@ color: #888; font-style: italic; } +.timeline-hide-scrollbar::-webkit-scrollbar { + display: none; /* Chrome Safari */ +} + +.timeline-hide-scrollbar { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ +} diff --git a/src/pages/story/index.tsx b/src/pages/story/index.tsx index d52c8ac..7f7ba75 100644 --- a/src/pages/story/index.tsx +++ b/src/pages/story/index.tsx @@ -1,7 +1,7 @@ import { DownOutlined, PlusOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { history, useRequest } from '@umijs/max'; -import { Avatar, Button, Card, Dropdown, Input, List, Modal, Radio } from 'antd'; +import { Avatar, Button, Card, Dropdown, Input, List, Modal } from 'antd'; import type { FC } from 'react'; import React, { useState } from 'react'; import OperationModal from './components/OperationModal'; @@ -9,12 +9,11 @@ import type { StoryType } from './data.d'; import { addStory, deleteStory, queryTimelineList, updateStory } from './service'; import useStyles from './style.style'; -/*const RadioButton = Radio.Button; -const RadioGroup = Radio.Group;*/ + const { Search } = Input; const ListContent = ({ - data: { ownerId, updatedId, createTime, updateTime, status }, + data: { createTime, updateTime, updateName, ownerName, itemCount }, }: { data: StoryType; }) => { @@ -23,15 +22,15 @@ const ListContent = ({
创建人 -

{ownerId}

+

{ownerName}

最近更新人 -

{updatedId}

+

{updateName}

节点数 -

{111}

+

{itemCount}

开始时间 @@ -107,11 +106,19 @@ export const BasicList: FC = () => { }; const extraContent = (
- {/* - 全部 - 进行中 - 等待中 - */} + {
- ; + pageSize?: number; + current?: number; +} & Partial & Partial; export async function queryTimelineList( params: ParamsType, @@ -55,12 +57,10 @@ export async function addStoryItem(params: FormData): Promise { }); } -export async function queryStoryItem(storyInstanceId: string): Promise<{ data: StoryItem[] }> { +export async function queryStoryItem(params: ParamsType): Promise<{ data: CommonListResponse }> { return request(`/story/item/list`, { method: 'GET', - params: { - storyInstanceId, - }, + params: params, }); } diff --git a/src/types/common.d.ts b/src/types/common.d.ts index 56bd7db..5f0c9b4 100644 --- a/src/types/common.d.ts +++ b/src/types/common.d.ts @@ -4,9 +4,16 @@ export interface CommonResponse { data?: T; } export type CommonListResponse = { - list?: T[]; + list: T[]; total?: number; pageSize?: number; pageNumber?: number; pages?: number; } +export interface PageParams { + // query + /** 当前的页码 */ + current?: number; + /** 页面的容量 */ + pageSize?: number; +}