feat: 实现时间线拖拽排序功能及PWA支持
Some checks failed
test/timeline-frontend/pipeline/head Something is wrong with the build of this commit

新增时间线节点的拖拽排序功能,使用dnd-kit库实现可排序网格布局。添加PWA支持,包括Service Worker注册和manifest配置。优化移动端适配,改进批量操作工具栏和撤销/重做功能。

重构用户登录和注册页面,修复登录跳转逻辑。调整画廊视图在不同设备上的显示效果。新增协作成员管理功能,支持批量修改权限。

修复请求错误处理中的跳转逻辑问题,避免重复跳转登录页。优化样式表,增强时间线卡片和图片展示的响应式布局。

新增多个API接口支持批量操作,包括排序、删除和时间修改。引入useBatchSelection和useHistory自定义Hook管理状态。添加UndoRedoToolbar组件提供撤销/重做功能。

实现Service Worker离线缓存策略,支持静态资源和API请求的缓存。新增PWA工具函数处理安装提示和更新检测。优化移动端交互,调整组件布局和操作按钮。
This commit is contained in:
2026-02-24 10:33:10 +08:00
parent 5139817b3c
commit 97a5ad3a00
24 changed files with 3012 additions and 247 deletions

View File

@@ -0,0 +1,159 @@
/**
* SortableTimelineGridItem - 可排序的时间线网格项组件
*
* 功能描述:
* 该组件包装 TimelineGridItem为其添加拖拽排序能力。
* 使用 dnd-kit 的 useSortable Hook 实现拖拽功能。
*
* 设计思路:
* 1. 使用 useSortable Hook 获取拖拽属性
* 2. 通过 CSS transform 实现拖拽动画
* 3. 拖拽时显示占位符和视觉反馈
* 4. 支持禁用拖拽(移动端默认禁用)
*
* @author Timeline Team
* @date 2024
*/
import { StoryItem } from '@/pages/story/data';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { DragOutlined } from '@ant-design/icons';
import React, { memo, CSSProperties } from 'react';
import TimelineGridItem from './TimelineGridItem';
/**
* 组件属性接口
* @property item - 时间线节点数据
* @property disabled - 是否禁用拖拽
* @property handleOption - 操作回调
* @property onOpenDetail - 打开详情回调
* @property refresh - 刷新数据回调
* @property disableEdit - 是否禁用编辑
*/
interface SortableTimelineGridItemProps {
item: StoryItem;
disabled?: boolean;
handleOption: (item: StoryItem, option: 'add' | 'edit' | 'addSubItem' | 'editSubItem') => void;
onOpenDetail: (item: StoryItem) => void;
refresh: () => void;
disableEdit?: boolean;
}
/**
* SortableTimelineGridItem 组件
* 为时间线节点添加拖拽排序能力
*/
const SortableTimelineGridItem: React.FC<SortableTimelineGridItemProps> = memo(({
item,
disabled = false,
handleOption,
onOpenDetail,
refresh,
disableEdit = false,
}) => {
/**
* useSortable Hook 核心功能:
* - attributes: 可访问性属性(如 tabindex
* - listeners: 拖拽事件监听器
* - setNodeRef: 设置 DOM 节点引用
* - transform: CSS transform 值
* - transition: CSS transition 值
* - isDragging: 是否正在拖拽
*/
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: item.instanceId,
disabled: disabled,
data: {
type: 'timeline-item',
item,
},
});
/**
* 构建拖拽样式
* - transform: 应用拖拽位移和缩放
* - transition: 平滑过渡动画
* - opacity: 拖拽时降低透明度
* - zIndex: 拖拽时提升层级
*/
const style: CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
zIndex: isDragging ? 1000 : 'auto',
position: 'relative' as const,
};
return (
<div
ref={setNodeRef}
style={style}
className="sortable-timeline-item-wrapper"
>
{/* 拖拽手柄 - 仅在非禁用状态显示 */}
{!disabled && !disableEdit && (
<div
className="drag-handle"
{...attributes}
{...listeners}
style={{
position: 'absolute',
top: 8,
left: 8,
zIndex: 20,
cursor: 'grab',
padding: '4px 8px',
background: 'rgba(0, 0, 0, 0.6)',
borderRadius: 4,
opacity: 0,
transition: 'opacity 0.2s ease',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
onClick={(e) => e.stopPropagation()}
>
<DragOutlined style={{ color: '#fff', fontSize: 14 }} />
</div>
)}
{/* 原有组件 */}
<TimelineGridItem
item={item}
handleOption={handleOption}
onOpenDetail={onOpenDetail}
refresh={refresh}
disableEdit={disableEdit}
/>
{/* 拖拽时的视觉反馈 */}
{isDragging && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
border: '2px dashed #1890ff',
borderRadius: 8,
pointerEvents: 'none',
background: 'rgba(24, 144, 255, 0.1)',
}}
/>
)}
</div>
);
});
SortableTimelineGridItem.displayName = 'SortableTimelineGridItem';
export default SortableTimelineGridItem;