任务中心
This commit is contained in:
@@ -1,18 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="log-content-preview">
|
<div class="log-content-preview">
|
||||||
<div class="log-content-preview__toolbar">
|
<div class="log-content-preview__panel" :class="{ 'is-expanded': hasContent && expanded }">
|
||||||
<a-button size="small" type="link" :disabled="!hasContent" @click="copyLog">
|
<div class="log-content-preview__toolbar">
|
||||||
复制日志
|
<a-button
|
||||||
</a-button>
|
v-if="hasContent"
|
||||||
</div>
|
size="small"
|
||||||
<div class="log-content-preview__viewport">
|
type="link"
|
||||||
<pre class="log-content-preview__text">{{ displayText }}</pre>
|
@click="toggleExpanded"
|
||||||
|
>
|
||||||
|
{{ expanded ? "收起" : "展开" }}
|
||||||
|
</a-button>
|
||||||
|
<a-button size="small" type="link" :disabled="!hasContent" @click="copyLog">
|
||||||
|
复制
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="log-content-preview__content"
|
||||||
|
:class="{ 'is-collapsed': hasContent && !expanded }"
|
||||||
|
>
|
||||||
|
<pre class="log-content-preview__text" :class="{ 'is-empty': !hasContent }">{{ displayText }}</pre>
|
||||||
|
<div
|
||||||
|
v-if="hasContent && !expanded"
|
||||||
|
class="log-content-preview__fade"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { showErrorTips, showSuccessTips } from "@/iview/utils/messageTips";
|
import { showErrorTips, showSuccessTips } from "@/iview/utils/messageTips";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -26,6 +44,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expanded = ref(false);
|
||||||
|
|
||||||
const hasContent = computed(() => !!`${props.content || ""}`.trim());
|
const hasContent = computed(() => !!`${props.content || ""}`.trim());
|
||||||
|
|
||||||
const displayText = computed(() => {
|
const displayText = computed(() => {
|
||||||
@@ -33,6 +53,10 @@ const displayText = computed(() => {
|
|||||||
return props.emptyText;
|
return props.emptyText;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function toggleExpanded() {
|
||||||
|
expanded.value = !expanded.value;
|
||||||
|
}
|
||||||
|
|
||||||
async function copyLog() {
|
async function copyLog() {
|
||||||
if (!hasContent.value) return;
|
if (!hasContent.value) return;
|
||||||
const text = `${props.content || ""}`;
|
const text = `${props.content || ""}`;
|
||||||
@@ -74,26 +98,45 @@ function copyByTextarea(text) {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-content-preview__toolbar {
|
.log-content-preview__panel {
|
||||||
display: flex;
|
position: relative;
|
||||||
justify-content: flex-end;
|
overflow: hidden;
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-content-preview__viewport {
|
|
||||||
/* overflow-x 非 visible 时,overflow-y: visible 会被浏览器算成 auto 而出现纵向滚动条 */
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: clip;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content-preview__panel.is-expanded {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content-preview__toolbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content-preview__content {
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 10px;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-content-preview__content.is-collapsed {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.log-content-preview__text {
|
.log-content-preview__text {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #999;
|
color: #666;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
@@ -103,6 +146,23 @@ function copyByTextarea(text) {
|
|||||||
overflow-wrap: normal;
|
overflow-wrap: normal;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content-preview__text.is-empty {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-content-preview__fade {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 48px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(245, 245, 245, 0),
|
||||||
|
rgba(245, 245, 245, 0.95)
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<template #icon><reload-outlined /></template>
|
<template #icon><reload-outlined /></template>
|
||||||
刷新
|
刷新
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-button type="default" @click="clearTask" :loading="clearTaskLoading">
|
||||||
|
<template #icon><delete-outlined /></template>
|
||||||
|
清除任务
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,7 +24,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div :class="type !== 'detail' ? 'name-click' : 'name-text'"
|
<div :class="type !== 'detail' ? 'name-click' : 'name-text'"
|
||||||
@click="type !== 'detail' ? detailClick(record) : null">
|
@click="type !== 'detail' ? detailClick(record) : null">
|
||||||
{{ record.taskType }}
|
{{ record.transId }}
|
||||||
</div>
|
</div>
|
||||||
<div class="item-detail" v-if="displayBizInfo(record)">
|
<div class="item-detail" v-if="displayBizInfo(record)">
|
||||||
<span class="item-title">业务编号</span>
|
<span class="item-title">业务编号</span>
|
||||||
@@ -58,7 +62,7 @@
|
|||||||
<span class="item-title">执行用时</span>
|
<span class="item-title">执行用时</span>
|
||||||
<span class="item-value">{{ displayUseTime(record.useTime) }}</span>
|
<span class="item-value">{{ displayUseTime(record.useTime) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="type === 'detail'" style="margin: 10px 0px;">
|
<div v-if="type === 'detail' && record.failureCount > 0" style="margin: 10px 0px;">
|
||||||
<IViewConfirm importance="normal" :title="`确认要重新执行异步任务吗?`" @confirm="() => restartClick(record)">
|
<IViewConfirm importance="normal" :title="`确认要重新执行异步任务吗?`" @confirm="() => restartClick(record)">
|
||||||
<a-button size="small" type="default"> 重新执行 </a-button>
|
<a-button size="small" type="default"> 重新执行 </a-button>
|
||||||
</IViewConfirm>
|
</IViewConfirm>
|
||||||
@@ -69,7 +73,7 @@
|
|||||||
<a-button size="small" type="default" @click="detailClick(record)">
|
<a-button size="small" type="default" @click="detailClick(record)">
|
||||||
详情
|
详情
|
||||||
</a-button>
|
</a-button>
|
||||||
<IViewConfirm importance="normal" :title="`确认要重新执行异步任务吗?`" @confirm="() => restartClick(record)">
|
<IViewConfirm v-if="record.failureCount > 0" importance="normal" :title="`确认要重新执行异步任务吗?`" @confirm="() => restartClick(record)">
|
||||||
<a-button size="small" type="default"> 重新执行 </a-button>
|
<a-button size="small" type="default"> 重新执行 </a-button>
|
||||||
</IViewConfirm>
|
</IViewConfirm>
|
||||||
</template>
|
</template>
|
||||||
@@ -86,7 +90,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { ReloadOutlined } from "@ant-design/icons-vue";
|
import { ReloadOutlined, DeleteOutlined } from "@ant-design/icons-vue";
|
||||||
import { showErrorTips, showSuccessTips } from "@/iview/utils/messageTips";
|
import { showErrorTips, showSuccessTips } from "@/iview/utils/messageTips";
|
||||||
import { apiRequest } from "@/iview/utils/request";
|
import { apiRequest } from "@/iview/utils/request";
|
||||||
import IViewConfirm from "@/iview/IViewConfirm.vue";
|
import IViewConfirm from "@/iview/IViewConfirm.vue";
|
||||||
@@ -94,6 +98,7 @@ import ILayoutPage from "@/iview/ILayoutPage.vue";
|
|||||||
import TaskLogs from "./TaskLogs.vue";
|
import TaskLogs from "./TaskLogs.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const clearTaskLoading = ref(false);
|
||||||
|
|
||||||
const pageRef = ref(null);
|
const pageRef = ref(null);
|
||||||
|
|
||||||
@@ -182,6 +187,19 @@ const detailClick = (record) => {
|
|||||||
pageRef.value.showDetailPage("detail", record);
|
pageRef.value.showDetailPage("detail", record);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearTask = async () => {
|
||||||
|
try {
|
||||||
|
clearTaskLoading.value = true;
|
||||||
|
await apiRequest("api.system.async.task.info.clear", {});
|
||||||
|
showSuccessTips("异步任务清除成功");
|
||||||
|
refreshPage();
|
||||||
|
} catch (error) {
|
||||||
|
showErrorTips(error);
|
||||||
|
} finally {
|
||||||
|
clearTaskLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const restartClick = async (record) => {
|
const restartClick = async (record) => {
|
||||||
try {
|
try {
|
||||||
record.actionLoading = true;
|
record.actionLoading = true;
|
||||||
@@ -226,7 +244,7 @@ const config = ref({
|
|||||||
isDefault: true,
|
isDefault: true,
|
||||||
pagination: {
|
pagination: {
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
showTotal: (total) => total > 0 ? `共 ${total} 条` : "暂无数据",
|
showTotal: (total) => total > 0 ? `共 ${total} 条` : "",
|
||||||
},
|
},
|
||||||
columns: recordColumns.value,
|
columns: recordColumns.value,
|
||||||
filters: {
|
filters: {
|
||||||
@@ -255,7 +273,6 @@ const config = ref({
|
|||||||
recordMap: (record) => {
|
recordMap: (record) => {
|
||||||
return record;
|
return record;
|
||||||
},
|
},
|
||||||
hiddenLabel: true,
|
|
||||||
columns: recordColumns.value,
|
columns: recordColumns.value,
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
@@ -313,13 +330,12 @@ const config = ref({
|
|||||||
color: rgba(0, 0, 0, 0.75);
|
color: rgba(0, 0, 0, 0.75);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name-click {
|
.name-click {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
|
||||||
color: #333333;
|
color: #333333;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -329,7 +345,6 @@ const config = ref({
|
|||||||
|
|
||||||
.name-text {
|
.name-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
|
||||||
color: #333333;
|
color: #333333;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="record-page">
|
<div class="record-page">
|
||||||
<ILayoutPage ref="pageRef" :config="config">
|
<ILayoutPage :config="config">
|
||||||
<template #bodyCell="{ column, record, type }">
|
<template #bodyCell="{ column, record, type }">
|
||||||
<template v-if="column.dataIndex === 'recordDetail'">
|
<template v-if="column.dataIndex === 'recordDetail'">
|
||||||
<pre class="log-params">{{ displayParams(taskInfo.taskParams) }}</pre>
|
|
||||||
<LogContentPreview :content="record.logsContent" />
|
<LogContentPreview :content="record.logsContent" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,21 +22,6 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageRef = ref(null);
|
|
||||||
|
|
||||||
const refreshPage = () => {
|
|
||||||
pageRef.value.refreshPage();
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayParams = (params) => {
|
|
||||||
try {
|
|
||||||
let paramsObj = JSON.parse(params);
|
|
||||||
return JSON.stringify(paramsObj, null, 2);
|
|
||||||
} catch (_) {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const recordColumns = ref([
|
const recordColumns = ref([
|
||||||
{
|
{
|
||||||
title: "执行日志",
|
title: "执行日志",
|
||||||
@@ -47,6 +31,23 @@ const recordColumns = ref([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function formatTaskParams(params) {
|
||||||
|
if (params == null || params === "") return "";
|
||||||
|
if (typeof params === "object") {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(params, null, 2);
|
||||||
|
} catch (_) {
|
||||||
|
return String(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const paramsObj = JSON.parse(params);
|
||||||
|
return JSON.stringify(paramsObj, null, 2);
|
||||||
|
} catch (_) {
|
||||||
|
return String(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const config = ref({
|
const config = ref({
|
||||||
infoPages: [
|
infoPages: [
|
||||||
{
|
{
|
||||||
@@ -77,15 +78,4 @@ const config = ref({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-params {
|
|
||||||
color: #999;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user