diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index ad0ba0f..583f658 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -337,7 +337,10 @@ func (tc *TaskController) GetTasks(c *gin.Context) { agentID = &agentIDStr } - tasks, total := tc.taskService.GetTasksWithPagination(p.Page, p.PageSize, name, agentID, tags, taskType) + sortBy := c.DefaultQuery("sort_by", "") + order := c.DefaultQuery("order", "") + + tasks, total := tc.taskService.GetTasksWithPagination(p.Page, p.PageSize, name, agentID, tags, taskType, sortBy, order) utils.PaginatedResponse(c, vo.ToTaskVOListFromModels(tasks), total, p) } @@ -702,7 +705,7 @@ func (tc *TaskController) BatchDeleteByQuery(c *gin.Context) { agentID = &agentIDStr } - tasks, _ := tc.taskService.GetTasksWithPagination(1, 999999, name, agentID, tags, taskType) + tasks, _ := tc.taskService.GetTasksWithPagination(1, 999999, name, agentID, tags, taskType, "", "") if len(tasks) == 0 { utils.Success(c, gin.H{"count": 0}) return diff --git a/internal/services/tasks/task_service.go b/internal/services/tasks/task_service.go index 2d9113b..5e9c2bb 100644 --- a/internal/services/tasks/task_service.go +++ b/internal/services/tasks/task_service.go @@ -111,7 +111,7 @@ func (ts *TaskService) GetTasks() []models.Task { } // GetTasksWithPagination 分页获取任务列表 -func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, agentID *string, tags string, taskType string) ([]models.Task, int64) { +func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, agentID *string, tags string, taskType string, sortBy string, order string) ([]models.Task, int64) { var tasks []models.Task var total int64 @@ -154,8 +154,20 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, a query = query.Where("agent_id = ?", *agentID) } + sortColumn := "created_at" + if sortBy != "" { + switch sortBy { + case "name", "next_run", "last_run", "created_at", "enabled": + sortColumn = sortBy + } + } + sortOrder := "DESC" + if strings.ToUpper(order) == "ASC" { + sortOrder = "ASC" + } + query.Count(&total) - query.Order("pin_type DESC, created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&tasks) + query.Order("pin_type DESC, " + sortColumn + " " + sortOrder).Offset((page - 1) * pageSize).Limit(pageSize).Find(&tasks) ts.loadTagsAndEnvs(tasks) return tasks, total diff --git a/web/src/api/index.ts b/web/src/api/index.ts index c00f4e5..e5f9ebc 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -140,7 +140,7 @@ export const api = { request('/auth/register', { method: 'POST', body: JSON.stringify(data) }) }, tasks: { - list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: string; tags?: string; type?: string }) => { + list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: string; tags?: string; type?: string; sort_by?: string; order?: string }) => { const query = new URLSearchParams() if (params?.page) query.set('page', String(params.page)) if (params?.page_size) query.set('page_size', String(params.page_size)) @@ -148,6 +148,8 @@ export const api = { if (params?.tags) query.set('tags', params.tags) if (params?.agent_id) query.set('agent_id', params.agent_id) if (params?.type) query.set('type', params.type) + if (params?.sort_by) query.set('sort_by', params.sort_by) + if (params?.order) query.set('order', params.order) return request(`/tasks?${query}`) }, create: (data: Partial) => request('/tasks', { method: 'POST', body: JSON.stringify(data) }), diff --git a/web/src/components/ui/dialog/DialogContent.vue b/web/src/components/ui/dialog/DialogContent.vue index 99dae59..c131bf1 100644 --- a/web/src/components/ui/dialog/DialogContent.vue +++ b/web/src/components/ui/dialog/DialogContent.vue @@ -11,6 +11,7 @@ import { } from "reka-ui" import { cn } from "@/lib/utils" import DialogOverlay from "./DialogOverlay.vue" +import { onMounted, onUnmounted, ref } from "vue" defineOptions({ inheritAttrs: false, @@ -24,17 +25,46 @@ const emits = defineEmits() const delegatedProps = reactiveOmit(props, "class") const forwarded = useForwardPropsEmits(delegatedProps, emits) + +const contentRef = ref(null) + +const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + const contents = document.querySelectorAll('[data-slot="dialog-content"]') + if (contents.length > 0) { + const topContent = contents[contents.length - 1] + const el = contentRef.value?.$el || contentRef.value + if (el && el === topContent) { + const closeBtn = el.querySelector('[data-slot="dialog-close"]') as HTMLElement + if (closeBtn) { + closeBtn.click() + } else { + emits('escapeKeyDown', event) + } + } + } + } +} + +onMounted(() => { + window.addEventListener('keydown', handleKeyDown, true) +}) + +onUnmounted(() => { + window.removeEventListener('keydown', handleKeyDown, true) +})