191 lines
4.5 KiB
Vue
191 lines
4.5 KiB
Vue
<template>
|
|
<a-modal
|
|
title="任务详情"
|
|
:open="visible"
|
|
:footer="null"
|
|
@cancel="handleCancel"
|
|
width="800px"
|
|
>
|
|
<a-descriptions bordered :column="2" v-if="task">
|
|
<a-descriptions-item label="任务名称">{{
|
|
task.name
|
|
}}</a-descriptions-item>
|
|
<a-descriptions-item label="任务类型">
|
|
<a-tag :color="getTypeColor(task.type)">{{
|
|
getTypeText(task.type)
|
|
}}</a-tag>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="命令/类" :span="2">
|
|
<code class="command-code">{{ task.command }}</code>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="Cron表达式">
|
|
<code class="cron-code">{{ task.expression }}</code>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="时区">{{
|
|
task.timezone
|
|
}}</a-descriptions-item>
|
|
<a-descriptions-item label="状态" :span="2">
|
|
<a-tag :color="task.is_active ? 'success' : 'error'">
|
|
{{ task.is_active ? "启用" : "禁用" }}
|
|
</a-tag>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="上次运行时间" :span="2">
|
|
{{ task.last_run_at ? formatDate(task.last_run_at) : "未运行" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="下次运行时间" :span="2">
|
|
{{ task.next_run_at ? formatDate(task.next_run_at) : "-" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="运行次数">
|
|
<a-tag color="success">成功: {{ task.run_count || 0 }}</a-tag>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="失败次数">
|
|
<a-tag :color="task.failed_count > 0 ? 'error' : 'default'">
|
|
失败: {{ task.failed_count || 0 }}
|
|
</a-tag>
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="后台运行" :span="2">
|
|
{{ task.run_in_background ? "是" : "否" }}
|
|
</a-descriptions-item>
|
|
<a-descriptions-item label="描述" :span="2">
|
|
{{ task.description || "-" }}
|
|
</a-descriptions-item>
|
|
</a-descriptions>
|
|
|
|
<!-- 最后输出 -->
|
|
<div v-if="task.last_output" class="output-section">
|
|
<a-divider>最后输出</a-divider>
|
|
<pre class="output-content">{{ task.last_output }}</pre>
|
|
</div>
|
|
|
|
<!-- 底部按钮 -->
|
|
<div class="dialog-footer">
|
|
<a-space>
|
|
<a-button @click="handleCancel">关闭</a-button>
|
|
<a-button type="primary" @click="handleRun">
|
|
<template #icon><PlayCircleOutlined /></template>
|
|
立即执行
|
|
</a-button>
|
|
</a-space>
|
|
</div>
|
|
</a-modal>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed } from "vue";
|
|
import { message } from "ant-design-vue";
|
|
import { PlayCircleOutlined } from "@ant-design/icons-vue";
|
|
import systemApi from "@/api/system";
|
|
|
|
const props = defineProps({
|
|
visible: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
record: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["update:visible", "refresh"]);
|
|
|
|
const task = computed(() => props.record);
|
|
|
|
// 获取任务类型文本
|
|
const getTypeText = (type) => {
|
|
const typeMap = {
|
|
command: "命令",
|
|
job: "任务",
|
|
closure: "闭包",
|
|
};
|
|
return typeMap[type] || type;
|
|
};
|
|
|
|
// 获取任务类型颜色
|
|
const getTypeColor = (type) => {
|
|
const colorMap = {
|
|
command: "blue",
|
|
job: "green",
|
|
closure: "orange",
|
|
};
|
|
return colorMap[type] || "default";
|
|
};
|
|
|
|
// 格式化日期
|
|
const formatDate = (dateStr) => {
|
|
if (!dateStr) return "-";
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleString("zh-CN");
|
|
};
|
|
|
|
// 执行任务
|
|
const handleRun = async () => {
|
|
if (!props.record) return;
|
|
|
|
try {
|
|
const res = await systemApi.tasks.run.post(props.record.id);
|
|
if (res.code === 200) {
|
|
message.success("任务执行成功");
|
|
emit("refresh");
|
|
handleCancel();
|
|
} else {
|
|
message.error(res.message || "任务执行失败");
|
|
}
|
|
} catch (error) {
|
|
message.error("任务执行失败");
|
|
}
|
|
};
|
|
|
|
// 取消
|
|
const handleCancel = () => {
|
|
emit("update:visible", false);
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.command-code {
|
|
padding: 4px 8px;
|
|
background: #f5f5f5;
|
|
border-radius: 3px;
|
|
font-family: "Consolas", "Monaco", monospace;
|
|
font-size: 12px;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.cron-code {
|
|
padding: 2px 6px;
|
|
background: #f5f5f5;
|
|
border-radius: 3px;
|
|
font-family: "Consolas", "Monaco", monospace;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.output-section {
|
|
margin-top: 16px;
|
|
|
|
.output-content {
|
|
padding: 12px;
|
|
background: #f5f5f5;
|
|
border-radius: 4px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-family: "Consolas", "Monaco", monospace;
|
|
font-size: 12px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding-top: 16px;
|
|
border-top: 1px solid #f0f0f0;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
:deep(.ant-descriptions-item-label) {
|
|
font-weight: 500;
|
|
}
|
|
</style>
|