forked from tools/tape-springboot-sysadmin
任务中心
This commit is contained in:
@@ -1,18 +1,36 @@
|
||||
<template>
|
||||
<div class="log-content-preview">
|
||||
<div class="log-content-preview__toolbar">
|
||||
<a-button size="small" type="link" :disabled="!hasContent" @click="copyLog">
|
||||
复制日志
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="log-content-preview__viewport">
|
||||
<pre class="log-content-preview__text">{{ displayText }}</pre>
|
||||
<div class="log-content-preview__panel" :class="{ 'is-expanded': hasContent && expanded }">
|
||||
<div class="log-content-preview__toolbar">
|
||||
<a-button
|
||||
v-if="hasContent"
|
||||
size="small"
|
||||
type="link"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { showErrorTips, showSuccessTips } from "@/iview/utils/messageTips";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -26,6 +44,8 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const expanded = ref(false);
|
||||
|
||||
const hasContent = computed(() => !!`${props.content || ""}`.trim());
|
||||
|
||||
const displayText = computed(() => {
|
||||
@@ -33,6 +53,10 @@ const displayText = computed(() => {
|
||||
return props.emptyText;
|
||||
});
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded.value = !expanded.value;
|
||||
}
|
||||
|
||||
async function copyLog() {
|
||||
if (!hasContent.value) return;
|
||||
const text = `${props.content || ""}`;
|
||||
@@ -74,26 +98,45 @@ function copyByTextarea(text) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.log-content-preview__toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
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;
|
||||
.log-content-preview__panel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
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;
|
||||
}
|
||||
|
||||
.log-content-preview__content.is-collapsed {
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-content-preview__text {
|
||||
margin: 0;
|
||||
color: #999;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
@@ -103,6 +146,23 @@ function copyByTextarea(text) {
|
||||
overflow-wrap: normal;
|
||||
width: max-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.log-content-preview__text.is-empty {
|
||||
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>
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<template #icon><reload-outlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button type="default" @click="clearTask" :loading="clearTaskLoading">
|
||||
<template #icon><delete-outlined /></template>
|
||||
清除任务
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -20,7 +24,7 @@
|
||||
<div>
|
||||
<div :class="type !== 'detail' ? 'name-click' : 'name-text'"
|
||||
@click="type !== 'detail' ? detailClick(record) : null">
|
||||
{{ record.taskType }}
|
||||
{{ record.transId }}
|
||||
</div>
|
||||
<div class="item-detail" v-if="displayBizInfo(record)">
|
||||
<span class="item-title">业务编号</span>
|
||||
@@ -58,7 +62,7 @@
|
||||
<span class="item-title">执行用时</span>
|
||||
<span class="item-value">{{ displayUseTime(record.useTime) }}</span>
|
||||
</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)">
|
||||
<a-button size="small" type="default"> 重新执行 </a-button>
|
||||
</IViewConfirm>
|
||||
@@ -69,7 +73,7 @@
|
||||
<a-button size="small" type="default" @click="detailClick(record)">
|
||||
详情
|
||||
</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>
|
||||
</IViewConfirm>
|
||||
</template>
|
||||
@@ -86,7 +90,7 @@
|
||||
|
||||
<script setup>
|
||||
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 { apiRequest } from "@/iview/utils/request";
|
||||
import IViewConfirm from "@/iview/IViewConfirm.vue";
|
||||
@@ -94,6 +98,7 @@ import ILayoutPage from "@/iview/ILayoutPage.vue";
|
||||
import TaskLogs from "./TaskLogs.vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const clearTaskLoading = ref(false);
|
||||
|
||||
const pageRef = ref(null);
|
||||
|
||||
@@ -182,6 +187,19 @@ const detailClick = (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) => {
|
||||
try {
|
||||
record.actionLoading = true;
|
||||
@@ -226,7 +244,7 @@ const config = ref({
|
||||
isDefault: true,
|
||||
pagination: {
|
||||
pageSize: 10,
|
||||
showTotal: (total) => total > 0 ? `共 ${total} 条` : "暂无数据",
|
||||
showTotal: (total) => total > 0 ? `共 ${total} 条` : "",
|
||||
},
|
||||
columns: recordColumns.value,
|
||||
filters: {
|
||||
@@ -255,7 +273,6 @@ const config = ref({
|
||||
recordMap: (record) => {
|
||||
return record;
|
||||
},
|
||||
hiddenLabel: true,
|
||||
columns: recordColumns.value,
|
||||
tabs: [
|
||||
{
|
||||
@@ -313,13 +330,12 @@ const config = ref({
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.name-click {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -329,7 +345,6 @@ const config = ref({
|
||||
|
||||
.name-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div class="record-page">
|
||||
<ILayoutPage ref="pageRef" :config="config">
|
||||
<ILayoutPage :config="config">
|
||||
<template #bodyCell="{ column, record, type }">
|
||||
<template v-if="column.dataIndex === 'recordDetail'">
|
||||
<pre class="log-params">{{ displayParams(taskInfo.taskParams) }}</pre>
|
||||
<LogContentPreview :content="record.logsContent" />
|
||||
</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([
|
||||
{
|
||||
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({
|
||||
infoPages: [
|
||||
{
|
||||
@@ -77,15 +78,4 @@ const config = ref({
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user