feat: 显示 MKH 数据更新进度 #36
@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Alert, Button, Card, Collapse, Dropdown, Input, Modal, Segmented, Select, Space, Spin, Switch,
|
||||
Alert, Button, Card, Collapse, Dropdown, Input, Modal, Progress, Segmented, Select, Space, Spin, Switch,
|
||||
Table, Tooltip, Typography, message,
|
||||
} from 'antd';
|
||||
import {
|
||||
@ -37,6 +37,19 @@ const EMPTY_FILTERS = { keyword: '', scope: 'warning' };
|
||||
// 会合并为每个缓存周期一次业务库读取,不会每 2 分钟重复扫远程大表。
|
||||
const AUTO_REFRESH_MS = 2 * 60 * 1000;
|
||||
|
||||
const getRefreshProgress = (elapsedSeconds) => {
|
||||
if (elapsedSeconds < 3) {
|
||||
return { percent: 12 + elapsedSeconds * 8, stage: '正在读取实时库存、箱表和加工单…' };
|
||||
}
|
||||
if (elapsedSeconds < 15) {
|
||||
return { percent: 36 + (elapsedSeconds - 3) * 3, stage: '正在核对待发货订单和近15天销量…' };
|
||||
}
|
||||
return {
|
||||
percent: Math.min(95, 72 + Math.floor((elapsedSeconds - 15) / 2)),
|
||||
stage: '正在合并最新数据与 BOM 配件结果…',
|
||||
};
|
||||
};
|
||||
|
||||
// 预警等级 → 标签配色
|
||||
const WARNING_TONE = {
|
||||
缺货: 'danger',
|
||||
@ -266,6 +279,7 @@ const MkhBomData = () => {
|
||||
// 导出(当前筛选条件下的全部数据)
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [refreshProgress, setRefreshProgress] = useState({ open: false, elapsedSeconds: 0, title: '' });
|
||||
|
||||
// 补货预警推送设置
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
@ -280,10 +294,35 @@ const MkhBomData = () => {
|
||||
const tableWrapRef = useRef(null);
|
||||
const listRequestGateRef = useRef(createRequestGate());
|
||||
const detailGenerationRef = useRef(0);
|
||||
const refreshProgressGenerationRef = useRef(0);
|
||||
const lastListRefreshAtRef = useRef(0);
|
||||
const autoRefreshPendingRef = useRef(false);
|
||||
const [bodyHeight, setBodyHeight] = useState(420);
|
||||
|
||||
const beginRefreshProgress = useCallback((title) => {
|
||||
refreshProgressGenerationRef.current += 1;
|
||||
const generation = refreshProgressGenerationRef.current;
|
||||
setRefreshProgress({ open: true, elapsedSeconds: 0, title, generation });
|
||||
return generation;
|
||||
}, []);
|
||||
|
||||
const endRefreshProgress = useCallback((generation) => {
|
||||
if (generation !== refreshProgressGenerationRef.current) return;
|
||||
setRefreshProgress((current) => ({ ...current, open: false }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!refreshProgress.open) return undefined;
|
||||
const startedAt = Date.now();
|
||||
const timer = window.setInterval(() => {
|
||||
setRefreshProgress((current) => ({
|
||||
...current,
|
||||
elapsedSeconds: Math.max(0, Math.floor((Date.now() - startedAt) / 1000)),
|
||||
}));
|
||||
}, 1000);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [refreshProgress.open]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = tableWrapRef.current;
|
||||
if (!el) return undefined;
|
||||
@ -320,12 +359,16 @@ const MkhBomData = () => {
|
||||
|
||||
const load = useCallback(async ({ background = false } = {}) => {
|
||||
const requestGeneration = listRequestGateRef.current.begin();
|
||||
let showProgress = false;
|
||||
let progressGeneration = null;
|
||||
if (!background) setLoading(true);
|
||||
if (!background) setError('');
|
||||
try {
|
||||
const now = Date.now();
|
||||
const forceFresh = !lastListRefreshAtRef.current
|
||||
|| now - lastListRefreshAtRef.current >= AUTO_REFRESH_MS;
|
||||
showProgress = forceFresh && !background;
|
||||
if (showProgress) progressGeneration = beginRefreshProgress('正在更新最新业务数据');
|
||||
// 首次打开/F5 和定时轮询才重读云库;筛选、分页只复用刚刷新的内存索引。
|
||||
// 后端仍会合并多用户的并发刷新,不会对业务云库造成重复全量查询。
|
||||
const result = await getMkhBomList({
|
||||
@ -347,8 +390,9 @@ const MkhBomData = () => {
|
||||
setError(requestError?.response?.data?.error || requestError?.message || '加载 MKH-BOM 资料失败');
|
||||
} finally {
|
||||
if (!background && listRequestGateRef.current.isCurrent(requestGeneration)) setLoading(false);
|
||||
if (showProgress) endRefreshProgress(progressGeneration);
|
||||
}
|
||||
}, [query]);
|
||||
}, [beginRefreshProgress, endRefreshProgress, query]);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
@ -850,6 +894,7 @@ const MkhBomData = () => {
|
||||
const handleSync = async () => {
|
||||
setSyncing(true);
|
||||
setLoading(true);
|
||||
const progressGeneration = beginRefreshProgress('正在强制同步全部最新数据');
|
||||
// 立即废弃同步前发出的列表/明细请求;它们即使稍后返回,也不能覆盖新快照。
|
||||
listRequestGateRef.current.invalidate();
|
||||
detailGenerationRef.current += 1;
|
||||
@ -874,11 +919,31 @@ const MkhBomData = () => {
|
||||
} finally {
|
||||
if (listRequestGateRef.current.isCurrent(syncGeneration)) setLoading(false);
|
||||
setSyncing(false);
|
||||
endRefreshProgress(progressGeneration);
|
||||
}
|
||||
};
|
||||
|
||||
const progressView = getRefreshProgress(refreshProgress.elapsedSeconds);
|
||||
|
||||
return (
|
||||
<SycmPage className="mkh-page">
|
||||
<Modal
|
||||
open={refreshProgress.open}
|
||||
title={refreshProgress.title}
|
||||
footer={null}
|
||||
closable={false}
|
||||
keyboard={false}
|
||||
maskClosable={false}
|
||||
centered
|
||||
width={480}
|
||||
>
|
||||
<Space direction="vertical" size={14} style={{ width: '100%' }}>
|
||||
<Progress percent={progressView.percent} status="active" strokeColor="#1677ff" />
|
||||
<Text>{progressView.stage}</Text>
|
||||
<Text type="secondary">已用时 {refreshProgress.elapsedSeconds} 秒,完成后页面会自动显示新数据。</Text>
|
||||
<Text type="secondary">进度为查询阶段估算,实际完成以服务端返回为准。</Text>
|
||||
</Space>
|
||||
</Modal>
|
||||
<header className="mkh-header">
|
||||
<div className="mkh-header__heading">
|
||||
<Title level={3}>MKH-BOM 资料</Title>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user