Files
kami_frontend/src/views/camel-oil-info/prefetch/components/prefetch-logs.vue
danial 10a8c21fab feat(prefetch): 增加实际查询时间字段及日志分页加载功能
- 在预拉取订单日志响应中新增实际最早和最晚日志时间字段
- 修改接口返回类型,添加 actualStartTime 和 actualEndTime 字段
- 组件中增加当前查询起止时间状态管理
- 优化日志列表分页加载逻辑,支持根据实际时间加载更早日志
- 修正滚动触底加载时机,避免重复加载
- 搜索后将滚动条重置至顶部
- 初始加载日志时,滚动条自动定位顶部展示最新日志
2025-12-08 15:01:40 +08:00

382 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>