diff --git a/src/components/common/log-drawer.vue b/src/components/common/log-drawer.vue
index 6d67c2e..d9af19a 100644
--- a/src/components/common/log-drawer.vue
+++ b/src/components/common/log-drawer.vue
@@ -1,6 +1,17 @@
<script setup lang="tsx">
-import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NDropdown, NEmpty, NSpin, NVirtualList } from 'naive-ui';
-import { defineComponent, onBeforeUnmount, ref, watch } from 'vue';
+import {
+ NButton,
+ NCard,
+ NCollapse,
+ NCollapseItem,
+ NDivider,
+ NDropdown,
+ NEmpty,
+ NSpin,
+ NVirtualList,
+ type VirtualListInst
+} from 'naive-ui';
+import { defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { fetchJobLogList, fetchRetryLogList } from '@/service/api/log';
import ButtonIcon from '@/components/custom/button-icon.vue';
@@ -29,6 +40,9 @@ const visible = defineModel<boolean>('show', {
default: false
});
+const isAutoScroll = ref(true);
+const isFullscreen = ref(true);
+const virtualListInst = ref<VirtualListInst>();
const syncTime = ref(1);
const logList = ref<Api.JobLog.JobMessage[]>([]);
const interval = ref<NodeJS.Timeout>();
@@ -94,6 +108,9 @@ async function getLogList() {
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
}
if (!finished.value && syncTime.value !== 0) {
+ nextTick(() => {
+ if (isAutoScroll.value) virtualListInst.value?.scrollTo({ position: 'bottom' });
+ });
clearTimeout(interval.value);
interval.value = setTimeout(getLogList, syncTime.value * 1000);
}
@@ -241,17 +258,29 @@ const SnailLogComponent = defineComponent({
return () => (
<code class="snail-log">
- <NVirtualList class="virtual-list" itemSize={65} items={logList.value}>
+ <NVirtualList
+ ref={virtualListInst}
+ class="virtual-list"
+ itemSize={85}
+ items={logList.value}
+ scrollbar-props={{ xScrollable: true }}
+ >
{{
default: ({ item: message }: { item: Api.JobLog.JobMessage }) => (
- <pre>
- <span class="log-hljs-time inline-block">{timestampToDate(message.time_stamp)}</span>
- <span class={`log-hljs-level-${message.level} ml-12px mr-12px inline-block`}>{`${message.level}`}</span>
- <span class="log-hljs-thread mr-12px inline-block">{`[${message.host}:${message.port}]`}</span>
- <span class="log-hljs-thread mr-12px inline-block">{`[${message.thread}]`}</span>
- <span class="log-hljs-location">{`${message.location}: \n`}</span> -
- <span class="pl-6px">{`${message.message}`}</span>
- {throwableComponent(message.throwable)}
+ <pre key={message.time_stamp} class="h-85px">
+ <div>
+ <span class="log-hljs-time inline-block">{timestampToDate(message.time_stamp)}</span>
+ <span
+ class={`log-hljs-level-${message.level} ml-12px mr-12px inline-block`}
+ >{`${message.level}`}</span>
+ <span class="log-hljs-thread mr-12px inline-block">{`[${message.host}:${message.port}]`}</span>
+ <span class="log-hljs-thread mr-12px inline-block">{`[${message.thread}]`}</span>
+ </div>
+ <div class="log-hljs-location">{`${message.location}: `}</div>
+ <div>
+ <span class="pl-6px">- {`${message.message}`}</span>
+ {throwableComponent(message.throwable)}
+ </div>
<NDivider />
</pre>
)
@@ -264,44 +293,68 @@ const SnailLogComponent = defineComponent({
</script>
<template>
- <NDrawer v-if="drawer" v-model:show="visible" width="100%" display-directive="if" :auto-focus="false">
+ <NDrawer
+ v-if="drawer"
+ v-model:show="visible"
+ :width="isFullscreen ? '100%' : '50%'"
+ display-directive="if"
+ :auto-focus="false"
+ >
<NDrawerContent closable>
<template #header>
- <div class="flex-center">
- <NTooltip v-if="finished">
- <template #trigger>
- <icon-material-symbols:check-circle class="text-20px color-success" />
- </template>
- 日志加载完成
- </NTooltip>
- <NTooltip v-else>
- <template #trigger>
- <NSpin size="small">
- <template #icon>
- <icon-nonicons:loading-16 />
- </template>
- </NSpin>
- </template>
- 日志正在加载
- </NTooltip>
- <span class="ml-6px">{{ title }}</span>
- <ButtonIcon icon="hugeicons:share-01" tooltip-content="在新标签页打开" class="ml-6px" @click="openNewTab" />
- <NDropdown trigger="hover" :options="syncOptions" width="trigger" @select="handleSyncSelect">
- <NTooltip placement="right">
+ <div class="flex items-center justify-between" :class="`tool-header${isFullscreen ? '-full' : ''}`">
+ <div class="flex-center">
+ <NTooltip v-if="finished">
<template #trigger>
- <NButton dashed class="ml-3px w-136px" @click="handleSyncSelect(-1)">
- <template #icon>
- <div class="flex-center gap-8px">
- <icon-solar:refresh-outline class="text-18px" />
- {{ syncOptions.filter(item => item.key === syncTime)[0].label }}
- <SvgIcon icon="material-symbols:expand-more-rounded" />
- </div>
- </template>
- </NButton>
+ <icon-material-symbols:check-circle class="text-20px color-success" />
</template>
- 自动刷新频率
+ 日志加载完成
</NTooltip>
- </NDropdown>
+ <NTooltip v-else>
+ <template #trigger>
+ <NSpin size="small">
+ <template #icon>
+ <icon-nonicons:loading-16 />
+ </template>
+ </NSpin>
+ </template>
+ 日志正在加载
+ </NTooltip>
+ <span class="ml-6px">{{ title }}</span>
+ <NDropdown trigger="hover" :options="syncOptions" width="trigger" @select="handleSyncSelect">
+ <NTooltip placement="right">
+ <template #trigger>
+ <NButton dashed class="ml-16px w-136px" @click="handleSyncSelect(-1)">
+ <template #icon>
+ <div class="flex-center gap-8px">
+ <icon-solar:refresh-outline class="text-18px" />
+ {{ syncOptions.filter(item => item.key === syncTime)[0].label }}
+ <SvgIcon icon="material-symbols:expand-more-rounded" />
+ </div>
+ </template>
+ </NButton>
+ </template>
+ 自动刷新频率
+ </NTooltip>
+ </NDropdown>
+ </div>
+ <div class="flex-center">
+ <NSwitch v-model:value="isAutoScroll" :round="false" size="large">
+ <template #checked>自动滚动</template>
+ <template #unchecked>自动滚动</template>
+ </NSwitch>
+ <ButtonIcon
+ size="tiny"
+ icon="hugeicons:share-01"
+ tooltip-content="在新标签页打开"
+ class="ml-6px"
+ @click="openNewTab"
+ />
+ <ButtonIcon size="tiny" @click="() => (isFullscreen = !isFullscreen)">
+ <icon-material-symbols:close-fullscreen-rounded v-if="isFullscreen" />
+ <icon-material-symbols:open-in-full-rounded v-else />
+ </ButtonIcon>
+ </div>
</div>
</template>
<div v-if="logList.length === 0" class="h-full flex-center">
@@ -314,10 +367,14 @@ const SnailLogComponent = defineComponent({
<NCard v-else :bordered="false" :title="title" size="small" class="h-full sm:flex-1-hidden card-wrapper">
<template #header-extra>
<div class="flex items-center">
+ <NSwitch v-model:value="isAutoScroll" :round="false" size="large">
+ <template #checked>自动滚动</template>
+ <template #unchecked>自动滚动</template>
+ </NSwitch>
<NDropdown trigger="hover" :options="syncOptions" width="trigger" @select="handleSyncSelect">
<NTooltip placement="right">
<template #trigger>
- <NButton dashed class="ml-3px w-136px" @click="handleSyncSelect(-1)">
+ <NButton dashed class="mx-16px w-136px" @click="handleSyncSelect(-1)">
<template #icon>
<div class="flex-center gap-8px">
<icon-solar:refresh-outline class="text-18px" />
@@ -371,7 +428,7 @@ const SnailLogComponent = defineComponent({
}
pre {
- white-space: pre-wrap;
+ // white-space: pre-wrap;
word-break: break-word;
margin: 0;
font-size: 16px;
@@ -429,4 +486,12 @@ const SnailLogComponent = defineComponent({
font-size: 18px !important;
margin-right: 6px;
}
+
+.tool-header-full {
+ width: calc(100vw - 72px);
+}
+
+.tool-header {
+ width: calc(50vw - 72px);
+}
</style>
diff --git a/src/views/job/batch/index.vue b/src/views/job/batch/index.vue
index e3cfae8..5392633 100644
--- a/src/views/job/batch/index.vue
+++ b/src/views/job/batch/index.vue
@@ -302,7 +302,7 @@ async function handleStopJob(id: string) {
:columns="columns"
:data="data"
:flex-height="!appStore.isMobile"
- :scroll-x="962"
+ :scroll-x="2000"
:loading="loading"
remote
:row-key="row => row.id"