refactor(queue): 优化队列管理页面的布局和交互,增加开关配置功能
This commit is contained in:
@@ -18,6 +18,9 @@
|
|||||||
size="middle"
|
size="middle"
|
||||||
:scroll="{ x: 600 }"
|
:scroll="{ x: 600 }"
|
||||||
>
|
>
|
||||||
|
<template #emptyText>
|
||||||
|
<span>暂无队列项</span>
|
||||||
|
</template>
|
||||||
<template #bodyCell="{ column, record, index }">
|
<template #bodyCell="{ column, record, index }">
|
||||||
<template v-if="column.key === 'index'"> 第{{ index + 1 }}个脚本 </template>
|
<template v-if="column.key === 'index'"> 第{{ index + 1 }}个脚本 </template>
|
||||||
<template v-else-if="column.key === 'script'">
|
<template v-else-if="column.key === 'script'">
|
||||||
@@ -45,9 +48,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
|
||||||
<div v-if="!queueItems.length && !loading" class="empty-state">
|
|
||||||
<a-empty description="暂无队列项数据" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 队列项编辑弹窗 -->
|
<!-- 队列项编辑弹窗 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
size="middle"
|
size="middle"
|
||||||
:scroll="{ x: 800 }"
|
:scroll="{ x: 800 }"
|
||||||
>
|
>
|
||||||
|
<template #emptyText>
|
||||||
|
<span>暂无定时项</span>
|
||||||
|
</template>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'enabled'">
|
<template v-if="column.key === 'enabled'">
|
||||||
<a-switch
|
<a-switch
|
||||||
@@ -58,10 +61,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
|
||||||
<div v-if="!timeSets.length && !loading" class="empty-state">
|
|
||||||
<a-empty :description="!props.queueId ? '请先选择一个队列' : '暂无定时项数据'" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 定时项编辑弹窗 -->
|
<!-- 定时项编辑弹窗 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:open="modalVisible"
|
v-model:open="modalVisible"
|
||||||
@@ -373,10 +372,6 @@ const deleteTimeSet = async (timeSetId: string) => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表格样式优化 */
|
/* 表格样式优化 */
|
||||||
:deep(.ant-table-tbody > tr > td) {
|
:deep(.ant-table-tbody > tr > td) {
|
||||||
|
|||||||
@@ -1,131 +1,196 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="loading" class="loading-box">
|
<!-- 加载状态 -->
|
||||||
<a-spin tip="加载中,请稍候..." size="large" />
|
<div v-if="loading" class="loading-container">
|
||||||
|
<a-spin size="large" tip="加载中,请稍候..." />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="queue-content">
|
<!-- 主要内容 -->
|
||||||
<!-- 队列头部 -->
|
<div v-else class="queue-main">
|
||||||
|
<!-- 页面头部 -->
|
||||||
<div class="queue-header">
|
<div class="queue-header">
|
||||||
<div class="header-title">
|
<div class="header-left">
|
||||||
<h1>调度队列</h1>
|
<h1 class="page-title">调度队列</h1>
|
||||||
|
<p class="page-description">管理您的自动化调度队列和任务配置</p>
|
||||||
</div>
|
</div>
|
||||||
<a-space size="middle">
|
<div class="header-actions">
|
||||||
|
<a-space size="middle">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="handleAddQueue"
|
||||||
|
v-if="queueList.length > 0 || currentQueueData"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<PlusOutlined />
|
||||||
|
</template>
|
||||||
|
新建队列
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="queueList.length > 0"
|
||||||
|
title="确定要删除这个队列吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleRemoveQueue(activeQueueId)"
|
||||||
|
>
|
||||||
|
<a-button danger size="large" :disabled="!activeQueueId">
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
删除当前队列
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button size="large" @click="handleRefresh">
|
||||||
|
<template #icon>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</template>
|
||||||
|
刷新
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="!queueList.length || !currentQueueData" class="empty-state">
|
||||||
|
<a-empty
|
||||||
|
image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg"
|
||||||
|
:image-style="{ height: '120px' }"
|
||||||
|
description="当前没有队列"
|
||||||
|
>
|
||||||
|
<template #description>
|
||||||
|
<span class="empty-description">
|
||||||
|
您还没有创建任何调度队列,点击下方按钮来创建您的第一个队列
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-button type="primary" size="large" @click="handleAddQueue">
|
<a-button type="primary" size="large" @click="handleAddQueue">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
</template>
|
</template>
|
||||||
新建队列
|
新建队列
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button size="large" @click="handleRefresh">
|
</a-empty>
|
||||||
<template #icon>
|
|
||||||
<ReloadOutlined />
|
|
||||||
</template>
|
|
||||||
刷新
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 如果没有计划,显示占位符 -->
|
|
||||||
<!-- 如果没有队列,显示占位符 -->
|
|
||||||
<div v-if="!queueList.length || !currentQueueData" class="placeholder-container">
|
|
||||||
<div class="placeholder-content">
|
|
||||||
<h2>当前没有队列</h2>
|
|
||||||
<p>您还没有创建任何调度队列,点击下方按钮来创建您的第一个队列</p>
|
|
||||||
<a-button type="primary" size="large" @click="handleAddQueue">
|
|
||||||
<template #icon>
|
|
||||||
<PlusOutlined />
|
|
||||||
</template>
|
|
||||||
新建队列
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 队列内容 -->
|
<!-- 队列内容 -->
|
||||||
<div class="queue-main-content" v-else-if="currentQueueData">
|
<div v-else class="queue-content">
|
||||||
<!-- 队列选择器 -->
|
<!-- 队列选择卡片 -->
|
||||||
<div class="queue-selector">
|
<a-card class="queue-selector-card" :bordered="false">
|
||||||
<a-tabs
|
<template #title>
|
||||||
v-model:activeKey="activeQueueId"
|
<div class="card-title">
|
||||||
type="editable-card"
|
<span>队列选择</span>
|
||||||
@edit="onTabEdit"
|
<a-tag :color="queueList.length > 0 ? 'success' : 'default'">
|
||||||
@change="onQueueChange"
|
{{ queueList.length }} 个队列
|
||||||
class="queue-tabs"
|
</a-tag>
|
||||||
>
|
</div>
|
||||||
<a-tab-pane
|
</template>
|
||||||
v-for="queue in queueList"
|
|
||||||
:key="queue.id"
|
|
||||||
:tab="queue.name"
|
|
||||||
:closable="queueList.length > 1"
|
|
||||||
/>
|
|
||||||
</a-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 队列配置区域 -->
|
<div class="queue-selection-container">
|
||||||
<div class="queue-config-section">
|
<!-- 队列按钮组 -->
|
||||||
<div class="section-header">
|
<div class="queue-buttons-container">
|
||||||
<div class="section-title">
|
<a-space wrap size="middle">
|
||||||
<div class="queue-name-editor">
|
<a-button
|
||||||
|
v-for="queue in queueList"
|
||||||
|
:key="queue.id"
|
||||||
|
:type="activeQueueId === queue.id ? 'primary' : 'default'"
|
||||||
|
size="large"
|
||||||
|
@click="onQueueChange(queue.id)"
|
||||||
|
class="queue-button"
|
||||||
|
>
|
||||||
|
{{ queue.name }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 队列配置卡片 -->
|
||||||
|
<a-card class="queue-config-card" :bordered="false">
|
||||||
|
<template #title>
|
||||||
|
<div class="queue-title-container">
|
||||||
|
<div v-if="!isEditingQueueName" class="queue-title-display">
|
||||||
|
<span class="queue-title-text">{{ currentQueueName || '队列配置' }}</span>
|
||||||
|
<a-button type="text" size="small" @click="startEditQueueName" class="queue-edit-btn">
|
||||||
|
<template #icon>
|
||||||
|
<EditOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="queue-title-edit">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="currentQueueName"
|
v-model:value="currentQueueName"
|
||||||
placeholder="请输入队列名称"
|
placeholder="请输入队列名称"
|
||||||
size="large"
|
size="small"
|
||||||
class="queue-name-input"
|
class="queue-title-input"
|
||||||
@blur="onQueueNameBlur"
|
@blur="finishEditQueueName"
|
||||||
@pressEnter="onQueueNameBlur"
|
@pressEnter="finishEditQueueName"
|
||||||
|
:maxlength="50"
|
||||||
|
ref="queueNameInputRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 队列开关配置 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<div class="queue-switches">
|
||||||
|
<div class="switch-item">
|
||||||
|
<div class="switch-label">
|
||||||
|
<span class="switch-title">启动时运行</span>
|
||||||
|
<span class="switch-description">程序启动时自动运行此队列</span>
|
||||||
|
</div>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="currentStartUpEnabled"
|
||||||
|
@change="onQueueSwitchChange"
|
||||||
|
size="default"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="switch-item">
|
||||||
|
<div class="switch-label">
|
||||||
|
<span class="switch-title">定时运行</span>
|
||||||
|
<span class="switch-description">按照设定的时间自动运行此队列</span>
|
||||||
|
</div>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="currentTimeEnabled"
|
||||||
|
@change="onQueueSwitchChange"
|
||||||
|
size="default"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="section-controls">-->
|
|
||||||
<!-- <a-space>-->
|
|
||||||
<!-- <span class="status-label">状态:</span>-->
|
|
||||||
<!-- <a-switch -->
|
|
||||||
<!-- v-model:checked="currentQueueEnabled" -->
|
|
||||||
<!-- @change="onQueueStatusChange"-->
|
|
||||||
<!-- checked-children="启用"-->
|
|
||||||
<!-- un-checked-children="禁用"-->
|
|
||||||
<!-- />-->
|
|
||||||
<!-- </a-space>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 定时项组件 -->
|
<a-divider />
|
||||||
<TimeSetManager
|
|
||||||
v-if="activeQueueId && currentQueueData"
|
|
||||||
:queue-id="activeQueueId"
|
|
||||||
:time-sets="currentTimeSets"
|
|
||||||
@refresh="refreshTimeSets"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 队列项组件 -->
|
<!-- 定时项管理 -->
|
||||||
<QueueItemManager
|
<div class="config-section">
|
||||||
v-if="activeQueueId && currentQueueData"
|
<TimeSetManager
|
||||||
:queue-id="activeQueueId"
|
v-if="activeQueueId && currentQueueData"
|
||||||
:queue-items="currentQueueItems"
|
:queue-id="activeQueueId"
|
||||||
@refresh="refreshQueueItems"
|
:time-sets="currentTimeSets"
|
||||||
/>
|
@refresh="refreshTimeSets"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
|
<!-- 队列项管理 -->
|
||||||
|
<div class="config-section">
|
||||||
|
<QueueItemManager
|
||||||
|
v-if="activeQueueId && currentQueueData"
|
||||||
|
:queue-id="activeQueueId"
|
||||||
|
:queue-items="currentQueueItems"
|
||||||
|
@refresh="refreshQueueItems"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 悬浮保存按钮 -->
|
|
||||||
<a-float-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleSave"
|
|
||||||
class="float-button"
|
|
||||||
:style="{ right: '24px' }"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<SaveOutlined />
|
|
||||||
</template>
|
|
||||||
</a-float-button>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, nextTick } from 'vue'
|
import { computed, onMounted, ref, nextTick, watch } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { PlusOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons-vue'
|
import { PlusOutlined, ReloadOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'
|
||||||
import { Service } from '@/api'
|
import { Service } from '@/api'
|
||||||
import TimeSetManager from '@/components/queue/TimeSetManager.vue'
|
import TimeSetManager from '@/components/queue/TimeSetManager.vue'
|
||||||
import QueueItemManager from '@/components/queue/QueueItemManager.vue'
|
import QueueItemManager from '@/components/queue/QueueItemManager.vue'
|
||||||
@@ -138,6 +203,11 @@ const currentQueueData = ref<Record<string, any> | null>(null)
|
|||||||
// 当前队列的名称和状态
|
// 当前队列的名称和状态
|
||||||
const currentQueueName = ref<string>('')
|
const currentQueueName = ref<string>('')
|
||||||
const currentQueueEnabled = ref<boolean>(true)
|
const currentQueueEnabled = ref<boolean>(true)
|
||||||
|
// 新增:启动时运行和定时运行的开关状态
|
||||||
|
const currentStartUpEnabled = ref<boolean>(false)
|
||||||
|
const currentTimeEnabled = ref<boolean>(false)
|
||||||
|
// 队列名称编辑状态
|
||||||
|
const isEditingQueueName = ref<boolean>(false)
|
||||||
|
|
||||||
// 当前队列的定时项和队列项
|
// 当前队列的定时项和队列项
|
||||||
const currentTimeSets = ref<any[]>([])
|
const currentTimeSets = ref<any[]>([])
|
||||||
@@ -159,7 +229,7 @@ const fetchQueues = async () => {
|
|||||||
try {
|
try {
|
||||||
// API响应格式: {"uid": "xxx", "type": "QueueConfig"}
|
// API响应格式: {"uid": "xxx", "type": "QueueConfig"}
|
||||||
const queueId = item.uid
|
const queueId = item.uid
|
||||||
const queueName = response.data[queueId]?.Info?.Name || `队列 ${index + 1}`
|
const queueName = response.data[queueId]?.Info?.Name || `新调度队列`
|
||||||
console.log('Queue ID:', queueId, 'Name:', queueName, 'Type:', typeof queueId) // 调试日志
|
console.log('Queue ID:', queueId, 'Name:', queueName, 'Type:', typeof queueId) // 调试日志
|
||||||
return {
|
return {
|
||||||
id: queueId,
|
id: queueId,
|
||||||
@@ -169,7 +239,7 @@ const fetchQueues = async () => {
|
|||||||
console.warn('解析队列项失败:', itemError, item)
|
console.warn('解析队列项失败:', itemError, item)
|
||||||
return {
|
return {
|
||||||
id: `queue_${index}`,
|
id: `queue_${index}`,
|
||||||
name: `队列 ${index + 1}`,
|
name: `新调度队列`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -225,6 +295,10 @@ const loadQueueData = async (queueId: string) => {
|
|||||||
|
|
||||||
// 使用nextTick确保DOM更新后再加载数据
|
// 使用nextTick确保DOM更新后再加载数据
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
|
// 更新开关状态 - 从API响应中获取
|
||||||
|
currentStartUpEnabled.value = queueData.Info?.StartUpEnabled ?? false
|
||||||
|
currentTimeEnabled.value = queueData.Info?.TimeEnabled ?? false
|
||||||
await new Promise(resolve => setTimeout(resolve, 50))
|
await new Promise(resolve => setTimeout(resolve, 50))
|
||||||
|
|
||||||
// 加载定时项和队列项数据 - 添加错误处理
|
// 加载定时项和队列项数据 - 添加错误处理
|
||||||
@@ -363,6 +437,7 @@ const refreshQueueItems = async () => {
|
|||||||
|
|
||||||
// 队列名称编辑失焦处理
|
// 队列名称编辑失焦处理
|
||||||
const onQueueNameBlur = () => {
|
const onQueueNameBlur = () => {
|
||||||
|
// 当用户编辑完队列名称后,更新按钮显示的名称
|
||||||
if (activeQueueId.value) {
|
if (activeQueueId.value) {
|
||||||
const currentQueue = queueList.value.find(queue => queue.id === activeQueueId.value)
|
const currentQueue = queueList.value.find(queue => queue.id === activeQueueId.value)
|
||||||
if (currentQueue) {
|
if (currentQueue) {
|
||||||
@@ -372,22 +447,35 @@ const onQueueNameBlur = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 队列状态切换处理
|
// 开始编辑队列名称
|
||||||
const onQueueStatusChange = () => {
|
const startEditQueueName = () => {
|
||||||
// 状态切换时只更新本地状态,不自动保存
|
isEditingQueueName.value = true
|
||||||
|
// 使用 nextTick 确保 DOM 更新后再获取焦点
|
||||||
|
setTimeout(() => {
|
||||||
|
const input = document.querySelector('.queue-title-input input') as HTMLInputElement
|
||||||
|
if (input) {
|
||||||
|
input.focus()
|
||||||
|
input.select()
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签页编辑处理
|
// 完成编辑队列名称
|
||||||
const onTabEdit = async (targetKey: string | MouseEvent, action: 'add' | 'remove') => {
|
const finishEditQueueName = () => {
|
||||||
try {
|
isEditingQueueName.value = false
|
||||||
if (action === 'add') {
|
onQueueNameBlur()
|
||||||
await handleAddQueue()
|
}
|
||||||
} else if (action === 'remove' && typeof targetKey === 'string') {
|
|
||||||
await handleRemoveQueue(targetKey)
|
// 队列开关切换处理
|
||||||
}
|
const onQueueSwitchChange = () => {
|
||||||
} catch (error) {
|
// 开关切换时自动保存
|
||||||
console.error('标签页操作失败:', error)
|
autoSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 队列状态切换处理
|
||||||
|
const onQueueStatusChange = () => {
|
||||||
|
// 状态切换时自动保存
|
||||||
|
autoSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加队列
|
// 添加队列
|
||||||
@@ -396,7 +484,7 @@ const handleAddQueue = async () => {
|
|||||||
const response = await Service.addQueueApiQueueAddPost()
|
const response = await Service.addQueueApiQueueAddPost()
|
||||||
|
|
||||||
if (response.code === 200 && response.queueId) {
|
if (response.code === 200 && response.queueId) {
|
||||||
const defaultName = `队列 ${queueList.value.length + 1}`
|
const defaultName = '新调度队列'
|
||||||
const newQueue = {
|
const newQueue = {
|
||||||
id: response.queueId,
|
id: response.queueId,
|
||||||
name: defaultName,
|
name: defaultName,
|
||||||
@@ -409,7 +497,9 @@ const handleAddQueue = async () => {
|
|||||||
currentQueueEnabled.value = true
|
currentQueueEnabled.value = true
|
||||||
|
|
||||||
await loadQueueData(newQueue.id)
|
await loadQueueData(newQueue.id)
|
||||||
message.success('队列创建成功')
|
|
||||||
|
// 显示名称修改提示
|
||||||
|
message.info('已创建新的调度队列,建议您修改为更有意义的名称', 3)
|
||||||
} else {
|
} else {
|
||||||
message.error('队列创建失败: ' + (response.message || '未知错误'))
|
message.error('队列创建失败: ' + (response.message || '未知错误'))
|
||||||
}
|
}
|
||||||
@@ -452,6 +542,8 @@ const onQueueChange = async (queueId: string) => {
|
|||||||
if (!queueId) return
|
if (!queueId) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 立即更新activeQueueId以确保按钮高亮切换
|
||||||
|
activeQueueId.value = queueId
|
||||||
// 清空当前数据,避免渲染问题
|
// 清空当前数据,避免渲染问题
|
||||||
currentTimeSets.value = []
|
currentTimeSets.value = []
|
||||||
currentQueueItems.value = []
|
currentQueueItems.value = []
|
||||||
@@ -462,17 +554,13 @@ const onQueueChange = async (queueId: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手动保存处理
|
// 自动保存处理
|
||||||
const handleSave = async () => {
|
const autoSave = async () => {
|
||||||
if (!activeQueueId.value) {
|
if (!activeQueueId.value) return
|
||||||
message.warning('请先选择一个队列')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await saveQueueData()
|
await saveQueueData()
|
||||||
message.success('保存成功')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('保存失败')
|
console.error('自动保存失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,11 +569,14 @@ const saveQueueData = async () => {
|
|||||||
if (!activeQueueId.value) return
|
if (!activeQueueId.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建符合API要求的数据结构
|
// 构建符合API要求的数据结构,包含开关状态
|
||||||
const queueData: Record<string, any> = {
|
const queueData: Record<string, any> = {
|
||||||
name: currentQueueName.value,
|
"Info": {
|
||||||
enabled: currentQueueEnabled.value,
|
"Name": currentQueueName.value,
|
||||||
// 这里可以添加其他需要保存的队列配置
|
"StartUpEnabled": currentStartUpEnabled.value,
|
||||||
|
"TimeEnabled": currentTimeEnabled.value,
|
||||||
|
"AfterAccomplish": "NoAction" // 保持默认值
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await Service.updateQueueApiQueueUpdatePost({
|
const response = await Service.updateQueueApiQueueUpdatePost({
|
||||||
@@ -509,6 +600,16 @@ const handleRefresh = async () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动保存功能
|
||||||
|
watch(
|
||||||
|
() => [currentQueueName.value, currentQueueEnabled.value, currentStartUpEnabled.value, currentTimeEnabled.value],
|
||||||
|
async () => {
|
||||||
|
await nextTick()
|
||||||
|
autoSave()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -521,67 +622,39 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 空状态样式 */
|
.queue-container {
|
||||||
.empty-state {
|
min-height: 100vh;
|
||||||
flex: 1;
|
background: var(--ant-color-bg-layout);
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-content {
|
.queue-main {
|
||||||
text-align: center;
|
max-width: 1400px;
|
||||||
padding: 48px;
|
margin: 0 auto;
|
||||||
background: var(--ant-color-bg-container);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
||||||
border: 1px solid var(--ant-color-border-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
/* 页面头部 */
|
||||||
font-size: 64px;
|
|
||||||
color: var(--ant-color-text-tertiary);
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content h3 {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content p {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
margin: 0 0 32px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 队列内容区域 */
|
|
||||||
.queue-content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--ant-color-bg-container);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
||||||
border: 1px solid var(--ant-color-border-secondary);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 队列头部样式 */
|
|
||||||
.queue-header {
|
.queue-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
padding: 24px 32px;
|
margin-bottom: 24px;
|
||||||
background: var(--ant-color-bg-container);
|
padding: 0 4px;
|
||||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-title h1 {
|
.header-left {
|
||||||
margin: 0;
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--ant-color-text);
|
color: var(--ant-color-text);
|
||||||
@@ -591,48 +664,81 @@ onMounted(async () => {
|
|||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 队列选择器 */
|
.page-description {
|
||||||
.queue-selector {
|
margin: 0;
|
||||||
padding: 0 32px;
|
font-size: 16px;
|
||||||
background: var(--ant-color-bg-container);
|
color: var(--ant-color-text-secondary);
|
||||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 队列主内容 */
|
.header-actions {
|
||||||
.queue-main-content {
|
flex-shrink: 0;
|
||||||
flex: 1;
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 400px;
|
||||||
|
background: var(--ant-color-bg-container);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--ant-color-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-description {
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 队列内容 */
|
||||||
|
.queue-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 队列配置区域 */
|
/* 队列选择卡片 */
|
||||||
.queue-config-section {
|
.queue-selector-card {
|
||||||
flex: 1;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
padding: 24px 32px;
|
border-radius: 12px;
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px 24px;
|
|
||||||
border: 1px solid var(--ant-color-border-secondary);
|
border: 1px solid var(--ant-color-border-secondary);
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
background: var(--ant-color-bg-container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title h3 {
|
.card-title {
|
||||||
margin: 0;
|
display: flex;
|
||||||
color: var(--ant-color-text);
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-controls {
|
.queue-selection-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 队列按钮组 */
|
||||||
|
.queue-buttons-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-button {
|
||||||
|
flex: 1 1 120px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 队列配置卡片 */
|
||||||
|
.queue-config-card {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--ant-color-border-secondary);
|
||||||
|
min-height: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-label {
|
.status-label {
|
||||||
@@ -641,124 +747,148 @@ onMounted(async () => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 队列名称编辑器样式 */
|
/* 队列名称编辑 */
|
||||||
.queue-name-editor {
|
.queue-title-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
justify-content: space-between;
|
||||||
|
|
||||||
.queue-name-input {
|
|
||||||
max-width: 300px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-name-input :deep(.ant-input) {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-name-input :deep(.ant-input:hover) {
|
|
||||||
border-color: var(--ant-color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-name-input :deep(.ant-input:focus) {
|
|
||||||
border-color: var(--ant-color-primary);
|
|
||||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-box {
|
|
||||||
min-height: 400px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content {
|
|
||||||
text-align: center;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h2 {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-content p {
|
.queue-title-display {
|
||||||
font-size: 16px;
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
margin-bottom: 24px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.float-button {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-content-fancy {
|
|
||||||
transition:
|
|
||||||
box-shadow 0.3s,
|
|
||||||
transform 0.2s;
|
|
||||||
border: none;
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 80px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 8px;
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-content-fancy h2 {
|
.queue-title-text {
|
||||||
font-size: 26px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
margin: 0 0 12px 0;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
color: var(--ant-color-text);
|
color: var(--ant-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-content-fancy h1 {
|
.queue-edit-btn {
|
||||||
font-size: 16px;
|
color: var(--ant-color-primary);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 队列名称输入框 */
|
||||||
|
.queue-title-input {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 400px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px 16px;
|
transition: all 0.2s ease;
|
||||||
margin: 0 0 12px 0;
|
}
|
||||||
display: inline-block;
|
|
||||||
|
/* 配置区域 */
|
||||||
|
.config-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 开关配置 */
|
||||||
|
.queue-switches {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-description {
|
||||||
|
font-size: 14px;
|
||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.queue-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.queue-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-description {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-title-input {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深度样式使用全局CSS变量 */
|
||||||
|
.queue-selector-card :deep(.ant-card-head) {
|
||||||
|
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-config-card :deep(.ant-card-head) {
|
||||||
|
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-config-card :deep(.ant-card-head-title) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-title-input :deep(.ant-input) {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-title-input :deep(.ant-input:focus) {
|
||||||
|
box-shadow: 0 0 0 2px var(--ant-color-primary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
/* 深色模式适配 */
|
/* 深色模式适配 */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.queue-config-section {
|
.queue-selector-card {
|
||||||
border-color: var(--ant-color-border-secondary);
|
background: var(--ant-color-bg-container);
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.queue-config-card {
|
||||||
border-color: var(--ant-color-border-secondary);
|
background: var(--ant-color-bg-container);
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user