From e56993028760444308d493981f0791ac33527b13 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Fri, 19 Sep 2025 01:45:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(QueueItemManager,=20TimeSetManager):=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=9A=E6=97=B6=E5=88=97=E8=A1=A8=E5=92=8C?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8=E7=9A=84=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/queue/QueueItemManager.vue | 289 ++++++++++++--- .../src/components/queue/TimeSetManager.vue | 337 +++++++++++++++--- 2 files changed, 525 insertions(+), 101 deletions(-) diff --git a/frontend/src/components/queue/QueueItemManager.vue b/frontend/src/components/queue/QueueItemManager.vue index 8a2ff04..c0c0c18 100644 --- a/frontend/src/components/queue/QueueItemManager.vue +++ b/frontend/src/components/queue/QueueItemManager.vue @@ -11,49 +11,67 @@ - - - - + + @@ -61,6 +79,7 @@ import { onMounted, ref, watch } from 'vue' import { message } from 'ant-design-vue' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue' +import draggable from 'vuedraggable' import { Service } from '@/api' // Props @@ -141,7 +160,7 @@ const loadOptions = async () => { const updateQueueItemScript = async (record: any) => { try { loading.value = true - + const response = await Service.updateItemApiQueueItemUpdatePost({ queueId: props.queueId, queueItemId: record.id, @@ -170,7 +189,7 @@ const updateQueueItemScript = async (record: any) => { const addQueueItem = async () => { try { loading.value = true - + // 直接创建队列项,默认ScriptId为null(未选择) const createResponse = await Service.addItemApiQueueItemAddPost({ queueId: props.queueId, @@ -211,6 +230,44 @@ const deleteQueueItem = async (itemId: string) => { } } +// 拖拽结束处理函数 +const onDragEnd = async (evt: any) => { + // 如果位置没有变化,直接返回 + if (evt.oldIndex === evt.newIndex) { + return + } + + try { + loading.value = true + + // 构造排序后的ID列表 + const sortedIds = queueItems.value.map(item => item.id) + + // 调用排序API + const response = await Service.reorderItemApiQueueItemOrderPost({ + queueId: props.queueId, + indexList: sortedIds, + }) + + if (response.code === 200) { + message.success('任务顺序已更新') + // 刷新数据以确保与服务器同步 + emit('refresh') + } else { + message.error('更新任务顺序失败: ' + (response.message || '未知错误')) + // 如果失败,刷新数据恢复原状态 + emit('refresh') + } + } catch (error: any) { + console.error('拖拽排序失败:', error) + message.error('更新任务顺序失败: ' + (error?.message || '网络错误')) + // 如果失败,刷新数据恢复原状态 + emit('refresh') + } finally { + loading.value = false + } +} + // 初始化 onMounted(() => { loadOptions() @@ -474,6 +531,124 @@ onMounted(() => { padding: 40px 0; } +/* 拖拽表格样式 */ +.draggable-table-container { + width: 100%; + border: 1px solid var(--ant-color-border); + border-radius: 6px; + overflow: hidden; +} + +.draggable-table-header { + display: flex; + background-color: var(--ant-color-fill-quaternary); + border-bottom: 1px solid var(--ant-color-border); +} + +.header-cell { + padding: 12px 16px; + font-weight: 600; + color: var(--ant-color-text); + text-align: center; + border-right: 1px solid var(--ant-color-border); +} + +.header-cell:last-child { + border-right: none; +} + +.index-cell { + width: 80px; + min-width: 80px; + max-width: 80px; +} + +.script-cell { + flex: 1; + min-width: 200px; +} + +.actions-cell { + width: 180px; + min-width: 180px; + max-width: 180px; +} + +.draggable-container { + min-height: 60px; +} + +.draggable-row { + display: flex; + align-items: center; + background: var(--ant-color-bg-container); + border-bottom: 1px solid var(--ant-color-border); + transition: all 0.2s ease; + cursor: move; +} + +.draggable-row:last-child { + border-bottom: none; +} + +.draggable-row:hover { + background-color: var(--ant-color-fill-quaternary); +} + +.draggable-row.row-dragging { + cursor: not-allowed; +} + +.row-cell { + padding: 12px 16px; + text-align: center; + border-right: 1px solid var(--ant-color-border); + display: flex; + align-items: center; + justify-content: center; +} + +.row-cell:last-child { + border-right: none; +} + +.row-cell.index-cell { + width: 80px; + min-width: 80px; + max-width: 80px; + font-weight: 500; + color: var(--ant-color-text-secondary); +} + +.row-cell.script-cell { + flex: 1; + min-width: 200px; +} + +.row-cell.actions-cell { + width: 180px; + min-width: 180px; + max-width: 180px; +} + +/* 拖拽状态样式 */ +.ghost { + opacity: 0.5; + background: var(--ant-color-primary-bg); + border: 2px dashed var(--ant-color-primary); +} + +.chosen { + background: var(--ant-color-primary-bg-hover); + transform: scale(1.02); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.drag { + transform: rotate(5deg); + opacity: 0.8; +} + /* 响应式设计 */ @media (max-width: 1200px) { .queue-items-grid { @@ -489,6 +664,30 @@ onMounted(() => { .queue-item-card-item { padding: 12px; } + + .draggable-row { + flex-direction: column; + align-items: stretch; + } + + .row-cell, + .header-cell { + border-right: none; + border-bottom: 1px solid var(--ant-color-border); + } + + .row-cell:last-child, + .header-cell:last-child { + border-bottom: none; + } + + .index-cell, + .script-cell, + .actions-cell { + width: 100% !important; + min-width: auto !important; + max-width: none !important; + } } /* 标签样式 */ @@ -589,4 +788,4 @@ onMounted(() => { .script-select :deep(.ant-select-item-option-content) { font-size: 13px !important; } - \ No newline at end of file + diff --git a/frontend/src/components/queue/TimeSetManager.vue b/frontend/src/components/queue/TimeSetManager.vue index 469ff48..085b48f 100644 --- a/frontend/src/components/queue/TimeSetManager.vue +++ b/frontend/src/components/queue/TimeSetManager.vue @@ -16,60 +16,78 @@ - - - - - - + + @@ -77,6 +95,7 @@ import { ref, watch } from 'vue' import { message } from 'ant-design-vue' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue' +import draggable from 'vuedraggable' import { Service } from '@/api' import dayjs from 'dayjs' @@ -159,7 +178,7 @@ const timeSets = ref([...props.timeSets]) const processTimeSets = (rawTimeSets: any[]) => { return rawTimeSets.map(item => ({ ...item, - timeValue: parseTimeString(item.time) + timeValue: parseTimeString(item.time), })) } @@ -221,7 +240,7 @@ const addTimeSet = async () => { const updateTimeSetTime = async (timeSet: any) => { try { const timeString = formatTimeValue(timeSet.timeValue) - + const response = await Service.updateTimeSetApiQueueTimeUpdatePost({ queueId: props.queueId, timeSetId: timeSet.id, @@ -297,6 +316,44 @@ const deleteTimeSet = async (timeSetId: string) => { message.error('删除定时项失败: ' + (error?.message || '网络错误')) } } + +// 拖拽结束处理函数 +const onDragEnd = async (evt: any) => { + // 如果位置没有变化,直接返回 + if (evt.oldIndex === evt.newIndex) { + return + } + + try { + loading.value = true + + // 构造排序后的ID列表 + const sortedIds = timeSets.value.map(item => item.id) + + // 调用排序API + const response = await Service.reorderTimeSetApiQueueTimeOrderPost({ + queueId: props.queueId, + indexList: sortedIds, + }) + + if (response.code === 200) { + message.success('定时顺序已更新') + // 刷新数据以确保与服务器同步 + emit('refresh') + } else { + message.error('更新定时顺序失败: ' + (response.message || '未知错误')) + // 如果失败,刷新数据恢复原状态 + emit('refresh') + } + } catch (error: any) { + console.error('拖拽排序失败:', error) + message.error('更新定时顺序失败: ' + (error?.message || '网络错误')) + // 如果失败,刷新数据恢复原状态 + emit('refresh') + } finally { + loading.value = false + } +} @@ -831,7 +1054,9 @@ const deleteTimeSet = async (timeSetId: string) => { transition: background 0.2s ease; } -[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover { +[data-theme='dark'] + .ant-picker-dropdown + .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.45); }