feat: 初步优化调度队列样式

This commit is contained in:
DLmaster361
2025-09-19 00:12:55 +08:00
parent b5b208c611
commit 4db49e0bb9
5 changed files with 1020 additions and 333 deletions

View File

@@ -20,11 +20,9 @@
# Contact: DLmaster_361@163.com # Contact: DLmaster_361@163.com
import uuid from fastapi import APIRouter, Body
import asyncio
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Body, Path
from app.core import TaskManager, Broadcast from app.core import TaskManager
from app.services import System from app.services import System
from app.models.schema import * from app.models.schema import *

View File

@@ -170,8 +170,8 @@ class QueueItem(ConfigBase):
self.Info_ScriptId = ConfigItem( self.Info_ScriptId = ConfigItem(
"Info", "Info",
"ScriptId", "ScriptId",
None, "-",
MultipleUIDValidator(None, self.related_config, "ScriptConfig"), MultipleUIDValidator("-", self.related_config, "ScriptConfig"),
) )
@@ -928,7 +928,7 @@ class AppConfig(GlobalConfig):
{ {
"Info": { "Info": {
"ScriptId": script_dict.get( "ScriptId": script_dict.get(
queue_config["Queue"][f"Script_{i}"], None queue_config["Queue"][f"Script_{i}"], "-"
) )
} }
} }

View File

@@ -133,7 +133,7 @@ class _TaskManager:
task_list = [] task_list = []
for queue_item in queue.QueueItem.values(): for queue_item in queue.QueueItem.values():
if queue_item.get("Info", "ScriptId") is None: if queue_item.get("Info", "ScriptId") == "-":
continue continue
script_id = uuid.UUID(queue_item.get("Info", "ScriptId")) script_id = uuid.UUID(queue_item.get("Info", "ScriptId"))
script = Config.ScriptConfig[script_id] script = Config.ScriptConfig[script_id]

View File

@@ -1,12 +1,12 @@
<template> <template>
<a-card title="队列项" class="queue-item-card" :loading="loading"> <a-card title="任务列表" class="queue-item-card" :loading="loading">
<template #extra> <template #extra>
<a-space> <a-space>
<a-button type="primary" @click="addQueueItem" :loading="loading"> <a-button type="primary" @click="addQueueItem" :loading="loading">
<template #icon> <template #icon>
<PlusOutlined /> <PlusOutlined />
</template> </template>
添加队列项 添加任务
</a-button> </a-button>
</a-space> </a-space>
</template> </template>
@@ -16,29 +16,36 @@
:data-source="queueItems" :data-source="queueItems"
:pagination="false" :pagination="false"
size="middle" size="middle"
:scroll="{ x: 600 }" :scroll="{ x: false, y: false }"
table-layout="auto"
class="queue-table"
> >
<template #emptyText> <template #emptyText>
<span>暂无队列项</span> <span>暂无任务</span>
</template> </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'">
{{ getScriptName(record.script) }} <a-select
v-model:value="record.script"
@change="updateQueueItemScript(record)"
size="small"
style="width: 200px"
class="script-select"
placeholder="请选择脚本"
:options="scriptOptions"
allow-clear
/>
</template> </template>
<template v-else-if="column.key === 'actions'"> <template v-else-if="column.key === 'actions'">
<a-space> <a-space>
<a-button size="small" @click="editQueueItem(record)">
<EditOutlined />
编辑
</a-button>
<a-popconfirm <a-popconfirm
title="确定要删除这个队列项吗?" title="确定要删除这个任务吗?"
@confirm="deleteQueueItem(record.id)" @confirm="deleteQueueItem(record.id)"
ok-text="确定" ok-text="确定"
cancel-text="取消" cancel-text="取消"
> >
<a-button size="small" danger> <a-button size="middle" danger>
<DeleteOutlined /> <DeleteOutlined />
删除 删除
</a-button> </a-button>
@@ -47,35 +54,13 @@
</template> </template>
</template> </template>
</a-table> </a-table>
<!-- 队列项编辑弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="editingQueueItem ? '编辑队列项' : '添加队列项'"
@ok="saveQueueItem"
@cancel="cancelEdit"
:confirm-loading="saving"
width="600px"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="关联脚本" name="script">
<a-select
v-model:value="form.script"
placeholder="请选择关联脚本"
allow-clear
:options="scriptOptions"
/>
</a-form-item>
</a-form>
</a-modal>
</a-card> </a-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import type { FormInstance } from 'ant-design-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons-vue' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { Service } from '@/api' import { Service } from '@/api'
// Props // Props
@@ -93,48 +78,29 @@ const emit = defineEmits<{
// 响应式数据 // 响应式数据
const loading = ref(false) const loading = ref(false)
const saving = ref(false)
const modalVisible = ref(false)
const editingQueueItem = ref<any>(null)
// 选项数据 // 选项数据
const scriptOptions = ref<Array<{ label: string; value: string }>>([]) const scriptOptions = ref<Array<{ label: string; value: string | null }>>([])
// 获取脚本名称
const getScriptName = (scriptId: string) => {
if (!scriptId) return '未选择脚本'
const script = scriptOptions.value.find(script => script.value === scriptId)
return script?.label || '未知脚本'
}
// 表单引用和数据
const formRef = ref<FormInstance>()
const form = reactive({
script: '',
})
// 表单验证规则
const rules = {
script: [{ required: true, message: '请选择关联脚本', trigger: 'change' }],
}
// 表格列配置 // 表格列配置
const queueColumns = [ const queueColumns = [
{ {
title: '序号', title: '序号',
key: 'index', key: 'index',
width: 150, width: 80,
align: 'center',
}, },
{ {
title: '脚本名称', title: '脚本任务',
key: 'script', key: 'script',
width: 200, align: 'center',
ellipsis: true,
}, },
{ {
title: '操作', title: '操作',
key: 'actions', key: 'actions',
width: 150, width: 100,
fixed: 'right', align: 'center',
}, },
] ]
@@ -160,7 +126,7 @@ const loadOptions = async () => {
if (scriptsResponse.code === 200) { if (scriptsResponse.code === 200) {
console.log('脚本API响应数据:', scriptsResponse.data) console.log('脚本API响应数据:', scriptsResponse.data)
// 数据已经是正确的格式,直接使用 // 直接使用接口返回的combox选项
scriptOptions.value = scriptsResponse.data || [] scriptOptions.value = scriptsResponse.data || []
console.log('处理后的脚本选项:', scriptOptions.value) console.log('处理后的脚本选项:', scriptOptions.value)
} else { } else {
@@ -171,99 +137,57 @@ const loadOptions = async () => {
} }
} }
// 添加队列项 // 更新队列项脚本
const addQueueItem = async () => { const updateQueueItemScript = async (record: any) => {
editingQueueItem.value = null
Object.assign(form, {
script: null,
})
// 确保在打开弹窗时加载脚本选项
await loadOptions()
modalVisible.value = true
}
// 编辑队列项
const editQueueItem = async (item: any) => {
editingQueueItem.value = item
Object.assign(form, {
script: item.script || '',
})
// 确保在打开弹窗时加载脚本选项
await loadOptions()
modalVisible.value = true
}
// 保存队列项
const saveQueueItem = async () => {
try { try {
await formRef.value?.validate() loading.value = true
saving.value = true
const response = await Service.updateItemApiQueueItemUpdatePost({
if (editingQueueItem.value) { queueId: props.queueId,
// 更新队列项 - 只保存脚本信息 queueItemId: record.id,
const response = await Service.updateItemApiQueueItemUpdatePost({ data: {
queueId: props.queueId, Info: {
queueItemId: editingQueueItem.value.id, ScriptId: record.script,
data: {
Info: {
ScriptId: form.script,
},
}, },
}) },
})
if (response.code === 200) { if (response.code === 200) {
message.success('队列项更新成功') message.success('脚本更新成功')
} else { emit('refresh')
message.error('队列项更新失败: ' + (response.message || '未知错误'))
return
}
} else { } else {
// 添加队列项 - 先创建,再更新 message.error('脚本更新失败: ' + (response.message || '未知错误'))
// 1. 先创建队列项只传queueId
const createResponse = await Service.addItemApiQueueItemAddPost({
queueId: props.queueId,
})
// 2. 用返回的queueItemId更新队列项数据
if (createResponse.code === 200 && createResponse.queueItemId) {
const updateResponse = await Service.updateItemApiQueueItemUpdatePost({
queueId: props.queueId,
queueItemId: createResponse.queueItemId,
data: {
Info: {
ScriptId: form.script,
},
},
})
if (updateResponse.code === 200) {
message.success('队列项添加成功')
} else {
message.error('队列项添加失败: ' + (updateResponse.message || '未知错误'))
return
}
} else {
message.error('创建队列项失败: ' + (createResponse.message || '未知错误'))
return
}
} }
} catch (error: any) {
modalVisible.value = false console.error('更新脚本失败:', error)
emit('refresh') message.error('更新脚本失败: ' + (error?.message || '网络错误'))
} catch (error) {
console.error('保存队列项失败:', error)
message.error('保存队列项失败: ' + (error?.message || '网络错误'))
} finally { } finally {
saving.value = false loading.value = false
} }
} }
// 取消编辑 // 添加队列项
const cancelEdit = () => { const addQueueItem = async () => {
modalVisible.value = false try {
editingQueueItem.value = null loading.value = true
// 直接创建队列项默认ScriptId为null未选择
const createResponse = await Service.addItemApiQueueItemAddPost({
queueId: props.queueId,
})
if (createResponse.code === 200 && createResponse.queueItemId) {
message.success('任务添加成功')
emit('refresh')
} else {
message.error('任务添加失败: ' + (createResponse.message || '未知错误'))
}
} catch (error: any) {
console.error('添加任务失败:', error)
message.error('添加任务失败: ' + (error?.message || '网络错误'))
} finally {
loading.value = false
}
} }
// 删除队列项 // 删除队列项
@@ -281,7 +205,7 @@ const deleteQueueItem = async (itemId: string) => {
} else { } else {
message.error('删除队列项失败: ' + (response.message || '未知错误')) message.error('删除队列项失败: ' + (response.message || '未知错误'))
} }
} catch (error) { } catch (error: any) {
console.error('删除队列项失败:', error) console.error('删除队列项失败:', error)
message.error('删除队列项失败: ' + (error?.message || '网络错误')) message.error('删除队列项失败: ' + (error?.message || '网络错误'))
} }
@@ -303,6 +227,199 @@ onMounted(() => {
font-weight: 600; font-weight: 600;
} }
/* 表格样式优化 */
.queue-table {
width: 100% !important;
max-width: 100% !important;
}
.queue-table :deep(.ant-table-wrapper) {
width: 100% !important;
max-width: 100% !important;
}
/* 禁用所有滚动条,让表格自动延伸 */
:deep(.ant-table-wrapper) {
overflow: visible !important;
}
:deep(.ant-table-container) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-body) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-content) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-tbody) {
overflow: visible !important;
}
:deep(.ant-table) {
font-size: 14px;
table-layout: auto;
width: 100%;
overflow: visible !important;
}
/* 列宽度控制 */
:deep(.ant-table-thead > tr > th:nth-child(1)) {
width: 80px !important;
min-width: 80px !important;
max-width: 80px !important;
}
:deep(.ant-table-thead > tr > th:nth-child(2)) {
width: auto !important;
min-width: 120px !important;
}
:deep(.ant-table-thead > tr > th:nth-child(3)) {
width: 180px !important;
min-width: 180px !important;
max-width: 180px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(1)) {
width: 80px !important;
min-width: 80px !important;
max-width: 80px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(2)) {
width: auto !important;
min-width: 120px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(3)) {
width: 180px !important;
min-width: 180px !important;
max-width: 180px !important;
}
/* 强制移除任何可能的滚动条 */
:deep(.ant-table-wrapper),
:deep(.ant-table-container),
:deep(.ant-table-body),
:deep(.ant-table-content),
:deep(.ant-table),
:deep(.ant-table-tbody) {
scrollbar-width: none !important; /* Firefox */
-ms-overflow-style: none !important; /* IE/Edge */
}
:deep(.ant-table-wrapper)::-webkit-scrollbar,
:deep(.ant-table-container)::-webkit-scrollbar,
:deep(.ant-table-body)::-webkit-scrollbar,
:deep(.ant-table-content)::-webkit-scrollbar,
:deep(.ant-table)::-webkit-scrollbar,
:deep(.ant-table-tbody)::-webkit-scrollbar {
display: none !important; /* Chrome/Safari */
}
/* 表格行和列样式 */
:deep(.ant-table-tbody > tr > td) {
padding: 8px 12px;
border-bottom: 1px solid var(--ant-color-border);
}
:deep(.ant-table-thead > tr > th) {
font-weight: 600;
padding: 8px 12px;
text-align: center;
background-color: var(--ant-color-bg-container);
border-bottom: 1px solid var(--ant-color-border);
}
/* 脚本名称列特殊处理 */
:deep(.ant-table-tbody > tr > td:nth-child(2)) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
}
:deep(.ant-table-thead > tr > th:nth-child(2)) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 确保列内容正确显示 */
:deep(.ant-table-thead > tr > th) {
text-align: center;
vertical-align: middle;
}
:deep(.ant-table-tbody > tr > td) {
text-align: center;
vertical-align: middle;
}
:deep(.ant-table-cell) {
text-align: center;
}
/* 表格整体布局优化 */
:deep(.ant-table-wrapper) {
width: 100%;
min-height: auto;
}
/* 确保表格不会被压缩 */
:deep(.ant-table-fixed-header) {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
:deep(.ant-table-fixed-header)::-webkit-scrollbar {
display: none !important;
}
/* 序号列样式 */
:deep(.ant-table-tbody > tr > td:first-child) {
font-weight: 500;
color: var(--ant-color-text-secondary);
}
/* 操作按钮布局 */
:deep(.ant-btn) {
min-width: auto;
height: 32px;
padding: 0 8px;
font-size: 14px;
line-height: 1.5;
}
:deep(.ant-space) {
gap: 6px !important;
}
:deep(.ant-space-item) {
margin-right: 6px !important;
}
/* 操作列内容居中且不超出 */
:deep(.ant-table-tbody > tr > td:nth-child(3) .ant-space) {
justify-content: center;
width: 100%;
}
/* 按钮图标样式调整 */
:deep(.ant-btn .anticon) {
font-size: 14px;
}
/* 队列项列表样式 */ /* 队列项列表样式 */
.queue-items-list { .queue-items-list {
display: flex; display: flex;
@@ -379,4 +496,97 @@ onMounted(() => {
margin: 0; margin: 0;
border-radius: 4px; border-radius: 4px;
} }
/* 脚本下拉框样式 - 使用与TimeSetManager.vue状态下拉框相同的样式 */
.script-select :deep(.ant-select-selector) {
background: transparent !important;
border: none !important;
padding: 0 6px !important;
font-size: 13px !important;
min-height: 28px !important;
line-height: 26px !important;
box-shadow: none !important;
text-align: center;
}
.script-select :deep(.ant-select-selection-item) {
line-height: 26px !important;
color: var(--ant-color-text) !important;
font-size: 13px !important;
font-weight: 500;
padding: 0;
margin: 0;
}
.script-select :deep(.ant-select-selection-placeholder) {
line-height: 26px !important;
color: var(--ant-color-text-placeholder) !important;
font-size: 13px !important;
padding: 0;
margin: 0;
}
.script-select :deep(.ant-select-clear) {
display: none !important;
}
.script-select :deep(.ant-select-selection-search) {
margin: 0 !important;
padding: 0;
}
.script-select :deep(.ant-select-selection-search-input) {
padding: 0 !important;
margin: 0 !important;
height: 26px !important;
}
.script-select:hover :deep(.ant-select-selector) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
}
.script-select:focus-within :deep(.ant-select-selector),
.script-select.ant-select-focused :deep(.ant-select-selector) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
outline: none !important;
}
.script-select :deep(.ant-select-selector):focus,
.script-select :deep(.ant-select-selector):focus-within {
border: none !important;
box-shadow: none !important;
background: transparent !important;
outline: none !important;
cursor: default !important;
}
/* 下拉箭头样式 */
.script-select :deep(.ant-select-arrow) {
right: 4px;
color: var(--ant-color-text-tertiary);
font-size: 10px;
}
.script-select :deep(.ant-select-arrow:hover) {
color: var(--ant-color-primary);
}
/* 自定义下拉框样式 - 增加下拉菜单宽度 */
.script-select :deep(.ant-select-dropdown) {
min-width: 200px !important;
max-width: 300px !important;
}
.script-select :deep(.ant-select-item) {
padding: 8px 12px !important;
font-size: 13px !important;
}
.script-select :deep(.ant-select-item-option-content) {
font-size: 13px !important;
}
</style> </style>

View File

@@ -1,5 +1,5 @@
<template> <template>
<a-card title="定时" class="time-set-card" :loading="loading"> <a-card title="定时列表" class="time-set-card" :loading="loading">
<template #extra> <template #extra>
<a-space> <a-space>
<a-button <a-button
@@ -11,7 +11,7 @@
<template #icon> <template #icon>
<PlusOutlined /> <PlusOutlined />
</template> </template>
添加定时 添加定时
</a-button> </a-button>
</a-space> </a-space>
</template> </template>
@@ -21,37 +21,45 @@
:data-source="timeSets" :data-source="timeSets"
:pagination="false" :pagination="false"
size="middle" size="middle"
:scroll="{ x: 800 }" :scroll="{ x: false, y: false }"
table-layout="auto"
class="time-set-table"
> >
<template #emptyText> <template #emptyText>
<span>暂无定时</span> <span>暂无定时</span>
</template> </template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'enabled'"> <template v-if="column.key === 'enabled'">
<a-switch <a-select
v-model:checked="record.enabled" v-model:value="record.enabled"
@change="updateTimeSetStatus(record)" @change="updateTimeSetStatus(record)"
size="small" size="small"
/> style="width: 80px"
class="status-select"
>
<a-select-option :value="true">启用</a-select-option>
<a-select-option :value="false">禁用</a-select-option>
</a-select>
</template> </template>
<template v-else-if="column.key === 'time'"> <template v-else-if="column.key === 'time'">
<div class="time-display"> <a-time-picker
{{ record.time || '--:--' }} v-model:value="record.timeValue"
</div> format="HH:mm"
placeholder="请选择时间"
size="small"
@change="updateTimeSetTime(record)"
:disabled="loading"
/>
</template> </template>
<template v-else-if="column.key === 'actions'"> <template v-else-if="column.key === 'actions'">
<a-space> <a-space>
<a-button size="small" @click="editTimeSet(record)">
<EditOutlined />
编辑
</a-button>
<a-popconfirm <a-popconfirm
title="确定要删除这个定时吗?" title="确定要删除这个定时吗?"
@confirm="deleteTimeSet(record.id)" @confirm="deleteTimeSet(record.id)"
ok-text="确定" ok-text="确定"
cancel-text="取消" cancel-text="取消"
> >
<a-button size="small" danger> <a-button size="middle" danger>
<DeleteOutlined /> <DeleteOutlined />
删除 删除
</a-button> </a-button>
@@ -61,41 +69,33 @@
</template> </template>
</a-table> </a-table>
<!-- 定时项编辑弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="editingTimeSet ? '编辑定时项' : '添加定时项'"
ok-text="确认"
cancel-text="取消"
@ok="saveTimeSet"
@cancel="cancelEdit"
:confirm-loading="saving"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="执行时间" name="time">
<a-time-picker
v-model:value="form.time"
format="HH:mm"
placeholder="请选择执行时间"
size="large"
/>
</a-form-item>
<a-form-item label="启用状态" name="enabled">
<a-switch v-model:checked="form.enabled" />
</a-form-item>
</a-form>
</a-modal>
</a-card> </a-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, watch } from 'vue' import { ref, watch } from 'vue'
import type { FormInstance } from 'ant-design-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons-vue' import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue'
import { Service } from '@/api' import { Service } from '@/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
// Props
interface Props {
queueId: string
timeSets: any[]
}
const props = defineProps<Props>()
// Emits
const emit = defineEmits<{
refresh: []
}>()
// 响应式数据
const loading = ref(false)
// 时间处理工具函数 // 时间处理工具函数
const parseTimeString = (timeStr: string) => { const parseTimeString = (timeStr: string) => {
if (!timeStr) return undefined if (!timeStr) return undefined
@@ -120,37 +120,6 @@ const formatTimeValue = (timeValue: any) => {
} }
} }
// Props
interface Props {
queueId: string
timeSets: any[]
}
const props = defineProps<Props>()
// Emits
const emit = defineEmits<{
refresh: []
}>()
// 响应式数据
const loading = ref(false)
const saving = ref(false)
const modalVisible = ref(false)
const editingTimeSet = ref<any>(null)
// 表单引用和数据
const formRef = ref<FormInstance>()
const form = reactive({
time: undefined as any,
enabled: true,
})
// 表单验证规则
const rules = {
time: [{ required: true, message: '请选择执行时间', trigger: 'change' }],
}
// 表格列配置 // 表格列配置
const timeColumns = [ const timeColumns = [
{ {
@@ -158,153 +127,126 @@ const timeColumns = [
dataIndex: 'index', dataIndex: 'index',
key: 'index', key: 'index',
width: 80, width: 80,
align: 'center',
customRender: ({ index }: { index: number }) => index + 1, customRender: ({ index }: { index: number }) => index + 1,
}, },
{
title: '状态',
dataIndex: 'enabled',
key: 'enabled',
width: 120,
align: 'center',
},
{ {
title: '执行时间', title: '执行时间',
dataIndex: 'time', dataIndex: 'time',
key: 'time', key: 'time',
width: 150, align: 'center',
}, ellipsis: true,
{
title: '启用状态',
dataIndex: 'enabled',
key: 'enabled',
width: 100,
}, },
{ {
title: '操作', title: '操作',
key: 'actions', key: 'actions',
width: 120, width: 100,
fixed: 'right', align: 'center',
}, },
] ]
// 计算属性 - 使用props传入的数据 // 计算属性 - 使用props传入的数据
const timeSets = ref([...props.timeSets]) const timeSets = ref([...props.timeSets])
// 处理时间数据为每个项添加timeValue字段用于时间选择器
const processTimeSets = (rawTimeSets: any[]) => {
return rawTimeSets.map(item => ({
...item,
timeValue: parseTimeString(item.time)
}))
}
// 监听props变化 // 监听props变化
watch( watch(
() => props.timeSets, () => props.timeSets,
newTimeSets => { newTimeSets => {
timeSets.value = [...newTimeSets] timeSets.value = processTimeSets(newTimeSets)
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
) )
// 添加定时项 // 添加定时项
const addTimeSet = () => { const addTimeSet = async () => {
editingTimeSet.value = null
Object.assign(form, {
time: undefined,
enabled: true,
})
modalVisible.value = true
}
// 编辑定时项
const editTimeSet = (timeSet: any) => {
editingTimeSet.value = timeSet
// 安全地处理时间值
const timeValue = parseTimeString(timeSet.time)
Object.assign(form, {
time: timeValue,
enabled: timeSet.enabled,
})
modalVisible.value = true
}
// 保存定时项
const saveTimeSet = async () => {
try { try {
await formRef.value?.validate()
saving.value = true
// 验证queueId是否存在 // 验证queueId是否存在
if (!props.queueId || props.queueId.trim() === '') { if (!props.queueId || props.queueId.trim() === '') {
message.error('队列ID为空无法添加定时') message.error('队列ID为空无法添加定时')
saving.value = false
return return
} }
// 处理时间格式 - 使用工具函数 loading.value = true
console.log(
'form.time:',
form.time,
'type:',
typeof form.time,
'isDayjs:',
dayjs.isDayjs(form.time)
)
const timeString = formatTimeValue(form.time) // 先创建,再设置默认值
console.log('timeString:', timeString) const createResponse = await Service.addTimeSetApiQueueTimeAddPost({
queueId: props.queueId,
})
if (editingTimeSet.value) { if (createResponse.code === 200 && createResponse.timeSetId) {
// 更新定时项 const updateResponse = await Service.updateTimeSetApiQueueTimeUpdatePost({
const response = await Service.updateTimeSetApiQueueTimeUpdatePost({
queueId: props.queueId, queueId: props.queueId,
timeSetId: editingTimeSet.value.id, timeSetId: createResponse.timeSetId,
data: { data: {
Info: { Info: {
Enabled: form.enabled, Enabled: false, // 默认禁用
Time: timeString, Time: '00:00', // 默认00:00
}, },
}, },
}) })
if (response.code === 200) { if (updateResponse.code === 200) {
message.success('定时项更新成功') message.success('定时项添加成功')
emit('refresh')
} else { } else {
message.error('定时项更新失败: ' + (response.message || '未知错误')) message.error('定时项添加失败: ' + (updateResponse.message || '未知错误'))
return
} }
} else { } else {
// 添加定时项 - 先创建,再更新 message.error('创建定时项失败: ' + (createResponse.message || '未知错误'))
const createResponse = await Service.addTimeSetApiQueueTimeAddPost({
queueId: props.queueId,
})
if (createResponse.code === 200 && createResponse.timeSetId) {
const updateResponse = await Service.updateTimeSetApiQueueTimeUpdatePost({
queueId: props.queueId,
timeSetId: createResponse.timeSetId,
data: {
Info: {
Enabled: form.enabled,
Time: timeString,
},
},
})
if (updateResponse.code === 200) {
message.success('定时项添加成功')
} else {
message.error('定时项添加失败: ' + (updateResponse.message || '未知错误'))
return
}
} else {
message.error('创建定时项失败: ' + (createResponse.message || '未知错误'))
return
}
} }
} catch (error: any) {
modalVisible.value = false console.error('添加定时项失败:', error)
emit('refresh') message.error('添加定时项失败: ' + (error?.message || '网络错误'))
} catch (error) {
console.error('保存定时项失败:', error)
message.error('保存定时项失败: ' + (error?.message || '网络错误'))
} finally { } finally {
saving.value = false loading.value = false
} }
} }
// 取消编辑 // 更新定时项时间
const cancelEdit = () => { const updateTimeSetTime = async (timeSet: any) => {
modalVisible.value = false try {
editingTimeSet.value = null const timeString = formatTimeValue(timeSet.timeValue)
const response = await Service.updateTimeSetApiQueueTimeUpdatePost({
queueId: props.queueId,
timeSetId: timeSet.id,
data: {
Info: {
Time: timeString,
},
},
})
if (response.code === 200) {
// 更新本地显示的时间
timeSet.time = timeString
message.success('时间更新成功')
} else {
message.error('时间更新失败: ' + (response.message || '未知错误'))
// 回滚时间值
timeSet.timeValue = parseTimeString(timeSet.time)
}
} catch (error: any) {
console.error('更新时间失败:', error)
message.error('更新时间失败: ' + (error?.message || '网络错误'))
// 回滚时间值
timeSet.timeValue = parseTimeString(timeSet.time)
}
} }
// 更新定时项状态 // 更新定时项状态
@@ -327,7 +269,7 @@ const updateTimeSetStatus = async (timeSet: any) => {
// 回滚状态 // 回滚状态
timeSet.enabled = !timeSet.enabled timeSet.enabled = !timeSet.enabled
} }
} catch (error) { } catch (error: any) {
console.error('更新状态失败:', error) console.error('更新状态失败:', error)
message.error('更新状态失败: ' + (error?.message || '网络错误')) message.error('更新状态失败: ' + (error?.message || '网络错误'))
// 回滚状态 // 回滚状态
@@ -350,7 +292,7 @@ const deleteTimeSet = async (timeSetId: string) => {
} else { } else {
message.error('删除定时项失败: ' + (response.message || '未知错误')) message.error('删除定时项失败: ' + (response.message || '未知错误'))
} }
} catch (error) { } catch (error: any) {
console.error('删除定时项失败:', error) console.error('删除定时项失败:', error)
message.error('删除定时项失败: ' + (error?.message || '网络错误')) message.error('删除定时项失败: ' + (error?.message || '网络错误'))
} }
@@ -368,12 +310,234 @@ const deleteTimeSet = async (timeSetId: string) => {
} }
/* 表格样式优化 */ /* 表格样式优化 */
.time-set-table {
width: 100% !important;
max-width: 100% !important;
}
.time-set-table :deep(.ant-table-wrapper) {
width: 100% !important;
max-width: 100% !important;
}
/* 禁用所有滚动条,让表格自动延伸 */
:deep(.ant-table-wrapper) {
overflow: visible !important;
}
:deep(.ant-table-container) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-body) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-content) {
overflow: visible !important;
max-height: none !important;
height: auto !important;
}
:deep(.ant-table-tbody) {
overflow: visible !important;
}
:deep(.ant-table) {
font-size: 14px;
table-layout: auto;
width: 100%;
overflow: visible !important;
}
/* 强制移除任何可能的滚动条 */
:deep(.ant-table-wrapper),
:deep(.ant-table-container),
:deep(.ant-table-body),
:deep(.ant-table-content),
:deep(.ant-table),
:deep(.ant-table-tbody) {
scrollbar-width: none !important; /* Firefox */
-ms-overflow-style: none !important; /* IE/Edge */
}
:deep(.ant-table-wrapper)::-webkit-scrollbar,
:deep(.ant-table-container)::-webkit-scrollbar,
:deep(.ant-table-body)::-webkit-scrollbar,
:deep(.ant-table-content)::-webkit-scrollbar,
:deep(.ant-table)::-webkit-scrollbar,
:deep(.ant-table-tbody)::-webkit-scrollbar {
display: none !important; /* Chrome/Safari */
}
/* 列宽度控制 */
:deep(.ant-table-thead > tr > th:nth-child(1)) {
width: 80px !important;
min-width: 80px !important;
max-width: 80px !important;
}
:deep(.ant-table-thead > tr > th:nth-child(2)) {
width: 120px !important;
min-width: 120px !important;
max-width: 120px !important;
}
:deep(.ant-table-thead > tr > th:nth-child(3)) {
width: auto !important;
min-width: 100px !important;
}
:deep(.ant-table-thead > tr > th:nth-child(4)) {
width: 180px !important;
min-width: 180px !important;
max-width: 180px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(1)) {
width: 80px !important;
min-width: 80px !important;
max-width: 80px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(2)) {
width: 120px !important;
min-width: 120px !important;
max-width: 120px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(3)) {
width: auto !important;
min-width: 100px !important;
}
:deep(.ant-table-tbody > tr > td:nth-child(4)) {
width: 180px !important;
min-width: 180px !important;
max-width: 180px !important;
}
/* 表格行和列样式 */
:deep(.ant-table-tbody > tr > td) { :deep(.ant-table-tbody > tr > td) {
padding: 12px 16px; padding: 8px 12px;
border-bottom: 1px solid var(--ant-color-border);
} }
:deep(.ant-table-thead > tr > th) { :deep(.ant-table-thead > tr > th) {
font-weight: 600; font-weight: 600;
padding: 8px 12px;
text-align: center;
background-color: var(--ant-color-bg-container);
border-bottom: 1px solid var(--ant-color-border);
}
/* 确保列内容正确显示 */
:deep(.ant-table-thead > tr > th) {
text-align: center;
vertical-align: middle;
}
:deep(.ant-table-tbody > tr > td) {
text-align: center;
vertical-align: middle;
}
:deep(.ant-table-cell) {
text-align: center;
}
/* 表格整体布局优化 */
:deep(.ant-table-wrapper) {
width: 100%;
min-height: auto;
}
/* 确保表格不会被压缩 */
:deep(.ant-table-fixed-header) {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
:deep(.ant-table-fixed-header)::-webkit-scrollbar {
display: none !important;
}
/* 操作按钮布局 */
:deep(.ant-btn) {
min-width: auto;
height: 32px;
padding: 0 8px;
font-size: 14px;
line-height: 1.5;
}
:deep(.ant-space) {
gap: 6px !important;
}
:deep(.ant-space-item) {
margin-right: 6px !important;
}
/* 操作列内容居中且不超出 */
:deep(.ant-table-tbody > tr > td:nth-child(4) .ant-space) {
justify-content: center;
width: 100%;
}
/* 按钮图标样式调整 */
:deep(.ant-btn .anticon) {
font-size: 14px;
}
/* 序号列样式 */
:deep(.ant-table-tbody > tr > td:first-child) {
font-weight: 500;
color: var(--ant-color-text-secondary);
}
/* 隐藏所有滚动条 */
:deep(.ant-table-container)::-webkit-scrollbar,
:deep(.ant-table-tbody)::-webkit-scrollbar,
:deep(.ant-table-content)::-webkit-scrollbar,
:deep(.ant-table-body)::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
/* 确保列宽度固定 */
:deep(.ant-table-thead > tr > th) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.ant-table-tbody > tr > td) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 序号列样式 */
:deep(.ant-table-tbody > tr > td:first-child) {
font-weight: 500;
color: var(--ant-color-text-secondary);
}
/* 操作按钮布局 */
:deep(.ant-btn) {
min-width: auto;
height: 32px;
padding: 0 12px;
}
:deep(.ant-space-item) {
margin-right: 8px !important;
} }
/* 时间选择器样式 */ /* 时间选择器样式 */
@@ -386,6 +550,99 @@ const deleteTimeSet = async (timeSetId: string) => {
margin: 0; margin: 0;
} }
/* 状态下拉框样式 - 使用与Plans.vue相同的config-select样式 */
.status-select :deep(.ant-select-selector) {
background: transparent !important;
border: none !important;
padding: 0 6px !important;
font-size: 13px !important;
min-height: 28px !important;
line-height: 26px !important;
box-shadow: none !important;
text-align: center;
}
.status-select :deep(.ant-select-selection-item) {
line-height: 26px !important;
color: var(--ant-color-text) !important;
font-size: 13px !important;
font-weight: 500;
padding: 0;
margin: 0;
}
.status-select :deep(.ant-select-selection-placeholder) {
line-height: 26px !important;
color: var(--ant-color-text-placeholder) !important;
font-size: 13px !important;
padding: 0;
margin: 0;
}
.status-select :deep(.ant-select-clear) {
display: none !important;
}
.status-select :deep(.ant-select-selection-search) {
margin: 0 !important;
padding: 0;
}
.status-select :deep(.ant-select-selection-search-input) {
padding: 0 !important;
margin: 0 !important;
height: 26px !important;
}
.status-select:hover :deep(.ant-select-selector) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
}
.status-select:focus-within :deep(.ant-select-selector),
.status-select.ant-select-focused :deep(.ant-select-selector) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
outline: none !important;
}
.status-select :deep(.ant-select-selector):focus,
.status-select :deep(.ant-select-selector):focus-within {
border: none !important;
box-shadow: none !important;
background: transparent !important;
outline: none !important;
cursor: default !important;
}
/* 隐藏下拉箭头或调整样式 */
.status-select :deep(.ant-select-arrow) {
right: 4px;
color: var(--ant-color-text-tertiary);
font-size: 10px;
}
.status-select :deep(.ant-select-arrow:hover) {
color: var(--ant-color-primary);
}
/* 自定义下拉框样式 - 增加下拉菜单宽度 */
.status-select :deep(.ant-select-dropdown) {
min-width: 100px !important;
max-width: 150px !important;
}
.status-select :deep(.ant-select-item) {
padding: 8px 12px !important;
font-size: 13px !important;
}
.status-select :deep(.ant-select-item-option-content) {
font-size: 13px !important;
}
/* 时间显示样式 */ /* 时间显示样式 */
.time-display { .time-display {
font-weight: 600; font-weight: 600;
@@ -396,4 +653,226 @@ const deleteTimeSet = async (timeSetId: string) => {
min-width: 60px; min-width: 60px;
text-align: center; text-align: center;
} }
/* 时间选择器样式 - 与状态下拉框保持一致 */
:deep(.ant-picker) {
background: transparent !important;
border: none !important;
padding: 0 6px !important;
font-size: 13px !important;
min-height: 28px !important;
line-height: 26px !important;
box-shadow: none !important;
text-align: center;
width: 80px !important;
}
:deep(.ant-picker-input > input) {
text-align: center !important;
font-size: 13px !important;
font-weight: 600 !important;
color: var(--ant-color-text) !important;
background: transparent !important;
border: none !important;
padding: 0 !important;
line-height: 26px !important;
}
:deep(.ant-picker:hover) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
}
:deep(.ant-picker:focus-within),
:deep(.ant-picker.ant-picker-focused) {
border: none !important;
box-shadow: none !important;
background: transparent !important;
outline: none !important;
}
:deep(.ant-picker-clear) {
display: none !important;
}
:deep(.ant-picker-suffix) {
right: 4px;
color: var(--ant-color-text-tertiary);
font-size: 10px;
}
:deep(.ant-picker-suffix:hover) {
color: var(--ant-color-primary);
}
/* 时间选择器弹出面板滚动条样式优化 */
:deep(.ant-picker-time-panel) {
border-radius: 8px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
/* Firefox 滚动条样式 - 时间选择器 */
:deep(.ant-picker-time-panel-column) {
scrollbar-width: thin;
scrollbar-color: #d9d9d9 #f5f5f5;
}
.dark :deep(.ant-picker-time-panel-column) {
scrollbar-width: thin;
scrollbar-color: var(--ant-color-border) var(--ant-color-bg-layout);
}
/* 浅色主题滚动条样式 - 时间选择器 */
:deep(.ant-picker-time-panel-column)::-webkit-scrollbar {
width: 8px;
}
:deep(.ant-picker-time-panel-column)::-webkit-scrollbar-track {
background: #f5f5f5;
border-radius: 4px;
}
:deep(.ant-picker-time-panel-column)::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 4px;
transition: background 0.2s ease;
}
:deep(.ant-picker-time-panel-column)::-webkit-scrollbar-thumb:hover {
background: #bfbfbf;
}
/* 深色主题滚动条样式 - 时间选择器 */
.dark :deep(.ant-picker-time-panel-column)::-webkit-scrollbar {
width: 8px;
}
.dark :deep(.ant-picker-time-panel-column)::-webkit-scrollbar-track {
background: var(--ant-color-bg-layout);
border-radius: 4px;
}
.dark :deep(.ant-picker-time-panel-column)::-webkit-scrollbar-thumb {
background: var(--ant-color-border);
border-radius: 4px;
transition: background 0.2s ease;
}
.dark :deep(.ant-picker-time-panel-column)::-webkit-scrollbar-thumb:hover {
background: var(--ant-color-text-tertiary);
}
/* 时间选择器面板项目样式优化 */
:deep(.ant-picker-time-panel-cell) {
transition: all 0.2s ease;
}
:deep(.ant-picker-time-panel-cell:hover) {
background: var(--ant-color-fill-tertiary);
}
</style>
<!-- 全局样式 - 用于时间选择器弹出面板 -->
<style>
/* 全局时间选择器面板滚动条样式 - 浅色主题 */
.ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar {
width: 8px;
}
.ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-track {
background: #f5f5f5;
border-radius: 4px;
}
.ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 4px;
transition: background 0.2s ease;
}
.ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover {
background: #bfbfbf;
}
/* 全局时间选择器面板滚动条样式 - 深色主题 */
:root.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar {
width: 8px;
}
:root.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-track {
background: #000000;
border-radius: 4px;
}
:root.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb {
background: #424242;
border-radius: 4px;
transition: background 0.2s ease;
}
:root.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.45);
}
/* 另一种深色模式选择器 */
[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar {
width: 8px;
}
[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-track {
background: #000000;
border-radius: 4px;
}
[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb {
background: #424242;
border-radius: 4px;
transition: background 0.2s ease;
}
[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.45);
}
/* body类名方式的深色模式 */
body.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar {
width: 8px;
}
body.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-track {
background: #000000;
border-radius: 4px;
}
body.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb {
background: #424242;
border-radius: 4px;
transition: background 0.2s ease;
}
body.dark .ant-picker-dropdown .ant-picker-time-panel-column::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.45);
}
/* Firefox 支持 */
.ant-picker-dropdown .ant-picker-time-panel-column {
scrollbar-width: thin;
scrollbar-color: #d9d9d9 #f5f5f5;
}
:root.dark .ant-picker-dropdown .ant-picker-time-panel-column {
scrollbar-width: thin;
scrollbar-color: #424242 #000000;
}
[data-theme='dark'] .ant-picker-dropdown .ant-picker-time-panel-column {
scrollbar-width: thin;
scrollbar-color: #424242 #000000;
}
body.dark .ant-picker-dropdown .ant-picker-time-panel-column {
scrollbar-width: thin;
scrollbar-color: #424242 #000000;
}
</style> </style>