feat(story): 支持多视频上传与展示
Some checks failed
test/timeline-frontend/pipeline/head There was a failure building this commit

1. 在文件服务 API 中新增 `batchGetFileInfo` 接口,支持批量获取文件详情。
2. 优化 `AddTimeLineItemModal` 组件,支持多视频选择、预览、批量上传及进度展示。
3. 改进 `TimelineGridItem` 组件,支持在时间轴列表中展示多个视频及对应的缩略图。
4. 增强视频上传流程,自动生成视频首帧作为缩略图并保存元数据。
This commit is contained in:
2026-02-13 11:14:07 +08:00
parent cd752d97d8
commit 5139817b3c
3 changed files with 358 additions and 191 deletions

View File

@@ -1,11 +1,12 @@
// TimelineGridItem.tsx - Grid card layout for timeline items
import TimelineImage from '@/components/TimelineImage';
import { StoryItem } from '@/pages/story/data';
import { batchGetFileInfo, queryStoryItemImages } from '@/services/file/api';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { useIntl, useRequest } from '@umijs/max';
import { Button, message, Popconfirm, Tag, theme } from 'antd';
import React, { useEffect } from 'react';
import { queryStoryItemImages, removeStoryItem } from '../service';
import { removeStoryItem } from '../service';
import TimelineVideo from './TimelineVideo';
// 格式化时间数组为易读格式
@@ -60,9 +61,16 @@ const TimelineGridItem: React.FC<{
root.style.setProperty('--timeline-more-color', token.colorWhite);
}, [token]);
const { data: imagesList } = useRequest(
const { data: filesInfo } = useRequest(
async () => {
return await queryStoryItemImages(item.instanceId);
const idsResponse = await queryStoryItemImages(item.instanceId);
// @ts-ignore
const ids = idsResponse.data || idsResponse || [];
if (Array.isArray(ids) && ids.length > 0) {
const res = await batchGetFileInfo(ids);
return res.data || [];
}
return [];
},
{
refreshDeps: [item.instanceId],
@@ -86,7 +94,7 @@ const TimelineGridItem: React.FC<{
// 动态计算卡片大小1-4格
const calculateCardSize = () => {
const imageCount = imagesList?.length || 0;
const imageCount = filesInfo?.length || 0;
const descriptionLength = item.description?.length || 0;
// 根据图片数量和描述长度决定卡片大小
@@ -175,30 +183,39 @@ const TimelineGridItem: React.FC<{
<p>{truncateText(item.description, descriptionMaxLength)}</p>
{/* Images preview - 固定间隔,单行展示,多余折叠 */}
{item.videoUrl ? (
{item.videoUrl || (filesInfo && filesInfo.length > 0) ? (
<div className="item-images-container" style={{ marginTop: '10px' }}>
<TimelineVideo videoInstanceId={item.videoUrl} thumbnailInstanceId={item.thumbnailUrl} />
</div>
) : (
imagesList &&
imagesList.length > 0 && (
<div className="item-images-container">
{item.videoUrl && (
<div style={{ marginBottom: 10 }}>
<TimelineVideo
videoInstanceId={item.videoUrl}
thumbnailInstanceId={item.thumbnailUrl}
/>
</div>
)}
{filesInfo && filesInfo.length > 0 && (
<div className="item-images-row">
{imagesList
.filter((imageInstanceId) => imageInstanceId && imageInstanceId.trim() !== '')
{filesInfo
.slice(0, 6) // 最多显示6张图片
.map((imageInstanceId, index) => (
<div key={imageInstanceId + index} className="item-image-wrapper">
<TimelineImage title={imageInstanceId} imageInstanceId={imageInstanceId} />
.map((file, index) => (
<div key={file.instanceId + index} className="item-image-wrapper">
{file.contentType && file.contentType.startsWith('video') ? (
<TimelineVideo
videoInstanceId={file.instanceId}
thumbnailInstanceId={file.thumbnailInstanceId}
/>
) : (
<TimelineImage title={file.imageName} imageInstanceId={file.instanceId} />
)}
</div>
))}
{imagesList.length > 6 && (
<div className="more-images-indicator">+{imagesList.length - 6}</div>
{filesInfo.length > 6 && (
<div className="more-images-indicator">+{filesInfo.length - 6}</div>
)}
</div>
</div>
)
)}
)}
</div>
) : null}
{/* Location badge */}
{item.location && <span className="timeline-location-badge">📍 {item.location}</span>}