feat: support sorting tasks and exit dialogs on ESC key (#148)
This commit is contained in:
@@ -337,7 +337,10 @@ func (tc *TaskController) GetTasks(c *gin.Context) {
|
|||||||
agentID = &agentIDStr
|
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)
|
utils.PaginatedResponse(c, vo.ToTaskVOListFromModels(tasks), total, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +705,7 @@ func (tc *TaskController) BatchDeleteByQuery(c *gin.Context) {
|
|||||||
agentID = &agentIDStr
|
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 {
|
if len(tasks) == 0 {
|
||||||
utils.Success(c, gin.H{"count": 0})
|
utils.Success(c, gin.H{"count": 0})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func (ts *TaskService) GetTasks() []models.Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTasksWithPagination 分页获取任务列表
|
// 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 tasks []models.Task
|
||||||
var total int64
|
var total int64
|
||||||
|
|
||||||
@@ -154,8 +154,20 @@ func (ts *TaskService) GetTasksWithPagination(page, pageSize int, name string, a
|
|||||||
query = query.Where("agent_id = ?", *agentID)
|
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.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)
|
ts.loadTagsAndEnvs(tasks)
|
||||||
|
|
||||||
return tasks, total
|
return tasks, total
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const api = {
|
|||||||
request('/auth/register', { method: 'POST', body: JSON.stringify(data) })
|
request('/auth/register', { method: 'POST', body: JSON.stringify(data) })
|
||||||
},
|
},
|
||||||
tasks: {
|
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()
|
const query = new URLSearchParams()
|
||||||
if (params?.page) query.set('page', String(params.page))
|
if (params?.page) query.set('page', String(params.page))
|
||||||
if (params?.page_size) query.set('page_size', String(params.page_size))
|
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?.tags) query.set('tags', params.tags)
|
||||||
if (params?.agent_id) query.set('agent_id', params.agent_id)
|
if (params?.agent_id) query.set('agent_id', params.agent_id)
|
||||||
if (params?.type) query.set('type', params.type)
|
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<TaskListResponse>(`/tasks?${query}`)
|
return request<TaskListResponse>(`/tasks?${query}`)
|
||||||
},
|
},
|
||||||
create: (data: Partial<Task>) => request<Task>('/tasks', { method: 'POST', body: JSON.stringify(data) }),
|
create: (data: Partial<Task>) => request<Task>('/tasks', { method: 'POST', body: JSON.stringify(data) }),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "reka-ui"
|
} from "reka-ui"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import DialogOverlay from "./DialogOverlay.vue"
|
import DialogOverlay from "./DialogOverlay.vue"
|
||||||
|
import { onMounted, onUnmounted, ref } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
@@ -24,17 +25,46 @@ const emits = defineEmits<DialogContentEmits>()
|
|||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
|
||||||
|
const contentRef = ref<any>(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)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogContent
|
<DialogContent
|
||||||
|
ref="contentRef"
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] max-h-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg overflow-y-auto custom-scrollbar',
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] max-h-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg overflow-y-auto custom-scrollbar focus:outline-none',
|
||||||
props.class,
|
props.class,
|
||||||
)"
|
)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,19 +39,28 @@ function toggleBodyScroll(lock: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape' && props.open) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听打开状态
|
// 监听打开状态
|
||||||
watch(() => props.open, (val) => {
|
watch(() => props.open, (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
isFullscreen.value = false
|
isFullscreen.value = false
|
||||||
toggleBodyScroll(true)
|
toggleBodyScroll(true)
|
||||||
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
} else {
|
} else {
|
||||||
toggleBodyScroll(false)
|
toggleBodyScroll(false)
|
||||||
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 确保组件卸载时恢复滚动
|
// 确保组件卸载时恢复滚动并注销事件监听
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
toggleBodyScroll(false)
|
toggleBodyScroll(false)
|
||||||
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Pagination from '@/components/Pagination.vue'
|
|||||||
import TaskDialog from './TaskDialog.vue'
|
import TaskDialog from './TaskDialog.vue'
|
||||||
import RepoDialog from './RepoDialog.vue'
|
import RepoDialog from './RepoDialog.vue'
|
||||||
import LogViewer from '@/views/history/LogViewer.vue'
|
import LogViewer from '@/views/history/LogViewer.vue'
|
||||||
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X, Loader2, RefreshCw, Wifi, WifiOff, Zap, ZapOff, Copy, Tag, ChevronDown, Pin, PinOff, MoreHorizontal, CalendarClock, Wrench } from 'lucide-vue-next'
|
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X, Loader2, RefreshCw, Wifi, WifiOff, Zap, ZapOff, Copy, Tag, ChevronDown, Pin, PinOff, MoreHorizontal, CalendarClock, Wrench, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-vue-next'
|
||||||
import TagInput from '@/components/TagInput.vue'
|
import TagInput from '@/components/TagInput.vue'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||||
@@ -47,6 +47,28 @@ const showRepoDialog = ref(false)
|
|||||||
const editingTask = ref<Partial<Task>>({})
|
const editingTask = ref<Partial<Task>>({})
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
|
|
||||||
|
const sortBy = ref('created_at')
|
||||||
|
const order = ref('desc')
|
||||||
|
|
||||||
|
function toggleSort(field: string) {
|
||||||
|
if (sortBy.value === field) {
|
||||||
|
order.value = order.value === 'asc' ? 'desc' : 'asc'
|
||||||
|
} else {
|
||||||
|
sortBy.value = field
|
||||||
|
order.value = 'desc'
|
||||||
|
}
|
||||||
|
currentPage.value = 1
|
||||||
|
loadTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSortChange(val: string) {
|
||||||
|
const parts = val.split(':')
|
||||||
|
sortBy.value = parts[0] || 'created_at'
|
||||||
|
order.value = parts[1] || 'desc'
|
||||||
|
currentPage.value = 1
|
||||||
|
loadTasks()
|
||||||
|
}
|
||||||
|
|
||||||
const showDeleteDialog = ref(false)
|
const showDeleteDialog = ref(false)
|
||||||
const deleteTaskId = ref<string | null>(null)
|
const deleteTaskId = ref<string | null>(null)
|
||||||
const deleteFiles = ref(false)
|
const deleteFiles = ref(false)
|
||||||
@@ -67,7 +89,7 @@ const agentMap = computed(() => {
|
|||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前筛选的 Agent 名称
|
// 当前筛选 of Agent 名称
|
||||||
const filterAgentName = computed(() => {
|
const filterAgentName = computed(() => {
|
||||||
if (!filterAgentId.value) return ''
|
if (!filterAgentId.value) return ''
|
||||||
const agent = agentMap.value[filterAgentId.value]
|
const agent = agentMap.value[filterAgentId.value]
|
||||||
@@ -97,7 +119,9 @@ async function loadTasks() {
|
|||||||
name: filterName.value || undefined,
|
name: filterName.value || undefined,
|
||||||
tags: filterTags.value || undefined,
|
tags: filterTags.value || undefined,
|
||||||
type: filterType.value === 'all' ? undefined : filterType.value,
|
type: filterType.value === 'all' ? undefined : filterType.value,
|
||||||
agent_id: filterAgentId.value || undefined
|
agent_id: filterAgentId.value || undefined,
|
||||||
|
sort_by: sortBy.value || undefined,
|
||||||
|
order: order.value || undefined
|
||||||
})
|
})
|
||||||
tasks.value = res.data
|
tasks.value = res.data
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
@@ -478,7 +502,9 @@ async function saveView() {
|
|||||||
name: filterName.value,
|
name: filterName.value,
|
||||||
tags: filterTags.value,
|
tags: filterTags.value,
|
||||||
agent_id: filterAgentId.value,
|
agent_id: filterAgentId.value,
|
||||||
type: filterType.value
|
type: filterType.value,
|
||||||
|
sort_by: sortBy.value,
|
||||||
|
order: order.value
|
||||||
},
|
},
|
||||||
isDefault: false
|
isDefault: false
|
||||||
}
|
}
|
||||||
@@ -504,6 +530,8 @@ function applyViewWithoutSearch(view: any) {
|
|||||||
filterTags.value = view.query.tags || ''
|
filterTags.value = view.query.tags || ''
|
||||||
filterAgentId.value = view.query.agent_id || null
|
filterAgentId.value = view.query.agent_id || null
|
||||||
filterType.value = view.query.type || TASK_TYPE.NORMAL
|
filterType.value = view.query.type || TASK_TYPE.NORMAL
|
||||||
|
sortBy.value = view.query.sort_by || 'created_at'
|
||||||
|
order.value = view.query.order || 'desc'
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyView(view: any) {
|
function applyView(view: any) {
|
||||||
@@ -675,8 +703,8 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 w-full sm:w-auto sm:justify-end">
|
<div class="flex items-center gap-2 w-full sm:w-auto sm:justify-end">
|
||||||
<!-- 移动端类型切换 -->
|
<!-- 移动端类型与排序切换 -->
|
||||||
<div class="xl:hidden flex-1 shrink-0">
|
<div class="xl:hidden flex items-center gap-2 flex-1 shrink-0">
|
||||||
<Select v-model="filterType" @update:model-value="(_v: any) => handleTypeChange()">
|
<Select v-model="filterType" @update:model-value="(_v: any) => handleTypeChange()">
|
||||||
<SelectTrigger class="h-9 w-full text-sm bg-muted/20 border-muted-foreground/10">
|
<SelectTrigger class="h-9 w-full text-sm bg-muted/20 border-muted-foreground/10">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
@@ -686,6 +714,21 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
<SelectItem :value="TASK_TYPE.REPO">仓库同步</SelectItem>
|
<SelectItem :value="TASK_TYPE.REPO">仓库同步</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
<Select :model-value="`${sortBy}:${order}`" @update:model-value="(val: any) => handleSortChange(val)">
|
||||||
|
<SelectTrigger class="h-9 w-full text-sm bg-muted/20 border-muted-foreground/10">
|
||||||
|
<SelectValue placeholder="排序规则" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="created_at:desc">创建时间 (最晚优先)</SelectItem>
|
||||||
|
<SelectItem value="created_at:asc">创建时间 (最早优先)</SelectItem>
|
||||||
|
<SelectItem value="name:asc">名称 (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="name:desc">名称 (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="next_run:asc">下次执行 (先-后)</SelectItem>
|
||||||
|
<SelectItem value="next_run:desc">下次执行 (后-先)</SelectItem>
|
||||||
|
<SelectItem value="enabled:desc">状态 (启用优先)</SelectItem>
|
||||||
|
<SelectItem value="enabled:asc">状态 (禁用优先)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="filterAgentId"
|
<div v-if="filterAgentId"
|
||||||
@@ -742,7 +785,12 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
|
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
|
||||||
<span class="w-12 shrink-0 pl-1">序号</span>
|
<span class="w-12 shrink-0 pl-1">序号</span>
|
||||||
<span class="w-8 shrink-0 text-center">类型</span>
|
<span class="w-8 shrink-0 text-center">类型</span>
|
||||||
<span class="w-56 shrink-0">名称</span>
|
<span class="w-56 shrink-0 flex items-center gap-1 cursor-pointer select-none hover:text-foreground transition-colors" @click="toggleSort('name')">
|
||||||
|
名称
|
||||||
|
<ArrowUpDown v-if="sortBy !== 'name'" class="h-3 w-3 opacity-40 shrink-0" />
|
||||||
|
<ArrowUp v-else-if="order === 'asc'" class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
<ArrowDown v-else class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
</span>
|
||||||
<span class="w-32 shrink-0">执行位置</span>
|
<span class="w-32 shrink-0">执行位置</span>
|
||||||
<span class="flex-1 min-w-0 flex items-center gap-1.5 line-clamp-1">
|
<span class="flex-1 min-w-0 flex items-center gap-1.5 line-clamp-1">
|
||||||
<GitBranch v-if="filterType === TASK_TYPE.REPO" class="h-3.5 w-3.5 opacity-50" />
|
<GitBranch v-if="filterType === TASK_TYPE.REPO" class="h-3.5 w-3.5 opacity-50" />
|
||||||
@@ -750,8 +798,18 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
{{ filterType === TASK_TYPE.REPO ? '仓库地址' : '命令内容' }}
|
{{ filterType === TASK_TYPE.REPO ? '仓库地址' : '命令内容' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="w-28 shrink-0">{{ filterType === TASK_TYPE.REPO ? '同步周期' : '定时规则' }}</span>
|
<span class="w-28 shrink-0">{{ filterType === TASK_TYPE.REPO ? '同步周期' : '定时规则' }}</span>
|
||||||
<span class="w-40 shrink-0">执行时间</span>
|
<span class="w-40 shrink-0 flex items-center gap-1 cursor-pointer select-none hover:text-foreground transition-colors" @click="toggleSort('next_run')">
|
||||||
<span class="w-8 shrink-0 text-center">状态</span>
|
执行时间
|
||||||
|
<ArrowUpDown v-if="sortBy !== 'next_run'" class="h-3 w-3 opacity-40 shrink-0" />
|
||||||
|
<ArrowUp v-else-if="order === 'asc'" class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
<ArrowDown v-else class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
</span>
|
||||||
|
<span class="w-14 shrink-0 text-center flex items-center justify-center gap-1 cursor-pointer select-none hover:text-foreground transition-colors" @click="toggleSort('enabled')">
|
||||||
|
状态
|
||||||
|
<ArrowUpDown v-if="sortBy !== 'enabled'" class="h-3 w-3 opacity-40 shrink-0" />
|
||||||
|
<ArrowUp v-else-if="order === 'asc'" class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
<ArrowDown v-else class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
</span>
|
||||||
<span class="w-24 shrink-0 text-center">操作</span>
|
<span class="w-24 shrink-0 text-center">操作</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@@ -798,7 +856,7 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
<span class="truncate">上: {{ task.last_run || '-' }}</span>
|
<span class="truncate">上: {{ task.last_run || '-' }}</span>
|
||||||
<span class="truncate">下: {{ task.next_run || '-' }}</span>
|
<span class="truncate">下: {{ task.next_run || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="w-8 flex justify-center shrink-0 cursor-pointer group" @click="toggleTask(task, !task.enabled)">
|
<span class="w-14 flex justify-center shrink-0 cursor-pointer group" @click="toggleTask(task, !task.enabled)">
|
||||||
<div v-if="task.enabled" class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20">
|
<div v-if="task.enabled" class="h-6 w-6 rounded-md bg-green-500/10 flex items-center justify-center group-hover:bg-green-500/20">
|
||||||
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
|
<Zap class="h-3.5 w-3.5 text-green-500 fill-green-500" />
|
||||||
</div>
|
</div>
|
||||||
@@ -850,14 +908,23 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
|
|
||||||
<!-- ========== 2. 中屏布局 (Medium 640px - 1280px) ========== -->
|
<!-- ========== 2. 中屏布局 (Medium 640px - 1280px) ========== -->
|
||||||
<div class="hidden sm:block xl:hidden">
|
<div class="hidden sm:block xl:hidden">
|
||||||
<!-- 表头 -->
|
|
||||||
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
|
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
|
||||||
<span class="w-12 shrink-0 pl-1">序号</span>
|
<span class="w-12 shrink-0 pl-1">序号</span>
|
||||||
<span class="w-48 shrink-0">任务信息</span>
|
<span class="w-48 shrink-0 flex items-center gap-1 cursor-pointer select-none hover:text-foreground transition-colors" @click="toggleSort('name')">
|
||||||
|
任务信息
|
||||||
|
<ArrowUpDown v-if="sortBy !== 'name'" class="h-3 w-3 opacity-40 shrink-0" />
|
||||||
|
<ArrowUp v-else-if="order === 'asc'" class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
<ArrowDown v-else class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
</span>
|
||||||
<span class="flex-1 min-w-0">
|
<span class="flex-1 min-w-0">
|
||||||
{{ filterType === TASK_TYPE.REPO ? '仓库地址' : '命令内容' }}
|
{{ filterType === TASK_TYPE.REPO ? '仓库地址' : '命令内容' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="w-8 shrink-0 text-center">状态</span>
|
<span class="w-14 shrink-0 text-center flex items-center justify-center gap-1 cursor-pointer select-none hover:text-foreground transition-colors" @click="toggleSort('enabled')">
|
||||||
|
状态
|
||||||
|
<ArrowUpDown v-if="sortBy !== 'enabled'" class="h-3 w-3 opacity-40 shrink-0" />
|
||||||
|
<ArrowUp v-else-if="order === 'asc'" class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
<ArrowDown v-else class="h-3 w-3 text-primary shrink-0" />
|
||||||
|
</span>
|
||||||
<span class="w-24 shrink-0 text-center">操作</span>
|
<span class="w-24 shrink-0 text-center">操作</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@@ -886,7 +953,7 @@ watch(() => route.query.agent_id, (newVal: any) => {
|
|||||||
<code class="flex-1 min-w-0 text-[11px] text-muted-foreground bg-muted/20 px-2 py-1 rounded truncate">
|
<code class="flex-1 min-w-0 text-[11px] text-muted-foreground bg-muted/20 px-2 py-1 rounded truncate">
|
||||||
{{ task.command }}
|
{{ task.command }}
|
||||||
</code>
|
</code>
|
||||||
<span class="w-8 flex justify-center shrink-0 cursor-pointer group" @click="toggleTask(task, !task.enabled)">
|
<span class="w-14 flex justify-center shrink-0 cursor-pointer group" @click="toggleTask(task, !task.enabled)">
|
||||||
<div v-if="task.enabled" class="h-6 w-6 rounded-md bg-green-500/5 flex items-center justify-center">
|
<div v-if="task.enabled" class="h-6 w-6 rounded-md bg-green-500/5 flex items-center justify-center">
|
||||||
<Zap class="h-3.5 w-3.5 text-green-500" />
|
<Zap class="h-3.5 w-3.5 text-green-500" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user