- 在预拉取订单日志响应中新增实际最早和最晚日志时间字段 - 修改接口返回类型,添加 actualStartTime 和 actualEndTime 字段 - 组件中增加当前查询起止时间状态管理 - 优化日志列表分页加载逻辑,支持根据实际时间加载更早日志 - 修正滚动触底加载时机,避免重复加载 - 搜索后将滚动条重置至顶部 - 初始加载日志时,滚动条自动定位顶部展示最新日志
382 lines
9.7 KiB
Vue
382 lines
9.7 KiB
Vue
<template>
|
||
<div class="prefetch-logs">
|
||
<!-- 搜索表单区域 -->
|
||
<a-row>
|
||
<a-col :flex="1">
|
||
<a-form
|
||
:model="formModel"
|
||
:label-col-props="{ span: 6 }"
|
||
:wrapper-col-props="{ span: 18 }"
|
||
label-align="left"
|
||
>
|
||
<a-row :gutter="16">
|
||
<a-col :span="8">
|
||
<a-form-item field="startTime" label="开始时间">
|
||
<a-date-picker
|
||
v-model="formModel.startTime"
|
||
style="width: 100%"
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
show-time
|
||
placeholder="请选择开始时间"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
<a-col :span="8">
|
||
<a-form-item field="endTime" label="结束时间">
|
||
<a-date-picker
|
||
v-model="formModel.endTime"
|
||
style="width: 100%"
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
show-time
|
||
placeholder="请选择结束时间"
|
||
/>
|
||
</a-form-item>
|
||
</a-col>
|
||
</a-row>
|
||
</a-form>
|
||
</a-col>
|
||
<a-divider style="height: 42px" direction="vertical" />
|
||
<a-col flex="180px" style="text-align: right">
|
||
<a-space direction="horizontal" :size="18">
|
||
<a-button type="primary" @click="searchLogs">
|
||
<template #icon>
|
||
<icon-search />
|
||
</template>
|
||
搜索
|
||
</a-button>
|
||
<a-button @click="resetLogs">
|
||
<template #icon>
|
||
<icon-refresh />
|
||
</template>
|
||
重置
|
||
</a-button>
|
||
</a-space>
|
||
</a-col>
|
||
</a-row>
|
||
<a-divider style="margin-top: 0" />
|
||
|
||
<!-- 表格操作按钮 -->
|
||
<div class="table-actions">
|
||
<a-button @click="$emit('showSettings')">
|
||
<template #icon>
|
||
<icon-settings />
|
||
</template>
|
||
设置
|
||
</a-button>
|
||
</div>
|
||
|
||
<!-- 日志数据文本区域 -->
|
||
<div class="log-container">
|
||
<div class="log-header">
|
||
<span class="log-title">预拉取订单日志</span>
|
||
</div>
|
||
<div ref="logContentRef" class="log-content">
|
||
<div v-if="loading" class="log-loading">
|
||
<a-spin />
|
||
<span>加载日志中...</span>
|
||
</div>
|
||
<div v-else-if="logsRenderData.length === 0" class="log-empty">
|
||
暂无日志数据
|
||
</div>
|
||
<div v-else class="log-entries">
|
||
<div
|
||
v-for="(log, index) in logsRenderData"
|
||
:key="log.timestamp || index"
|
||
class="log-entry"
|
||
>
|
||
<div class="log-line">
|
||
<span class="log-time">{{ formatDateTime(log.timestamp) }}</span>
|
||
<span class="log-separator">|</span>
|
||
<span class="log-content">
|
||
{{ log.responseData || '无响应数据' }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<!-- 加载更多指示器 -->
|
||
<div v-if="loadingMore" class="log-loading-more">
|
||
<a-spin size="small" />
|
||
<span>加载更多日志...</span>
|
||
</div>
|
||
<!-- 无更多数据提示 -->
|
||
<div
|
||
v-else-if="!hasMore && logsRenderData.length > 0"
|
||
class="log-no-more"
|
||
>
|
||
已加载全部日志
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { reactive, ref, onMounted, nextTick } from 'vue';
|
||
import { Message } from '@arco-design/web-vue';
|
||
import useLoading from '@/hooks/loading';
|
||
import type { KamiApiCamelOilV1PrefetchOrderLogItem } from '@/api/generated/index.ts';
|
||
import { jdV2PrefetchClient } from '@/api/index.ts';
|
||
import dayjs from 'dayjs';
|
||
|
||
interface Emits {
|
||
(e: 'showSettings'): void;
|
||
}
|
||
|
||
const emit = defineEmits<Emits>();
|
||
|
||
const { loading, setLoading } = useLoading(false);
|
||
|
||
const formModel = reactive({
|
||
startTime: null,
|
||
endTime: null
|
||
});
|
||
|
||
const logsRenderData = ref<KamiApiCamelOilV1PrefetchOrderLogItem[]>([]);
|
||
const hasMore = ref(true);
|
||
const logContentRef = ref<HTMLElement>();
|
||
const loadingMore = ref(false);
|
||
const currentStartTime = ref<string | null>(null); // 当前查询的开始时间
|
||
const currentEndTime = ref<string | null>(null); // 当前查询的结束时间
|
||
|
||
const generateFormModel = () => {
|
||
const now = dayjs();
|
||
return {
|
||
startTime: now.subtract(10, 'minute').toDate(),
|
||
endTime: now.toDate()
|
||
};
|
||
};
|
||
|
||
// 初始化表单数据
|
||
formModel.startTime = generateFormModel().startTime;
|
||
formModel.endTime = generateFormModel().endTime;
|
||
|
||
const formatDateTime = (dateTime: string | undefined): string => {
|
||
if (!dateTime) return '-';
|
||
return dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss');
|
||
};
|
||
|
||
const fetchLogs = async (
|
||
reset = true,
|
||
startTimeOverride?: string,
|
||
endTimeOverride?: string
|
||
) => {
|
||
let queryStartTime: string;
|
||
let queryEndTime: string;
|
||
|
||
if (reset) {
|
||
// 初始查询或搜索,使用表单时间
|
||
if (!formModel.startTime || !formModel.endTime) {
|
||
Message.warning({
|
||
content: '请选择开始时间和结束时间',
|
||
closable: true
|
||
});
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
queryStartTime = dayjs(formModel.startTime).format('YYYY-MM-DD HH:mm:ss');
|
||
queryEndTime = dayjs(formModel.endTime).format('YYYY-MM-DD HH:mm:ss');
|
||
setLoading(true);
|
||
} else {
|
||
// 继续查询,使用传入的时间覆盖或当前时间往前倒1分钟
|
||
queryStartTime =
|
||
startTimeOverride ||
|
||
dayjs(currentStartTime.value)
|
||
.subtract(1, 'minute')
|
||
.format('YYYY-MM-DD HH:mm:ss');
|
||
queryEndTime = endTimeOverride || currentStartTime.value!; // 从当前最早时间开始
|
||
loadingMore.value = true;
|
||
}
|
||
|
||
try {
|
||
const { data } = await jdV2PrefetchClient.apiJdV2PrefetchLogsGet({
|
||
startTime: queryStartTime,
|
||
endTime: queryEndTime
|
||
});
|
||
|
||
const newLogs = data.logs || [];
|
||
const actualStartTime = data.actualStartTime;
|
||
const actualEndTime = data.actualEndTime;
|
||
|
||
if (reset) {
|
||
// 初始查询
|
||
logsRenderData.value = newLogs.reverse(); // 倒序显示,最新的在前面
|
||
currentStartTime.value = actualStartTime || queryStartTime;
|
||
currentEndTime.value = actualEndTime || queryEndTime;
|
||
hasMore.value = newLogs.length > 0 && actualStartTime !== null;
|
||
} else {
|
||
// 继续查询
|
||
logsRenderData.value.push(...newLogs.reverse()); // 倒序显示,追加在后面
|
||
currentStartTime.value = actualStartTime || queryStartTime;
|
||
// 如果没有返回更多数据或者实际查询时间没有变化,说明没有更多数据了
|
||
hasMore.value = newLogs.length > 0 && actualStartTime !== null;
|
||
}
|
||
} catch (err) {
|
||
console.error('获取预拉取订单日志失败:', err);
|
||
Message.error({
|
||
content: '获取预拉取订单日志失败',
|
||
closable: true
|
||
});
|
||
} finally {
|
||
setLoading(false);
|
||
loadingMore.value = false;
|
||
}
|
||
};
|
||
|
||
const loadMoreLogs = async () => {
|
||
if (!hasMore.value || loadingMore.value || loading.value) {
|
||
return;
|
||
}
|
||
|
||
await fetchLogs(false);
|
||
};
|
||
|
||
const handleScroll = (event: Event) => {
|
||
const target = event.target as HTMLElement;
|
||
const { scrollTop, scrollHeight, clientHeight } = target;
|
||
|
||
// 当滚动到底部(距离底部50px内)时加载更早的日志
|
||
if (scrollHeight - scrollTop <= clientHeight + 50) {
|
||
loadMoreLogs();
|
||
}
|
||
};
|
||
|
||
const searchLogs = () => {
|
||
fetchLogs(true);
|
||
// 搜索后重置滚动位置到顶部
|
||
nextTick(() => {
|
||
if (logContentRef.value) {
|
||
logContentRef.value.scrollTop = 0;
|
||
}
|
||
});
|
||
};
|
||
|
||
const resetLogs = () => {
|
||
const defaultData = generateFormModel();
|
||
formModel.startTime = defaultData.startTime;
|
||
formModel.endTime = defaultData.endTime;
|
||
searchLogs();
|
||
};
|
||
|
||
onMounted(() => {
|
||
fetchLogs(true);
|
||
|
||
// 添加滚动监听
|
||
nextTick(() => {
|
||
if (logContentRef.value) {
|
||
logContentRef.value.addEventListener('scroll', handleScroll);
|
||
// 初始状态滚动到顶部显示最新日志
|
||
logContentRef.value.scrollTop = 0;
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.prefetch-logs {
|
||
width: 100%;
|
||
}
|
||
|
||
.table-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* 日志容器样式 */
|
||
.log-container {
|
||
border: 1px solid var(--color-border-2);
|
||
border-radius: var(--border-radius-medium);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.log-header {
|
||
padding: 12px 16px;
|
||
background-color: var(--color-fill-1);
|
||
border-bottom: 1px solid var(--color-border-2);
|
||
}
|
||
|
||
.log-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--color-text-1);
|
||
}
|
||
|
||
.log-content {
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
background-color: var(--color-bg-2);
|
||
}
|
||
|
||
.log-loading,
|
||
.log-empty {
|
||
padding: 40px;
|
||
text-align: center;
|
||
color: var(--color-text-3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.log-entries {
|
||
padding: 16px;
|
||
}
|
||
|
||
.log-entry {
|
||
margin-bottom: 4px;
|
||
padding: 4px 8px;
|
||
background-color: var(--color-bg-1);
|
||
border-radius: var(--border-radius-small);
|
||
}
|
||
|
||
.log-entry:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.log-line {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.log-time {
|
||
font-size: 12px;
|
||
color: var(--color-text-3);
|
||
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.log-separator {
|
||
color: var(--color-text-3);
|
||
font-size: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.log-content {
|
||
font-size: 12px;
|
||
color: var(--color-text-2);
|
||
word-break: break-all;
|
||
flex: 1;
|
||
}
|
||
|
||
.log-loading-more {
|
||
padding: 16px;
|
||
text-align: center;
|
||
color: var(--color-text-3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.log-no-more {
|
||
padding: 16px;
|
||
text-align: center;
|
||
color: var(--color-text-3);
|
||
font-size: 12px;
|
||
}
|
||
</style>
|