我做了一个看板,包含3列(To do,Doing and Done).每一栏都有任务.我把每一个动作都连接到Firebase上(添加,删除或修改任务和列)。但只有一个动作,我不能连接到firebase,这是当我拖动一个任务之间的列,我希望该任务将其columnId更改为它已添加到列。实现了拖动,但它没有连接到Firebase。我将为您提供我的Firebase结构和所需的代码。Firebase:
columns (collection):
todo (document id): title (string field)
doing (document id): title (string field)
done (document id): title (string field)
tasks (collection): auto generated taskId:
columnId: (string field) which determines the column
content: (string field) content of the task
字符串
index.ts:类型
export type Id = string | number;
export type Column = { id: Id; title: string; };
export type Task = { id: Id; columnId: Id; content: string; };
型
tsx:所有事情发生的地方(这不是完整的代码)
function KanbanBoard() {
const [columns, setColumns] = useState<Column[]>([]);
const [tasks, setTasks] = useState<Task[]>([]);
const [activeColumn, setActiveColumn] = useState<Column | null>(null);
const [activeTask, setActiveTask] = useState<Task | null>(null);
return (
<div
className="
m-auto
flex
min-h-screen
w-full
items-center
overflow-x-auto
overflow-y-hidden
px-[40]
">
<DndContext
sensors={sensors}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
onDragOver={onDragOver}>
<div className="m-auto flex gap-2">
<div className="flex gap-4">
{columns.map((col) => (
<ColumnContainer
key={col.id}
column={col}
updateColumn={updateColumn}
createTask={createTask}
tasks={tasks.filter((task) => task.columnId === col.id)}
deleteTask={deleteTask}
updateTask={updateTask}
/>
))}
</div>
</div>
{typeof document !== "undefined" &&
createPortal(
<DragOverlay>
{activeColumn && (
<ColumnContainer
column={activeColumn}
updateColumn={updateColumn}
createTask={createTask}
deleteTask={deleteTask}
updateTask={updateTask}
tasks={tasks.filter(
(task) => task.columnId === activeColumn.id
)}
/>
)}
{activeTask && (
<TaskCard
task={activeTask}
deleteTask={deleteTask}
updateTask={updateTask}
/>
)}
</DragOverlay>,
document.body
)}
</DndContext>
</div>
);
function onDragEnd(event: DragEndEvent) {
setActiveColumn(null);
setActiveTask(null);
const { active, over } = event;
if (!over) return;
const activeColumnId = active.id;
const overColumnId = over.id;
if (activeColumnId === overColumnId) return;
setColumns((columns) => {
const activeColumnIndex = columns.findIndex(
(col) => col.id === activeColumnId
);
const overColumnIndex = columns.findIndex(
(col) => col.id === overColumnId
);
return arrayMove(columns, activeColumnIndex, overColumnIndex);
});
}
function onDragOver(event: DragOverEvent) {
const { active, over } = event;
if (!over) return;
const activeId = active.id;
const overId = over.id;
if (activeId === overId) return;
const isActiveATask = active.data.current?.type === "Task";
const isOverATask = over.data.current?.type === "Task";
if (!isActiveATask) return;
//Dropping a Task over another task
if (isActiveATask && isOverATask) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
const overIndex = tasks.findIndex((t) => t.id === overId);
tasks[activeIndex].columnId = tasks[overIndex].columnId;
return arrayMove(tasks, activeIndex, overIndex);
});
}
const isOverAColumn = over.data.current?.type === "Column";
//Dropping a Task over a column
if (isActiveATask && isOverAColumn) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
tasks[activeIndex].columnId = overId;
return arrayMove(tasks, activeIndex, activeIndex);
});
}
}
function onDragStart(event: DragStartEvent) {
if (event.active.data.current?.type === "Column") {
setActiveColumn(event.active.data.current.column);
return;
}
if (event.active.data.current?.type === "Task") {
setActiveTask(event.active.data.current.task);
return;
}
}
async function updateTask(id: Id, content: string) {
const newTasks = tasks.map((task) => {
if (task.id !== id) return task;
return { ...task, content };
});
try {
const taskRef = doc(db, "tasks", id.toString());
await updateDoc(taskRef, {
content,
});
} catch (error) {
console.log(error);
}
setTasks(newTasks);
}
型
ColumnContainer.tsx:
interface Props {
column: Column;
tasks: Task[];
updateColumn: (id: Id, title: string) => void;
createTask: (columnId: Id) => void;
deleteTask: (id: Id) => void;
updateTask: (id: Id, content: string) => void;
}
function ColumnContainer(props: Props) {
const { column, updateColumn, createTask, tasks, deleteTask, updateTask } =
props;
const [editMode, setEditMode] = useState(false);
const [localTitle, setLocalTitle] = useState(column.title);
const taskIds = useMemo(() => {
return tasks.map((task) => task.id);
}, [tasks]);
const {
setNodeRef,
attributes,
listeners,
transform,
transition,
isDragging,
} = useSortable({
id: column.id,
data: {
type: "Column",
column,
},
disabled: editMode,
});
const style = {
transition,
transform: CSS.Transform.toString(transform),
};
const toggleEditMode = () => {
setEditMode((prev) => !prev);
};
const saveColumnTitle = debounce(() => {
updateColumn(column.id, localTitle);
}, 0);
useEffect(() => {
if (editMode) {
// If in edit mode, update local title immediately
saveColumnTitle.flush();
}
}, [editMode]);
if (isDragging) {
return (
<div
ref={setNodeRef}
style={style}
data-column-id={column.id}
className="
bg-columnBackgroundColor
opacity-40
border-2
border-rose-500
w-[350px]
h-[500px]
max-h-[500px]
rounded-md
flex
flex-col
"></div>
);
}
return (
<div
ref={setNodeRef}
style={style}
className="
bg-columnBackgroundColor
w-[350px]
h-[500px]
max-h-[500px]
rounded-md
flex
flex-col
">
{/* Column title */}
<div
{...attributes}
{...listeners}
onClick={() => {
setEditMode(true);
}}
className="
bg-mainBackgroundColor
text-md
h-[60px]
cursor-grab
rounded-md
rounded-b-none
p-3
font-bold
border-columnBackgroundColor
border-4x
flex
items-center
justify-between
">
<div className="flex gap-2">
{!editMode && column.title}
{editMode && (
<input
className="bg-black focus:border-rose-500 border rounded outline-none px-2"
value={localTitle}
onChange={(e) => setLocalTitle(e.target.value)}
autoFocus
onBlur={() => {
toggleEditMode();
saveColumnTitle();
}}
onKeyDown={(e) => {
if (e.key !== "Enter") return;
toggleEditMode();
saveColumnTitle();
}}
/>
)}
</div>
</div>
{/* Column task container */}
<div className="flex flex-grow flex-col gap-4 p-2 overflow-x-hidden overflow-y-auto">
<SortableContext items={[0]}>
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
deleteTask={deleteTask}
updateTask={updateTask}
/>
))}
</SortableContext>
</div>
{/* Column footer */}
<button
onClick={() => {
createTask(column.id);
}}
className="flex gap-2 items-center border-columnBackgroundColor boder-2 rounded-md p-4 border-x-columnBackgroundColor hover:bg-mainBackgroundColor hover:text-rose-500 active:bg-black">
<PlusCircleIcon className="w-6 h-6" />
Add task
</button>
</div>
);
}
export default ColumnContainer;
型
如果你需要更多的信息,请让我知道。
1条答案
按热度按时间2guxujil1#
我使用SQL Server. net 6 API作为后端做了我的工作,我将很快制作一个github repo来解释我是如何做到的,现在我将简要地解释一下...你有一个函数,处理拖动的卡类似...
const handleDragEnd = async (dropResult: DropResult) => {
然后获得拖动的卡数据const { source, destination, type } = dropResult;
现在处理卡重新排序逻辑////检查卡是否在同一个列表中移动if (source.droppableId === destination.droppableId)
你可以用很多方法来排序它,我做的方法是创建一个名为sequence的字段来保存卡的序列,所以每次我拖动卡片的时候,我都会发布一个API,并设置为2sec来更新拖动卡片的顺序,这取决于它是否在同一个列表中移动。