You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular跨Column组件实现Task跨列拖拽功能求助

Angular看板任务跨列拖拽实现方案

问题背景

已实现Angular看板的列拖拽排序、列内任务重排,但无法实现任务跨列拖拽转移。当前架构:

  • board-page.component:管理列列表
  • column.component:管理列内任务列表
    尝试使用cdkDropListGroup因组件分离未生效,需解决跨组件的拖拽连接问题。

现有代码片段

board-page.component.html

<section *ngIf="(board$ | async) as board" class="board">
  <app-board-page-header
    class="board__header"
    [boardId]="board._id"
    (back)="navigateBack()"
    [boardTitle]="board.title || ''">
  </app-board-page-header>

  <div *ngIf="userId$ | async as userId"
       class="board__content">
    <ng-container *ngIf="(columns$ | async) as columns">
      <ul *ngIf="columns.length; else noColumns" class="board__columns columns"
       cdkDropList
       cdkDropListOrientation="horizontal"
       (cdkDropListDropped)="reorderColumns($event, columns)"
       cdkDropListGroup>
        <li *ngFor="let column of columns | sortByOrder" class="columns__item" cdkDrag [cdkDragData]="column" cdkDropList>
          <app-column [boardId]="board._id" [column]="column" [userId]="userId"></app-column>
        </li>
      </ul>

      <ng-template #noColumns>
        <div class="no-data">
          <h1 class="no-data__title">No columns! Create one.</h1>
          <app-no-data-svg class="no-data__image"></app-no-data-svg>
        </div>
      </ng-template>
    </ng-container>
  </div>
</section>

column.component.html

<div class="column">
  <header class="column__header"
          [ngClass]="isEditingTitle ? 'column__header--editing' : ''">
    <ng-container *ngIf="isEditingTitle; else notEditing">
      <form (ngSubmit)="submitEditTitle(editTitleInput.value)"
            class="edit-column-form">
        <mat-form-field appearance="standard" class="edit-column-form__field">
          <input #editTitleInput
                 [value]="column.title"
                 class="edit-column-form__input" matInput>
          <button
            class="edit-column-form__submit-btn"
            mat-icon-button
            matSuffix
            matTooltip="Submit"
            type="submit">
            <mat-icon>check</mat-icon>
          </button>
          <button
            (click)="cancelEditTitle()"
            type="button"
            class="edit-column-form__cancel-btn"
            mat-icon-button
            matSuffix
            matTooltip="Cancel">
            <mat-icon>close</mat-icon>
          </button>
        </mat-form-field>
      </form>
    </ng-container>

    <ng-template #notEditing>
      <button (click)="editColumn()" mat-button>
        <h3 class="column__title">
          {{ column.title }}
        </h3>
      </button>

      <button class="column__delete-btn" mat-button>
        <mat-icon (click)="showDeleteColumnModal()" class="delete-task">delete</mat-icon>
      </button>
    </ng-template>
  </header>
  <ul #tasksContainer *ngIf="(tasks$ | async) as tasks" class="column__tasks tasks"
  cdkDropList
  (cdkDropListDropped)="reorderTasks($event,tasks)"
  [cdkDropListData]="tasks"
   >
    <li *ngFor="let task of tasks | sortByOrder" class="tasks__item task" cdkDrag>
      <app-task [boardId]="boardId" [columnId]="column._id" [task]="task">
      </app-task>
    </li>
  </ul>
  <button (click)="openCreateTaskModal()" class="column__create-task-btn" mat-button>
    <mat-icon>add</mat-icon>
    {{ "board.create-task-btn" | translate}}
  </button>
</div>

解决方案

1. 正确配置cdkDropListGroup

将cdkDropListGroup移至所有任务列表的共同父容器,确保所有列内的任务cdkDropList都属于同一个组,实现自动连接。修改board-page.component.html:

<section *ngIf="(board$ | async) as board" class="board">
  <app-board-page-header
    class="board__header"
    [boardId]="board._id"
    (back)="navigateBack()"
    [boardTitle]="board.title || ''">
  </app-board-page-header>

  <div *ngIf="userId$ | async as userId" class="board__content">
    <ng-container *ngIf="(columns$ | async) as columns">
      <!-- 新增cdkDropListGroup,作为所有任务列表的共同祖先 -->
      <div cdkDropListGroup>
        <ul *ngIf="columns.length; else noColumns" class="board__columns columns"
            cdkDropList
            cdkDropListOrientation="horizontal"
            (cdkDropListDropped)="reorderColumns($event, columns)">
          <li *ngFor="let column of columns | sortByOrder" class="columns__item" cdkDrag [cdkDragData]="column">
            <!-- 新增taskDropped事件监听,传递拖拽事件到父组件 -->
            <app-column [boardId]="board._id" [column]="column" [userId]="userId"
                        (taskDropped)="handleTaskDrop($event)"></app-column>
          </li>
        </ul>
      </div>

      <ng-template #noColumns>
        <div class="no-data">
          <h1 class="no-data__title">No columns! Create one.</h1>
          <app-no-data-svg class="no-data__image"></app-no-data-svg>
        </div>
      </ng-template>
    </ng-container>
  </div>
</section>

2. 配置Column组件的任务列表

给每个列的任务列表设置唯一ID,并将拖拽事件输出到父组件。修改column.component.html:

<ul #tasksContainer *ngIf="(tasks$ | async) as tasks" class="column__tasks tasks"
    cdkDropList
    [cdkDropListId]="column._id" <!-- 用列ID作为拖拽列表唯一标识 -->
    [cdkDropListData]="tasks"
    (cdkDropListDropped)="onTaskDropped($event)"> <!-- 自定义事件处理 -->
  <li *ngFor="let task of tasks | sortByOrder" class="tasks__item task" cdkDrag>
    <app-task [boardId]="boardId" [columnId]="column._id" [task]="task"></app-task>
  </li>
</ul>

在column.component.ts中添加输出事件:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Task } from '../models/task.model'; // 替换为你的Task类型

@Component({
  selector: 'app-column',
  templateUrl: './column.component.html',
  styleUrls: ['./column.component.css']
})
export class ColumnComponent {
  @Input() boardId!: string;
  @Input() column!: any; // 替换为你的Column类型
  @Input() userId!: string;
  @Output() taskDropped = new EventEmitter<CdkDragDrop<Task[]>>();

  onTaskDropped(event: CdkDragDrop<Task[]>) {
    this.taskDropped.emit(event);
  }

  // 保留原有逻辑:editColumn、submitEditTitle等
}

3. 在Board组件中统一处理拖拽逻辑

在board-page.component.ts中实现跨列拖拽的核心逻辑,区分列内和跨列操作:

import { Component } from '@angular/core';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { Task } from '../models/task.model';
import { Column } from '../models/column.model';
// 导入你的数据服务,如BoardService、TaskService等

@Component({
  selector: 'app-board-page',
  templateUrl: './board-page.component.html',
  styleUrls: ['./board-page.component.css']
})
export class BoardPageComponent {
  // 保留原有属性:board$, columns$, userId$...

  handleTaskDrop(event: CdkDragDrop<Task[]>) {
    // 列内拖拽:仅调整排序
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
      this.updateTaskOrder(event.container.data, event.container.id);
    } else {
      // 跨列拖拽:转移任务并更新所属列
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
      // 更新任务的columnId字段
      const movedTask = event.container.data[event.currentIndex];
      movedTask.columnId = event.container.id;
      // 同步更新后端数据
      this.updateTaskColumn(movedTask);
      this.updateTaskOrder(event.previousContainer.data, event.previousContainer.id);
      this.updateTaskOrder(event.container.data, event.container.id);
    }
  }

  // 更新列内任务排序(调用API或服务)
  private updateTaskOrder(tasks: Task[], columnId: string) {
    // 示例逻辑:给每个任务设置新的order值并提交
    const updatedTasks = tasks.map((task, index) => ({ ...task, order: index }));
    // this.columnService.updateTasks(columnId, updatedTasks).subscribe();
  }

  // 更新任务所属列(调用API或服务)
  private updateTaskColumn(task: Task) {
    // this.taskService.updateTask(task._id, { columnId: task.columnId }).subscribe();
  }

  // 保留原有逻辑:reorderColumns、navigateBack等
}

关键注意事项

  • cdkDropListGroup必须覆盖所有任务拖拽列表的共同祖先,否则组件分离时无法建立连接。
  • 每个任务列表的cdkDropListId必须唯一,建议使用列的_id,确保拖拽时能准确识别目标列。
  • 跨列拖拽后,必须同时更新任务的columnId和两个列的任务排序,确保前后端数据同步。
  • 将拖拽逻辑统一放在Board组件中,便于管理全看板的数据变更。

内容的提问来源于stack exchange,提问作者Shahzod Kakhkhorov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 23:45:54