feat(prefetch): 优化预拉取日志显示与设置表单体验
- 预拉取日志支持分页加载,滚动到底自动加载更多 - 日志条目样式调整,优化响应数据展示布局 - 增加加载中和无更多日志的提示信息 - 设置表单拆分为豪猪平台设置、账号并发设置和面额库存设置三个部分 - 面额库存设置新增面额值重复校验,防止重复添加 - 调整表单布局,增加帮助提示,提升可用性与易用性 - 优化提示信息文本,统一简洁表达 - 组件加载时初始化日志和配置数据,增强交互流畅度
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user