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;
|