2025-08-07 19:48:36 +08:00
|
|
|
|
// src/pages/story/detail.tsx
|
2025-08-05 19:02:14 +08:00
|
|
|
|
import AddTimeLineItemModal from '@/pages/story/components/AddTimeLineItemModal';
|
|
|
|
|
|
import TimelineItem from '@/pages/story/components/TimelineItem/TimelineItem';
|
|
|
|
|
|
import { StoryItem } from '@/pages/story/data';
|
2025-08-07 19:48:36 +08:00
|
|
|
|
import { queryStoryDetail, queryStoryItem } from '@/pages/story/service';
|
2025-08-05 19:02:14 +08:00
|
|
|
|
import { PageContainer } from '@ant-design/pro-components';
|
2025-08-08 17:42:07 +08:00
|
|
|
|
import { history, useRequest } from '@umijs/max';
|
|
|
|
|
|
import { FloatButton, Spin, Empty, Button } from 'antd';
|
2025-08-07 19:48:36 +08:00
|
|
|
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
2025-08-05 19:02:14 +08:00
|
|
|
|
import { useParams } from 'react-router';
|
2025-08-07 19:48:36 +08:00
|
|
|
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
2025-08-08 17:42:07 +08:00
|
|
|
|
import { VariableSizeList as List } from 'react-window';
|
|
|
|
|
|
import { SyncOutlined } from '@ant-design/icons';
|
2025-08-05 19:02:14 +08:00
|
|
|
|
import './index.css';
|
|
|
|
|
|
|
|
|
|
|
|
const Index = () => {
|
|
|
|
|
|
const { id: lineId } = useParams<{ id: string }>();
|
|
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
2025-08-08 17:42:07 +08:00
|
|
|
|
const listRef = useRef<any>(null);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
const [items, setItems] = useState<StoryItem[]>([]);
|
2025-08-05 19:02:14 +08:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
2025-08-08 17:42:07 +08:00
|
|
|
|
const [hasMoreOld, setHasMoreOld] = useState(true); // 是否有更老的数据
|
|
|
|
|
|
const [hasMoreNew, setHasMoreNew] = useState(true); // 是否有更新的数据
|
2025-08-05 19:02:14 +08:00
|
|
|
|
const [openAddItemModal, setOpenAddItemModal] = useState(false);
|
|
|
|
|
|
const [currentItem, setCurrentItem] = useState<StoryItem>();
|
2025-08-07 19:48:36 +08:00
|
|
|
|
const [currentOption, setCurrentOption] = useState<
|
|
|
|
|
|
'add' | 'edit' | 'addSubItem' | 'editSubItem'
|
|
|
|
|
|
>();
|
|
|
|
|
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
2025-08-08 17:42:07 +08:00
|
|
|
|
// 存储每个item的高度
|
|
|
|
|
|
const [itemSizes, setItemSizes] = useState<Record<string, number>>({});
|
|
|
|
|
|
// 存储已测量高度的item ID集合
|
|
|
|
|
|
const measuredItemsRef = useRef<Set<string>>(new Set());
|
|
|
|
|
|
const [isRefreshing, setIsRefreshing] = useState(false); // 是否正在刷新最新数据
|
|
|
|
|
|
const [showScrollTop, setShowScrollTop] = useState(false); // 是否显示回到顶部按钮
|
2025-08-05 19:02:14 +08:00
|
|
|
|
|
2025-08-07 19:48:36 +08:00
|
|
|
|
const { data: response, run } = useRequest(
|
2025-08-05 19:02:14 +08:00
|
|
|
|
() => {
|
2025-08-07 19:48:36 +08:00
|
|
|
|
return queryStoryItem({ storyInstanceId: lineId, ...pagination });
|
2025-08-05 19:02:14 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
manual: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
2025-08-05 19:02:14 +08:00
|
|
|
|
const {
|
|
|
|
|
|
data: detail,
|
|
|
|
|
|
run: queryDetail,
|
|
|
|
|
|
loading: queryDetailLoading,
|
|
|
|
|
|
} = useRequest(() => {
|
|
|
|
|
|
return queryStoryDetail(lineId ?? '');
|
|
|
|
|
|
});
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
2025-08-05 19:02:14 +08:00
|
|
|
|
// 初始化加载数据
|
|
|
|
|
|
useEffect(() => {
|
2025-08-07 19:48:36 +08:00
|
|
|
|
setItems([]);
|
|
|
|
|
|
setPagination({ current: 1, pageSize: 10 });
|
2025-08-08 17:42:07 +08:00
|
|
|
|
setHasMoreOld(true);
|
|
|
|
|
|
setHasMoreNew(true);
|
|
|
|
|
|
// 重置高度缓存
|
|
|
|
|
|
setItemSizes({});
|
|
|
|
|
|
measuredItemsRef.current = new Set();
|
2025-08-05 19:02:14 +08:00
|
|
|
|
run();
|
|
|
|
|
|
}, [lineId]);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理响应数据
|
2025-08-05 19:02:14 +08:00
|
|
|
|
useEffect(() => {
|
2025-08-07 19:48:36 +08:00
|
|
|
|
if (!response) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (pagination.current === 1) {
|
|
|
|
|
|
// 首页数据
|
|
|
|
|
|
setItems(response.list || []);
|
2025-08-08 17:42:07 +08:00
|
|
|
|
} else if (pagination.current > 1) {
|
|
|
|
|
|
// 追加更老的数据
|
2025-08-07 19:48:36 +08:00
|
|
|
|
setItems(prev => [...prev, ...(response.list || [])]);
|
2025-08-08 17:42:07 +08:00
|
|
|
|
} else if (pagination.current < 1) {
|
|
|
|
|
|
// 在前面插入更新的数据
|
|
|
|
|
|
setItems(prev => [...(response.list || []), ...prev]);
|
|
|
|
|
|
// 保持滚动位置
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (listRef.current && response.list) {
|
|
|
|
|
|
listRef.current.scrollToItem(response.list.length, 'start');
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 0);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否还有更多数据
|
2025-08-08 17:42:07 +08:00
|
|
|
|
if (pagination.current >= 1) {
|
|
|
|
|
|
// 检查是否有更老的数据
|
|
|
|
|
|
setHasMoreOld(response.list && response.list.length === pagination.pageSize);
|
|
|
|
|
|
} else if (pagination.current < 1) {
|
|
|
|
|
|
// 检查是否有更新的数据
|
|
|
|
|
|
setHasMoreNew(response.list && response.list.length === pagination.pageSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:48:36 +08:00
|
|
|
|
setLoading(false);
|
2025-08-08 17:42:07 +08:00
|
|
|
|
setIsRefreshing(false);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
}, [response, pagination]);
|
|
|
|
|
|
|
2025-08-08 17:42:07 +08:00
|
|
|
|
// 滚动到底部加载更老的数据
|
|
|
|
|
|
const loadOlder = useCallback(() => {
|
|
|
|
|
|
if (loading || !hasMoreOld || pagination.current < 1) return;
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
setPagination(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
current: prev.current + 1
|
2025-08-05 19:02:14 +08:00
|
|
|
|
}));
|
2025-08-08 17:42:07 +08:00
|
|
|
|
}, [loading, hasMoreOld, pagination]);
|
|
|
|
|
|
|
|
|
|
|
|
// 滚动到顶部加载更新的数据
|
|
|
|
|
|
const loadNewer = useCallback(() => {
|
|
|
|
|
|
if (loading || !hasMoreNew || pagination.current > 1 || isRefreshing) return;
|
|
|
|
|
|
|
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
|
setPagination(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
current: prev.current - 1
|
|
|
|
|
|
}));
|
|
|
|
|
|
}, [loading, hasMoreNew, pagination, isRefreshing]);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 当分页变化时重新请求数据
|
|
|
|
|
|
useEffect(() => {
|
2025-08-08 17:42:07 +08:00
|
|
|
|
if (pagination.current !== 1) {
|
2025-08-07 19:48:36 +08:00
|
|
|
|
run();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [pagination, run]);
|
2025-08-05 19:02:14 +08:00
|
|
|
|
|
2025-08-08 17:42:07 +08:00
|
|
|
|
// 获取item高度的函数
|
|
|
|
|
|
const getItemSize = useCallback((index: number) => {
|
|
|
|
|
|
const item = items[index];
|
|
|
|
|
|
if (!item) return 300; // 默认高度
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经测量过该item的高度,则使用缓存的值
|
|
|
|
|
|
if (itemSizes[item.id]) {
|
|
|
|
|
|
return itemSizes[item.id];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回默认高度
|
|
|
|
|
|
return 300;
|
|
|
|
|
|
}, [items, itemSizes]);
|
|
|
|
|
|
|
|
|
|
|
|
// 当item尺寸发生变化时调用
|
|
|
|
|
|
const onItemResize = useCallback((itemId: string, height: number) => {
|
|
|
|
|
|
// 只有当高度发生变化时才更新
|
|
|
|
|
|
if (itemSizes[itemId] !== height) {
|
|
|
|
|
|
setItemSizes(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[itemId]: height
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
// 通知List组件重新计算尺寸
|
|
|
|
|
|
if (listRef.current) {
|
|
|
|
|
|
listRef.current.resetAfterIndex(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [itemSizes]);
|
|
|
|
|
|
|
2025-08-07 19:48:36 +08:00
|
|
|
|
// 渲染单个时间线项的函数
|
|
|
|
|
|
const renderTimelineItem = useCallback(
|
|
|
|
|
|
({ index, style }: { index: number; style: React.CSSProperties }) => {
|
2025-08-08 17:42:07 +08:00
|
|
|
|
// 显示加载指示器的条件
|
|
|
|
|
|
const showOlderLoading = index === items.length && hasMoreOld && pagination.current >= 1;
|
|
|
|
|
|
const showNewerLoading = index === 0 && hasMoreNew && pagination.current < 1 && isRefreshing;
|
|
|
|
|
|
|
|
|
|
|
|
if (showOlderLoading || showNewerLoading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={style}>
|
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
|
|
|
|
|
<Spin />
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
{showNewerLoading ? '正在加载更新的内容...' : '正在加载更多内容...'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 19:48:36 +08:00
|
|
|
|
const item = items[index];
|
|
|
|
|
|
if (!item) return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={style}>
|
2025-08-08 17:42:07 +08:00
|
|
|
|
<div
|
|
|
|
|
|
ref={(el) => {
|
|
|
|
|
|
// 当元素被渲染时测量其实际高度
|
|
|
|
|
|
if (el && !measuredItemsRef.current.has(item.id)) {
|
|
|
|
|
|
measuredItemsRef.current.add(item.id);
|
|
|
|
|
|
// 使用requestAnimationFrame确保DOM已经渲染完成
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
|
if (el) {
|
|
|
|
|
|
const height = el.getBoundingClientRect().height;
|
|
|
|
|
|
onItemResize(item.id, height);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
}}
|
2025-08-08 17:42:07 +08:00
|
|
|
|
>
|
|
|
|
|
|
<TimelineItem
|
|
|
|
|
|
item={item}
|
|
|
|
|
|
handleOption={(
|
|
|
|
|
|
item: StoryItem,
|
|
|
|
|
|
option: 'add' | 'edit' | 'addSubItem' | 'editSubItem',
|
|
|
|
|
|
) => {
|
|
|
|
|
|
setCurrentItem(item);
|
|
|
|
|
|
setCurrentOption(option);
|
|
|
|
|
|
setOpenAddItemModal(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
refresh={() => {
|
|
|
|
|
|
// 刷新当前页数据
|
|
|
|
|
|
setPagination(prev => ({ ...prev, current: 1 }));
|
|
|
|
|
|
// 重置高度测量
|
|
|
|
|
|
measuredItemsRef.current = new Set();
|
|
|
|
|
|
run();
|
|
|
|
|
|
queryDetail();
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-08-07 19:48:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2025-08-08 17:42:07 +08:00
|
|
|
|
[items, hasMoreOld, hasMoreNew, pagination, isRefreshing, onItemResize, run, queryDetail],
|
2025-08-07 19:48:36 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理滚动事件
|
2025-08-08 17:42:07 +08:00
|
|
|
|
const handleItemsRendered = useCallback(({ visibleStartIndex, visibleStopIndex }) => {
|
|
|
|
|
|
// 当可视区域接近列表顶部时加载更新的数据
|
|
|
|
|
|
if (visibleStartIndex <= 3 && hasMoreNew && !isRefreshing && pagination.current >= 1) {
|
|
|
|
|
|
loadNewer();
|
2025-08-07 19:48:36 +08:00
|
|
|
|
}
|
2025-08-08 17:42:07 +08:00
|
|
|
|
|
|
|
|
|
|
// 当可视区域接近列表底部时加载更老的数据
|
|
|
|
|
|
if (visibleStopIndex >= items.length - 3 && hasMoreOld && !loading && pagination.current >= 1) {
|
|
|
|
|
|
loadOlder();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 控制回到顶部按钮的显示
|
|
|
|
|
|
setShowScrollTop(visibleStartIndex > 5);
|
|
|
|
|
|
}, [hasMoreNew, hasMoreOld, isRefreshing, loading, items.length, pagination, loadNewer, loadOlder]);
|
|
|
|
|
|
|
|
|
|
|
|
// 手动刷新最新数据
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
|
if (isRefreshing) return;
|
|
|
|
|
|
|
|
|
|
|
|
setIsRefreshing(true);
|
|
|
|
|
|
setPagination(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
current: 0 // 使用0作为刷新标识
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 回到顶部
|
|
|
|
|
|
const scrollToTop = () => {
|
|
|
|
|
|
if (listRef.current) {
|
|
|
|
|
|
listRef.current.scrollToItem(0, 'start');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 监听滚动事件,动态显示/隐藏提示信息
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
|
if (containerRef.current) {
|
|
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否在顶部
|
|
|
|
|
|
const isTop = scrollTop === 0;
|
|
|
|
|
|
// 判断是否在底部
|
|
|
|
|
|
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
|
|
|
|
|
|
|
|
|
|
|
setHasMoreNew(isTop && hasMoreNew); // 更新顶部提示的显示状态
|
|
|
|
|
|
setHasMoreOld(isBottom && hasMoreOld); // 更新底部提示的显示状态
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const timelineContainer = containerRef.current;
|
|
|
|
|
|
if (timelineContainer) {
|
|
|
|
|
|
timelineContainer.addEventListener('scroll', handleScroll);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
if (timelineContainer) {
|
|
|
|
|
|
timelineContainer.removeEventListener('scroll', handleScroll);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [hasMoreNew, hasMoreOld]);
|
2025-08-05 19:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<PageContainer
|
|
|
|
|
|
onBack={() => history.push('/story')}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
title={
|
|
|
|
|
|
queryDetailLoading ? '加载中' : `${detail?.title} ${`共${detail?.itemCount ?? 0}个时刻`}`
|
|
|
|
|
|
}
|
2025-08-08 17:42:07 +08:00
|
|
|
|
extra={
|
|
|
|
|
|
<Button
|
|
|
|
|
|
icon={<SyncOutlined />}
|
|
|
|
|
|
onClick={handleRefresh}
|
|
|
|
|
|
loading={isRefreshing}
|
|
|
|
|
|
>
|
|
|
|
|
|
刷新
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
}
|
2025-08-05 19:02:14 +08:00
|
|
|
|
>
|
2025-08-07 19:48:36 +08:00
|
|
|
|
<div
|
|
|
|
|
|
className="timeline"
|
|
|
|
|
|
ref={containerRef}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
height: 'calc(100vh - 200px)',
|
2025-08-08 17:42:07 +08:00
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
position: 'relative'
|
2025-08-07 19:48:36 +08:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{items.length > 0 ? (
|
2025-08-08 17:42:07 +08:00
|
|
|
|
<>
|
|
|
|
|
|
{/* 顶部提示信息 */}
|
|
|
|
|
|
{!hasMoreNew && pagination.current <= 1 && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
color: '#999',
|
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
zIndex: 10,
|
|
|
|
|
|
borderBottom: '1px solid #f0f0f0'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
已加载全部更新内容
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<AutoSizer>
|
|
|
|
|
|
{({ height, width }) => (
|
2025-08-07 19:48:36 +08:00
|
|
|
|
<List
|
2025-08-08 17:42:07 +08:00
|
|
|
|
ref={listRef}
|
|
|
|
|
|
height={height - (hasMoreNew && pagination.current <= 1 ? 40 : 0) - (hasMoreOld && pagination.current >= 1 ? 40 : 0)}
|
|
|
|
|
|
itemCount={items.length + (hasMoreOld && pagination.current >= 1 ? 1 : 0) + (hasMoreNew && pagination.current < 1 && isRefreshing ? 1 : 0)}
|
|
|
|
|
|
itemSize={getItemSize}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
width={width}
|
2025-08-08 17:42:07 +08:00
|
|
|
|
onItemsRendered={handleItemsRendered}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
>
|
|
|
|
|
|
{renderTimelineItem}
|
|
|
|
|
|
</List>
|
2025-08-08 17:42:07 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</AutoSizer>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 底部提示信息 */}
|
|
|
|
|
|
{!hasMoreOld && pagination.current >= 1 && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
color: '#999',
|
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
|
bottom: 0,
|
|
|
|
|
|
backgroundColor: '#fff',
|
|
|
|
|
|
zIndex: 10,
|
|
|
|
|
|
borderTop: '1px solid #f0f0f0'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
已加载全部历史内容
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
2025-08-08 17:42:07 +08:00
|
|
|
|
{/* 回到顶部按钮 */}
|
|
|
|
|
|
{showScrollTop && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
bottom: 20,
|
|
|
|
|
|
right: 20,
|
|
|
|
|
|
zIndex: 10
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
shape="circle"
|
|
|
|
|
|
icon={<SyncOutlined />}
|
|
|
|
|
|
onClick={scrollToTop}
|
|
|
|
|
|
/>
|
2025-08-07 19:48:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-08 17:42:07 +08:00
|
|
|
|
</>
|
2025-08-07 19:48:36 +08:00
|
|
|
|
) : (
|
2025-08-08 17:42:07 +08:00
|
|
|
|
<div style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
height: '100%',
|
|
|
|
|
|
textAlign: 'center'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{loading ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Spin size="large" />
|
|
|
|
|
|
<div style={{ marginTop: 16 }}>正在加载时间线数据...</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Empty
|
|
|
|
|
|
description="暂无时间线数据"
|
|
|
|
|
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
style={{ marginTop: 16 }}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setCurrentOption('add');
|
|
|
|
|
|
setCurrentItem();
|
|
|
|
|
|
setOpenAddItemModal(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
添加第一个时刻
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2025-08-07 19:48:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-08-05 19:02:14 +08:00
|
|
|
|
</div>
|
2025-08-07 19:48:36 +08:00
|
|
|
|
|
|
|
|
|
|
<FloatButton
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setCurrentOption('add');
|
|
|
|
|
|
setCurrentItem();
|
|
|
|
|
|
setOpenAddItemModal(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2025-08-05 19:02:14 +08:00
|
|
|
|
<AddTimeLineItemModal
|
|
|
|
|
|
visible={openAddItemModal}
|
|
|
|
|
|
initialValues={currentItem}
|
|
|
|
|
|
option={currentOption}
|
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
|
setOpenAddItemModal(false);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onOk={() => {
|
|
|
|
|
|
setOpenAddItemModal(false);
|
2025-08-07 19:48:36 +08:00
|
|
|
|
// 添加新项后刷新数据
|
|
|
|
|
|
setPagination(prev => ({ ...prev, current: 1 }));
|
2025-08-08 17:42:07 +08:00
|
|
|
|
// 重置高度测量
|
|
|
|
|
|
measuredItemsRef.current = new Set();
|
2025-08-05 19:02:14 +08:00
|
|
|
|
run();
|
2025-08-08 17:42:07 +08:00
|
|
|
|
queryDetail();
|
2025-08-05 19:02:14 +08:00
|
|
|
|
}}
|
|
|
|
|
|
storyId={lineId}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</PageContainer>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default Index;
|