From 9851073110ce6c2b4bf382c8ffdb5eb8df31d414 Mon Sep 17 00:00:00 2001 From: LinXin Date: Thu, 27 Nov 2025 13:41:42 +0800 Subject: [PATCH 1/2] feat: dragging function is integrated with the backend, add default columns for the board --- backend/middleware/rbac.go | 1 + backend/routes/routes.go | 37 ++- backend/services/board_service.go | 61 ++++- .../features/CreateColumnDialog.vue | 115 ++++++++ .../components/features/CreateTaskDialog.vue | 119 ++++++++ .../src/components/features/KanbanColumn.vue | 114 ++++---- frontend/src/components/features/TaskCard.vue | 50 ++-- frontend/src/services/column-api.ts | 54 ++++ frontend/src/services/index.ts | 1 + frontend/src/services/task-api.ts | 33 ++- frontend/src/stores/kanban.ts | 258 ++++++++---------- frontend/src/views/dashboard/KanbanView.vue | 156 ++++++++++- 12 files changed, 746 insertions(+), 253 deletions(-) create mode 100644 frontend/src/components/features/CreateColumnDialog.vue create mode 100644 frontend/src/components/features/CreateTaskDialog.vue create mode 100644 frontend/src/services/column-api.ts diff --git a/backend/middleware/rbac.go b/backend/middleware/rbac.go index 01b5852..88c7ae6 100644 --- a/backend/middleware/rbac.go +++ b/backend/middleware/rbac.go @@ -94,6 +94,7 @@ func (m *RBACMiddleware) RequireProjectAccess(level string, paramKey string, idT c.Abort() return } + projectID = task.ProjectID default: c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid ID type for RBAC"}) diff --git a/backend/routes/routes.go b/backend/routes/routes.go index 7f863ec..0fc5250 100644 --- a/backend/routes/routes.go +++ b/backend/routes/routes.go @@ -10,8 +10,8 @@ import ( "progress-wall-backend/handlers/column" "progress-wall-backend/handlers/project" "progress-wall-backend/handlers/task" - "progress-wall-backend/handlers/user" "progress-wall-backend/handlers/team" + "progress-wall-backend/handlers/user" "progress-wall-backend/middleware" "progress-wall-backend/services" @@ -31,10 +31,19 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { // 配置CORS corsConfig := cors.DefaultConfig() - corsConfig.AllowOrigins = strings.Split(cfg.CORS.AllowOrigins, ",") corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} corsConfig.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization"} corsConfig.AllowCredentials = true + + // 在开发环境下允许所有Origin,避免跨域问题 + if cfg.Server.Mode == "release" { + corsConfig.AllowOrigins = strings.Split(cfg.CORS.AllowOrigins, ",") + } else { + corsConfig.AllowOriginFunc = func(origin string) bool { + return true + } + } + r.Use(cors.New(corsConfig)) permService := services.NewPermissionService(db) @@ -95,38 +104,38 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { projectHandler.GetTeamProjects, ) protected.GET("/projects", projectHandler.GetProjects) - protected.GET("/projects/:projectId", + protected.GET("/projects/:projectId", rbac.RequireProjectAccess("view", "projectId", "project"), projectHandler.GetProject, ) - protected.PUT("/projects/:projectId", + protected.PUT("/projects/:projectId", rbac.RequireProjectAccess("manage", "projectId", "project"), projectHandler.UpdateProject, ) - protected.DELETE("/projects/:projectId", + protected.DELETE("/projects/:projectId", rbac.RequireProjectAccess("manage", "projectId", "project"), projectHandler.DeleteProject, ) // 看板相关 protected.GET("/boards", boardHandler.GetBoards) - protected.GET("/projects/:projectId/boards", + protected.GET("/projects/:projectId/boards", rbac.RequireProjectAccess("view", "projectId", "project"), boardHandler.GetBoardsByProject, ) - protected.POST("/projects/:projectId/boards", + protected.POST("/projects/:projectId/boards", rbac.RequireProjectAccess("manage", "projectId", "project"), boardHandler.CreateBoard, ) - protected.GET("/boards/:boardId", + protected.GET("/boards/:boardId", rbac.RequireProjectAccess("view", "boardId", "board"), boardHandler.GetBoard, ) - protected.PUT("/boards/:boardId", + protected.PUT("/boards/:boardId", rbac.RequireProjectAccess("manage", "boardId", "board"), boardHandler.UpdateBoard, ) - protected.DELETE("/boards/:boardId", + protected.DELETE("/boards/:boardId", rbac.RequireProjectAccess("manage", "boardId", "board"), boardHandler.DeleteBoard, ) @@ -136,7 +145,7 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { rbac.RequireProjectAccess("view", "boardId", "board"), columnHandler.GetColumns, ) - protected.POST("/boards/:boardId/columns", + protected.POST("/boards/:boardId/columns", // Only admins can create columns rbac.RequireProjectAccess("manage", "boardId", "board"), columnHandler.CreateColumn, @@ -163,7 +172,7 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { rbac.RequireProjectAccess("view", "columnId", "column"), taskHandler.CreateTask, ) - protected.GET("/tasks/:taskId", + protected.GET("/tasks/:taskId", rbac.RequireProjectAccess("view", "taskId", "task"), taskHandler.GetTask, ) @@ -171,7 +180,7 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { rbac.RequireProjectAccess("view", "taskId", "task"), taskHandler.UpdateTask, ) - protected.DELETE("/tasks/:taskId", + protected.DELETE("/tasks/:taskId", rbac.RequireProjectAccess("view", "taskId", "task"), taskHandler.DeleteTask, ) @@ -179,7 +188,7 @@ func SetupRoutes(db *gorm.DB, cfg *config.Config) *gin.Engine { rbac.RequireProjectAccess("view", "taskId", "task"), taskHandler.MoveTask, ) - + // 看板活动日志 protected.GET("/boards/:boardId/activities", boardActivitiesHandler.GetBoardActivities) diff --git a/backend/services/board_service.go b/backend/services/board_service.go index 24d151b..64e0780 100644 --- a/backend/services/board_service.go +++ b/backend/services/board_service.go @@ -76,12 +76,63 @@ func (s *BoardService) GetBoardsByProjectID(projectID uint) ([]models.Board, err return boards, nil } -// CreateBoard 创建看板 +// CreateBoard 创建看板(带默认列) func (s *BoardService) CreateBoard(board *models.Board) error { - if err := s.db.Create(board).Error; err != nil { - return fmt.Errorf("创建看板失败: %v", err) - } - return nil + return s.db.Transaction(func(tx *gorm.DB) error { + if err := tx.Create(board).Error; err != nil { + return fmt.Errorf("创建看板失败: %v", err) + } + + // 初始化默认列 + defaultColumns := []models.Column{ + { + Name: "Backlog", + Description: "待办事项", + Color: "#6B7280", // Gray + Position: 1000, + BoardID: board.ID, + Status: models.ColumnStatusActive, + }, + { + Name: "Ready", + Description: "准备就绪", + Color: "#3B82F6", // Blue + Position: 2000, + BoardID: board.ID, + Status: models.ColumnStatusActive, + }, + { + Name: "In processing", + Description: "进行中", + Color: "#F59E0B", // Yellow + Position: 3000, + BoardID: board.ID, + Status: models.ColumnStatusActive, + }, + { + Name: "In review", + Description: "审核中", + Color: "#8B5CF6", // Purple + Position: 4000, + BoardID: board.ID, + Status: models.ColumnStatusActive, + }, + { + Name: "Done", + Description: "已完成", + Color: "#10B981", // Green + Position: 5000, + BoardID: board.ID, + Status: models.ColumnStatusActive, + }, + } + + if err := tx.Create(&defaultColumns).Error; err != nil { + return fmt.Errorf("创建默认列失败: %v", err) + } + + return nil + }) } // UpdateBoard 更新看板 diff --git a/frontend/src/components/features/CreateColumnDialog.vue b/frontend/src/components/features/CreateColumnDialog.vue new file mode 100644 index 0000000..a590f22 --- /dev/null +++ b/frontend/src/components/features/CreateColumnDialog.vue @@ -0,0 +1,115 @@ + + + + diff --git a/frontend/src/components/features/CreateTaskDialog.vue b/frontend/src/components/features/CreateTaskDialog.vue new file mode 100644 index 0000000..75df558 --- /dev/null +++ b/frontend/src/components/features/CreateTaskDialog.vue @@ -0,0 +1,119 @@ + + + + diff --git a/frontend/src/components/features/KanbanColumn.vue b/frontend/src/components/features/KanbanColumn.vue index 63e6411..55960b7 100644 --- a/frontend/src/components/features/KanbanColumn.vue +++ b/frontend/src/components/features/KanbanColumn.vue @@ -1,39 +1,50 @@ @@ -51,7 +62,8 @@ const props = defineProps() const emit = defineEmits<{ 'select-task': [task: any] - 'delete-task': [taskId: string] + 'delete-task': [taskId: number] + 'add-task': [columnId: number] }>() const kanbanStore = useKanbanStore() @@ -60,35 +72,32 @@ const kanbanStore = useKanbanStore() const onDragEnd = async (event: any) => { const { item, to, from, newIndex, oldIndex } = event - // 获取被拖拽的任务ID - item 本身就是 Card 元素 - const taskId = item.getAttribute('data-task-id') - console.log('拖拽结束 - taskId:', taskId, 'event:', event) - if (!taskId) { + // 获取被拖拽的任务ID + const taskIdStr = item.getAttribute('data-task-id') + if (!taskIdStr) { console.error('未找到 taskId') return } + const taskId = parseInt(taskIdStr, 10) // 获取目标列ID const targetColumnElement = to.closest('[data-column-id]') - const targetColumnId = targetColumnElement?.getAttribute('data-column-id') - console.log('目标列 - targetColumnId:', targetColumnId) - if (!targetColumnId) { + const targetColumnIdStr = targetColumnElement?.getAttribute('data-column-id') + if (!targetColumnIdStr) { console.error('未找到 targetColumnId') return } + const targetColumnId = parseInt(targetColumnIdStr, 10) // 如果是同一列内的重新排序,或者跨列移动 if (from !== to || newIndex !== oldIndex) { - console.log('开始调用API - taskId:', taskId, 'targetColumnId:', targetColumnId, 'newIndex:', newIndex) + console.log('拖拽移动 - taskId:', taskId, 'to column:', targetColumnId, 'index:', newIndex) try { await kanbanStore.moveTaskWithDrag(taskId, targetColumnId, newIndex) - console.log('API调用成功') } catch (error) { console.error('拖拽移动任务失败:', error) - // 这里可以添加错误提示 + // 可以在这里添加 toast 提示 } - } else { - console.log('未触发移动 - 位置未改变') } } @@ -102,25 +111,12 @@ const onDragEnd = async (event: any) => { } :deep(.chosen-task) { - transform: rotate(5deg); + transform: rotate(2deg); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); } :deep(.drag-task) { - transform: rotate(5deg); - opacity: 0.8; -} - -/* 拖拽区域样式 */ -.sortable-ghost { - opacity: 0.5; -} - -.sortable-chosen { - opacity: 0.8; -} - -.sortable-drag { - opacity: 0.6; + transform: rotate(2deg); + opacity: 0.9; } diff --git a/frontend/src/components/features/TaskCard.vue b/frontend/src/components/features/TaskCard.vue index e531e6e..30befa0 100644 --- a/frontend/src/components/features/TaskCard.vue +++ b/frontend/src/components/features/TaskCard.vue @@ -1,17 +1,17 @@