feat(prefetch): 优化预拉取日志显示与设置表单体验

- 预拉取日志支持分页加载,滚动到底自动加载更多
- 日志条目样式调整,优化响应数据展示布局
- 增加加载中和无更多日志的提示信息
- 设置表单拆分为豪猪平台设置、账号并发设置和面额库存设置三个部分
- 面额库存设置新增面额值重复校验,防止重复添加
- 调整表单布局,增加帮助提示,提升可用性与易用性
- 优化提示信息文本,统一简洁表达
- 组件加载时初始化日志和配置数据,增强交互流畅度
This commit is contained in:
danial
2025-12-07 00:25:29 +08:00
parent 7af3c2759e
commit 84265b6076
2 changed files with 312 additions and 215 deletions

View File

@@ -84,17 +84,25 @@
:key="log.timestamp || index"
class="log-entry"
>
<div class="log-time">
{{ formatDateTime(log.timestamp) }}
</div>
<div class="log-response">
<div class="log-response-header">
<span>响应数据:</span>
</div>
<div class="log-response-content">
<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>
@@ -103,7 +111,7 @@
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
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';
@@ -124,6 +132,12 @@ const formModel = reactive({
});
const logsRenderData = ref<KamiApiCamelOilV1PrefetchOrderLogItem[]>([]);
const allLogs = ref<KamiApiCamelOilV1PrefetchOrderLogItem[]>([]);
const currentPage = ref(1);
const pageSize = ref(50);
const hasMore = ref(true);
const logContentRef = ref<HTMLElement>();
const loadingMore = ref(false);
const generateFormModel = () => {
const now = dayjs();
@@ -142,7 +156,7 @@ const formatDateTime = (dateTime: string | undefined): string => {
return dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss');
};
const fetchLogs = async () => {
const fetchLogs = async (reset = true) => {
if (!formModel.startTime || !formModel.endTime) {
Message.warning({
content: '请选择开始时间和结束时间',
@@ -152,15 +166,34 @@ const fetchLogs = async () => {
return;
}
if (reset) {
setLoading(true);
} else {
loadingMore.value = true;
}
try {
const { data } = await jdV2PrefetchClient.apiJdV2PrefetchLogsGet({
startTime: dayjs(formModel.startTime).format('YYYY-MM-DD HH:mm:ss'),
endTime: dayjs(formModel.endTime).format('YYYY-MM-DD HH:mm:ss')
});
// 直接使用API返回的全部日志数据
logsRenderData.value = data.logs || [];
const newLogs = data.logs || [];
if (reset) {
allLogs.value = newLogs;
logsRenderData.value = newLogs.slice(0, pageSize.value);
currentPage.value = 1;
hasMore.value = newLogs.length > pageSize.value;
} else {
const nextBatch = newLogs.slice(
currentPage.value * pageSize.value,
(currentPage.value + 1) * pageSize.value
);
logsRenderData.value.push(...nextBatch);
currentPage.value++;
hasMore.value = currentPage.value * pageSize.value < newLogs.length;
}
} catch (err) {
console.error('获取预拉取订单日志失败:', err);
Message.error({
@@ -169,11 +202,30 @@ const fetchLogs = async () => {
});
} 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 (scrollTop + clientHeight >= scrollHeight - 50) {
loadMoreLogs();
}
};
const searchLogs = () => {
fetchLogs();
fetchLogs(true);
};
const resetLogs = () => {
@@ -183,8 +235,16 @@ const resetLogs = () => {
searchLogs();
};
// 组件挂载时加载数据
fetchLogs();
onMounted(() => {
fetchLogs(true);
// 添加滚动监听
nextTick(() => {
if (logContentRef.value) {
logContentRef.value.addEventListener('scroll', handleScroll);
}
});
});
</script>
<style scoped>
@@ -239,10 +299,9 @@ fetchLogs();
}
.log-entry {
margin-bottom: 16px;
padding: 12px;
margin-bottom: 4px;
padding: 4px 8px;
background-color: var(--color-bg-1);
border: 1px solid var(--color-border-2);
border-radius: var(--border-radius-small);
}
@@ -250,35 +309,49 @@ fetchLogs();
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);
margin-bottom: 8px;
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
white-space: nowrap;
flex-shrink: 0;
}
.log-response-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.log-separator {
color: var(--color-text-3);
font-size: 12px;
flex-shrink: 0;
}
.log-response-header span {
font-size: 13px;
font-weight: 500;
color: var(--color-text-1);
}
.log-response-content {
.log-content {
font-size: 12px;
color: var(--color-text-2);
line-height: 1.5;
word-break: break-all;
background-color: var(--color-fill-1);
padding: 8px;
border-radius: var(--border-radius-small);
max-height: 200px;
overflow-y: auto;
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>

View File

@@ -7,7 +7,17 @@
label-align="left"
@submit="handleSubmit"
>
<!-- 基础设置区域 -->
<!-- 第一部分豪猪平台设置 -->
<a-card title="豪猪平台设置" :bordered="false">
<template #extra>
<a-space>
<icon-mobile />
<span style="color: var(--color-text-3); font-size: 13px">
配置豪猪平台的登录信息和账号获取设置
</span>
</a-space>
</template>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="useHaozhuPlatform" label="豪猪平台">
@@ -15,10 +25,7 @@
v-model="formModel.useHaozhuPlatform"
checked-text="启用"
unchecked-text="禁用"
>
<template #checked-text>启用</template>
<template #unchecked-text>禁用</template>
</a-switch>
/>
<template #help>是否从豪猪平台获取手机号登录</template>
</a-form-item>
</a-col>
@@ -85,6 +92,18 @@
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 第二部分账号并发设置 -->
<a-card title="账号并发设置" :bordered="false" style="margin-top: 16px">
<template #extra>
<a-space>
<icon-thunderbolt />
<span style="color: var(--color-text-3); font-size: 13px">
配置预拉取订单的并发账号数量和单个账号的并发数
</span>
</a-space>
</template>
<a-row :gutter="24">
<a-col :span="12">
@@ -134,9 +153,21 @@
</a-form-item>
</a-col>
</a-row>
</a-card>
<div class="denominations-section">
<div class="denominations-actions">
<!-- 第三部分面额库存设置 -->
<a-card title="面额库存设置" :bordered="false" style="margin-top: 16px">
<template #extra>
<a-space>
<icon-currency-dollar />
<span style="color: var(--color-text-3); font-size: 13px">
配置不同面额卡券的最小库存和目标库存当库存低于最小值时自动补充到目标值
</span>
</a-space>
</template>
<!-- 添加面额按钮 -->
<div style="margin-bottom: 16px; text-align: right">
<a-button type="primary" size="small" @click="addDenomination">
<template #icon>
<icon-plus />
@@ -162,13 +193,14 @@
:min="1"
placeholder="如100、200、500"
style="width: 100%"
@change="validateDenomination(rowIndex)"
/>
</template>
<template #minCapacity="{ rowIndex }">
<a-input-number
v-model="formModel.targetDenominations[rowIndex].minCapacity"
:min="0"
placeholder="库存低于此值时触发补充"
placeholder="库存低于此值时触发补充可为0"
style="width: 100%"
/>
</template>
@@ -196,11 +228,11 @@
</a-table>
<a-empty v-else description="暂无面额设置,请添加面额配置" />
</div>
</a-card>
<!-- 操作按钮区域 -->
<div class="form-actions">
<a-row justify="center">
<a-row justify="end">
<a-col>
<a-space :size="16">
<a-button type="primary" html-type="submit" :loading="loading">
@@ -278,9 +310,6 @@ const columns: TableColumnData[] = [
];
const addDenomination = () => {
if (!formModel.targetDenominations) {
formModel.targetDenominations = [];
}
formModel.targetDenominations.push({
denomination: null,
minCapacity: null,
@@ -288,6 +317,23 @@ const addDenomination = () => {
});
};
const validateDenomination = (index: number) => {
const currentItem = formModel.targetDenominations[index];
if (!currentItem.denomination) return;
// 检查是否有重复的面额
const duplicateIndex = formModel.targetDenominations.findIndex(
(item, i) => i !== index && item.denomination === currentItem.denomination
);
if (duplicateIndex !== -1) {
Message.warning(
`面额 ${currentItem.denomination} 已存在,请修改为不同的面额值`
);
currentItem.denomination = null;
}
};
const removeDenomination = (index: number) => {
formModel.targetDenominations.splice(index, 1);
};
@@ -297,10 +343,7 @@ const handleSubmit = async () => {
// 验证豪猪平台设置
if (formModel.useHaozhuPlatform) {
if (!formModel.haozhuUsername || !formModel.haozhuPassword) {
Message.warning({
content: '启用豪猪平台时请填写用户名和密码',
duration: 3000
});
Message.warning('启用豪猪平台时请填写用户名和密码');
return;
}
}
@@ -312,21 +355,29 @@ const handleSubmit = async () => {
) {
for (let i = 0; i < formModel.targetDenominations.length; i++) {
const item = formModel.targetDenominations[i];
if (!item.denomination || !item.minCapacity || !item.targetCapacity) {
Message.warning({
content: `请完善第${i + 1}个面额设置的必填项`,
duration: 3000
});
if (
!item.denomination ||
item.minCapacity === null ||
!item.targetCapacity
) {
Message.warning(`请完善第${i + 1}个面额设置的必填项`);
return;
}
if (item.minCapacity >= item.targetCapacity) {
Message.warning({
content: `${i + 1}个面额设置的最小库存必须小于目标库存`,
duration: 3000
});
Message.warning(`${i + 1}个面额设置的最小库存必须小于目标库存`);
return;
}
}
// 检查面额重复
const denominations = formModel.targetDenominations.map(
item => item.denomination
);
const uniqueDenominations = [...new Set(denominations)];
if (denominations.length !== uniqueDenominations.length) {
Message.warning('存在重复的面额设置,请确保每个面额值都是唯一的');
return;
}
}
setLoading(true);
@@ -334,16 +385,10 @@ const handleSubmit = async () => {
kamiApiCamelOilV1UpdateSettingsReq: { ...formModel }
});
Message.success({
content: '设置保存成功',
duration: 2000
});
Message.success('设置保存成功');
} catch (error) {
console.error('保存设置失败:', error);
Message.error({
content: '保存设置失败',
duration: 2000
});
Message.error('保存设置失败');
} finally {
setLoading(false);
}
@@ -364,38 +409,17 @@ const loadSettings = async () => {
}
} catch (error) {
console.error('获取设置失败:', error);
Message.error({
content: '获取设置失败',
duration: 2000
});
Message.error('获取设置失败');
} finally {
setLoading(false);
}
};
// 组件挂载时加载设置
onMounted(() => {
loadSettings();
});
onMounted(loadSettings);
</script>
<style scoped>
.denominations-section {
margin-top: 20px;
}
.denominations-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 16px;
}
.section-title {
font-weight: 500;
color: var(--color-text-1);
font-size: 14px;
}
.form-actions {
margin-top: 32px;
padding-top: 20px;