修改故事列表

This commit is contained in:
jiangh277 2025-08-07 19:48:36 +08:00
parent 182a58d0db
commit efd3f4a82c
8 changed files with 202 additions and 85 deletions

View File

@ -59,7 +59,9 @@
"rc-util": "^5.38.1", "rc-util": "^5.38.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^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": { "devDependencies": {
"@ant-design/pro-cli": "^2.1.5", "@ant-design/pro-cli": "^2.1.5",

View File

@ -11,7 +11,6 @@ const useFetchImageUrl = (imageInstanceId: string) => {
{ {
manual: true, manual: true,
onSuccess: (data) => { onSuccess: (data) => {
console.log(data);
}, },
}, },
); );

View File

@ -35,6 +35,9 @@ export interface StoryType {
status?: string; status?: string;
createTime?: string; createTime?: string;
ownerId?: string; ownerId?: string;
ownerName?: string;
updateName?: string;
itemCount: number,
updatedId?: string; updatedId?: string;
updateTime?: string; updateTime?: string;
logo?: string; logo?: string;

View File

@ -1,18 +1,20 @@
// src/pages/story/detail.tsx
import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal'; import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal';
import TimelineItem from '@/pages/story/components/TimelineItem/TimelineItem'; import TimelineItem from '@/pages/story/components/TimelineItem/TimelineItem';
import { StoryItem } from '@/pages/story/data'; 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 { PageContainer } from '@ant-design/pro-components';
import { history, useIntl, useRequest } from '@umijs/max'; import { history, useIntl, useRequest } from '@umijs/max';
import { FloatButton, Spin, Timeline } from 'antd'; import { FloatButton, Spin } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import AutoSizer from 'react-virtualized-auto-sizer';
import { FixedSizeList as List } from 'react-window';
import './index.css'; import './index.css';
import useStyles from './style.style'; import useStyles from './style.style';
interface TimelineItemProps { interface TimelineItemProps {
children: React.ReactNode; // 修正:使用 ReactNode 更通用 children: React.ReactNode;
// label: string
} }
const Index = () => { const Index = () => {
@ -20,23 +22,25 @@ const Index = () => {
const { styles } = useStyles(); const { styles } = useStyles();
const intl = useIntl(); const intl = useIntl();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [items, setItems] = useState<TimelineItemProps[]>([]); const [items, setItems] = useState<StoryItem[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [hasMoreNew, setHasMoreNew] = useState(true); const [hasMore, setHasMore] = useState(true);
const [hasMoreOld, setHasMoreOld] = useState(true);
const [openAddItemModal, setOpenAddItemModal] = useState(false); const [openAddItemModal, setOpenAddItemModal] = useState(false);
const [currentItem, setCurrentItem] = useState<StoryItem>(); const [currentItem, setCurrentItem] = useState<StoryItem>();
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, manual: true,
}, },
); );
const { const {
data: detail, data: detail,
run: queryDetail, run: queryDetail,
@ -44,56 +48,153 @@ const Index = () => {
} = useRequest(() => { } = useRequest(() => {
return queryStoryDetail(lineId ?? ''); return queryStoryDetail(lineId ?? '');
}); });
const { data: count, run: queryCount } = useRequest(() => {
return countStoryItem(lineId ?? '');
});
// 初始化加载数据 // 初始化加载数据
useEffect(() => { useEffect(() => {
setItems([]);
setPagination({ current: 1, pageSize: 10 });
setHasMore(true);
run(); run();
}, [lineId]); }, [lineId]);
useEffect(() => {
if (!storyItemList?.length) return;
console.log(storyItemList);
let timelineItems = storyItemList; //handleStoryItemList(storyItemList);
// 转换为 Timeline 组件需要的格式
const formattedItems = timelineItems.map((item: StoryItem) => ({
children: (
<TimelineItem
item={item}
handleOption={(item: StoryItem, option: 'add' | 'edit' | 'addSubItem' | 'editSubItem') => {
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 (
<div style={style}>
<TimelineItem
item={item}
handleOption={(
item: StoryItem,
option: 'add' | 'edit' | 'addSubItem' | 'editSubItem',
) => {
setCurrentItem(item);
setCurrentOption(option);
setOpenAddItemModal(true);
}}
refresh={() => {
// 刷新当前页数据
setPagination(prev => ({ ...prev, current: 1 }));
run();
queryDetail();
}}
/>
</div>
);
},
[items, run, queryDetail],
);
// 处理滚动事件
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
// 当滚动到底部时加载更多
if (scrollTop + clientHeight >= scrollHeight - 10) {
loadMore();
}
}, [loadMore]);
return ( return (
<PageContainer <PageContainer
onBack={() => history.push('/story')} onBack={() => history.push('/story')}
title={queryDetailLoading ? '加载中' : `${detail?.title} ${count ? `${count}个时刻` : ``}`} title={
queryDetailLoading ? '加载中' : `${detail?.title} ${`${detail?.itemCount ?? 0}个时刻`}`
}
> >
<div className="timeline" ref={containerRef}> <div
{!hasMoreOld && <div style={{ textAlign: 'center', color: '#999' }}></div>} className="timeline"
{loading && <Spin style={{ display: 'block', margin: '20px auto' }} />} ref={containerRef}
<Timeline items={items} mode={'left'} /> style={{
{loading && <Spin style={{ display: 'block', margin: '20px auto' }} />} height: 'calc(100vh - 200px)',
{!hasMoreNew && <div style={{ textAlign: 'center', color: '#999' }}></div>} overflowY: 'auto',
}}
>
{items.length > 0 ? (
<AutoSizer>
{({ height, width }) => (
<div
style={{ height, width, position: 'relative' }}
onScroll={handleScroll}
className="timeline-hide-scrollbar"
>
<List
height={height}
itemCount={items.length}
itemSize={300} // 根据实际项高度调整
width={width}
>
{renderTimelineItem}
</List>
{/* 加载更多指示器 */}
{loading && (
<div style={{ textAlign: 'center', padding: '20px' }}>
<Spin />
</div>
)}
{!hasMore && items.length > 0 && (
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
</div>
)}
</div>
)}
</AutoSizer>
) : (
<div style={{ textAlign: 'center', padding: '50px 0' }}>
{loading ? <Spin /> : '暂无时间线数据'}
</div>
)}
</div> </div>
<FloatButton onClick={() => {
setCurrentOption('add'); <FloatButton
setCurrentItem(); onClick={() => {
setOpenAddItemModal(true); setCurrentOption('add');
}} /> setCurrentItem();
setOpenAddItemModal(true);
}}
/>
<AddTimeLineItemModal <AddTimeLineItemModal
visible={openAddItemModal} visible={openAddItemModal}
initialValues={currentItem} initialValues={currentItem}
@ -103,7 +204,10 @@ const Index = () => {
}} }}
onOk={() => { onOk={() => {
setOpenAddItemModal(false); setOpenAddItemModal(false);
// 添加新项后刷新数据
setPagination(prev => ({ ...prev, current: 1 }));
run(); run();
queryDetail()
}} }}
storyId={lineId} storyId={lineId}
/> />

View File

@ -138,3 +138,11 @@
color: #888; color: #888;
font-style: italic; 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+ */
}

View File

@ -1,7 +1,7 @@
import { DownOutlined, PlusOutlined } from '@ant-design/icons'; import { DownOutlined, PlusOutlined } from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer } from '@ant-design/pro-components';
import { history, useRequest } from '@umijs/max'; 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 type { FC } from 'react';
import React, { useState } from 'react'; import React, { useState } from 'react';
import OperationModal from './components/OperationModal'; import OperationModal from './components/OperationModal';
@ -9,12 +9,11 @@ import type { StoryType } from './data.d';
import { addStory, deleteStory, queryTimelineList, updateStory } from './service'; import { addStory, deleteStory, queryTimelineList, updateStory } from './service';
import useStyles from './style.style'; import useStyles from './style.style';
/*const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;*/
const { Search } = Input; const { Search } = Input;
const ListContent = ({ const ListContent = ({
data: { ownerId, updatedId, createTime, updateTime, status }, data: { createTime, updateTime, updateName, ownerName, itemCount },
}: { }: {
data: StoryType; data: StoryType;
}) => { }) => {
@ -23,15 +22,15 @@ const ListContent = ({
<div> <div>
<div className={styles.listContentItem}> <div className={styles.listContentItem}>
<span></span> <span></span>
<p>{ownerId}</p> <p>{ownerName}</p>
</div> </div>
<div className={styles.listContentItem}> <div className={styles.listContentItem}>
<span></span> <span></span>
<p>{updatedId}</p> <p>{updateName}</p>
</div> </div>
<div className={styles.listContentItem}> <div className={styles.listContentItem}>
<span></span> <span></span>
<p>{111}</p> <p>{itemCount}</p>
</div> </div>
<div className={styles.listContentItem}> <div className={styles.listContentItem}>
<span></span> <span></span>
@ -107,11 +106,19 @@ export const BasicList: FC = () => {
}; };
const extraContent = ( const extraContent = (
<div> <div>
{/*<RadioGroup defaultValue="all"> <Button
<RadioButton value="all"></RadioButton> type="dashed"
<RadioButton value="progress"></RadioButton> onClick={() => {
<RadioButton value="waiting"></RadioButton> setVisible(true);
</RadioGroup>*/} }}
style={{
marginBottom: 8,
float: 'left'
}}
>
<PlusOutlined />
</Button>
<Search <Search
className={styles.extraContentSearch} className={styles.extraContentSearch}
placeholder="请输入" placeholder="请输入"
@ -211,19 +218,6 @@ export const BasicList: FC = () => {
</Card> </Card>
</div> </div>
</PageContainer> </PageContainer>
<Button
type="dashed"
onClick={() => {
setVisible(true);
}}
style={{
width: '100%',
marginBottom: 8,
}}
>
<PlusOutlined />
</Button>
<OperationModal <OperationModal
done={done} done={done}
open={open} open={open}

View File

@ -1,12 +1,14 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { StoryItem, StoryType } from './data.d'; import { StoryItem, StoryType } from './data.d';
import {CommonResponse} from "@/types/common"; import {CommonListResponse, CommonResponse} from "@/types/common";
type ParamsType = { type ParamsType = {
count?: number; count?: number;
instanceId?: string; instanceId?: string;
storyName?: string; storyName?: string;
} & Partial<StoryType>; pageSize?: number;
current?: number;
} & Partial<StoryType> & Partial<StoryItem>;
export async function queryTimelineList( export async function queryTimelineList(
params: ParamsType, params: ParamsType,
@ -55,12 +57,10 @@ export async function addStoryItem(params: FormData): Promise<any> {
}); });
} }
export async function queryStoryItem(storyInstanceId: string): Promise<{ data: StoryItem[] }> { export async function queryStoryItem(params: ParamsType): Promise<{ data: CommonListResponse<StoryItem> }> {
return request(`/story/item/list`, { return request(`/story/item/list`, {
method: 'GET', method: 'GET',
params: { params: params,
storyInstanceId,
},
}); });
} }

View File

@ -4,9 +4,16 @@ export interface CommonResponse<T> {
data?: T; data?: T;
} }
export type CommonListResponse<T> = { export type CommonListResponse<T> = {
list?: T[]; list: T[];
total?: number; total?: number;
pageSize?: number; pageSize?: number;
pageNumber?: number; pageNumber?: number;
pages?: number; pages?: number;
} }
export interface PageParams {
// query
/** 当前的页码 */
current?: number;
/** 页面的容量 */
pageSize?: number;
}