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工具函数处理安装提示和更新检测。优化移动端交互,调整组件布局和操作按钮。
160 lines
4.1 KiB
TypeScript
160 lines
4.1 KiB
TypeScript
/**
|
||
* 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;
|