前情提要
平台端的导出都是在当前页面导出且只请求一次,导出数据量大时会等待很久,失败率也很高。公共导出在公用导航组件,导出的同时不影响页面操作,且逻辑为轮询请求后台是否导出完成,提供导出进度,降低大数据导出失败率。(不感兴趣的直接看最后的使用方法)
功能要点
1.配置过的页面导出文件都可在列表查看进度
2.文件数据存在oss不用再次导出就可以下载
3.清空导出列表
配置导出
1.在需要展示导出的位置引用:
import OfflineExport from "./OfflineExport";
<OfflineExport />
页面位置 
===显示位置===
//在需要显示的位置引入
import OfflineExport from "./OfflineExport";
<OfflineExport />
===store===
//公共参数来管理:有新的导出时标记绿点,以及导出时默认打开导出列表
const GlobalModel = {
namespace: "global",
state: {
isInitiateExport: false, //标示有正在导出的绿点
showExportList: false, //点击导出后默认展开导出列表
},
effects: {
},
reducers: {
changeIsInitiateExport(
state = {
isInitiateExport: false,
},
{ payload }
) {
return { ...state, isInitiateExport: payload };
},
changeShowExportList(
state = {
showExportList: false,
},
{ payload }
) {
return { ...state, showExportList: payload };
},
}
};
export default GlobalModel;
===OfflineExport===
// 导出组件,所有导出的处理都在这
import { Badge, Button, Dropdown, List, Spin, Progress } from "antd";
import React, { useEffect, useState, useRef} from "react";
import { saveAs } from "file-saver";
import { connect } from "umi";
import styles from "./OfflineExport.less";
import {
DownloadOutlined,
SnippetsOutlined,
CloseOutlined,
} from "@ant-design/icons";
import {
exportOrderByOperationList,
excelExportLogDel,
} from "@/services/order";
// 导出列表
const OfflineExport = (props) => {
const { isInitiateExport, showExportList, dispatch } = props;
const [dataList, setDataList] = useState([]); // 数据列表
const [listLoading, setListLoading] = useState(false); // list loading
let visibleRef = useRef();
let timer;
const exportLogDel = async () => {
clearTimeout(timer);
try {
const res = await excelExportLogDel({});
if (res.code) {
console.log("清空导出列表的返回:", res);
changeVisibleExport();
}
} catch (error) {
console.log(error);
}
};
// 导航栏图标,点击取消绿点,请求列表
const downloadIconClick = () => {
if (!visibleRef.current) {
console.log("false时执行清除计时器:", visibleRef.current);
clearTimeout(timer);
return;
}
exportOrderByOperationList({ offset: 0, limit: 10 })
.then((res) => {
if (res.code === "200") {
clearTimeout(timer);
console.log("请求回来的数据==", res);
listLoading ? "" : setListLoading(false);
setDataList(res.data); // 列表数据
const progressList = res.data.map((item) => parseInt(item.progress));
const errorProgress = progressList.filter((n) => isNaN(n));
const num = progressList.filter((n) => n === 100).length;
console.log(
"总进度长度:",
progressList,
"error的长度",
errorProgress,
"总导出长度-失败导出长度:",
progressList.length - errorProgress.length,
"成功的长度",
num
);
//在数据总长度-失败的数据长度!== 导出进度100的数据长度 && 下拉菜单是展开的,这种情况下执行计时器
if (
progressList.length - errorProgress.length !== num &&
visibleRef.current
) {
dispatch({
type: "global/changeIsInitiateExport",
payload: true,
});
clearTimeout(timer);
timer = setTimeout(() => downloadIconClick(), 10000);
console.log("执行计时器中。。。");
} else {
console.log("clear timer");
dispatch({
type: "global/changeIsInitiateExport",
payload: false,
});
clearTimeout(timer);
return;
}
}
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
visibleRef.current = showExportList;
downloadIconClick();
return () => clearTimeout(timer);
}, [showExportList]);
// 改变导出弹窗的显示隐藏
const changeVisibleExport = (v) => {
const show = v ? v : false;
visibleRef.current = show;
if (dispatch) {
dispatch({
type: "global/changeShowExportList",
payload: show,
});
}
console.log(
"改变导出弹窗的显示隐藏:",
"公共展示弹窗:",
showExportList,
"关闭事件中的弹窗:",
show
);
};
// 导航栏图标及气泡
const trigger = (
<span className={[styles.action, styles.noticeButton]}>
<Badge
color="green"
dot={isInitiateExport}
style={{
boxShadow: "none",
}}
className={styles.badge}
>
<DownloadOutlined className={styles.icon} />
</Badge>
</span>
);
// 导出列表
const exportList = () => (
<div className={styles.exportListBox}>
<List
loading={listLoading}
header={
<div className={styles.exportListHeader}>
<span>导出列表</span>
<CloseOutlined
onClick={() => changeVisibleExport()}
className={styles.exportListClose}
/>
</div>
}
footer={
<div style={{ textAlign: "center" }}>
<Button onClick={() => exportLogDel()}>清空导出记录</Button>
</div>
}
bordered
dataSource={dataList}
size="small"
renderItem={(item) => (
<List.Item>
<Spin
style={{
visibility:
parseInt(item.progress) !== 100 &&
!isNaN(parseInt(item.progress))
? "visible"
: "hidden",
}}
/>
<div className={styles.exportListItem}>
<div className={styles.exportListContent}>
<SnippetsOutlined /> {item.fileName}
</div>
{parseInt(item.progress) !== 100 ? (
isNaN(parseInt(item.progress)) ? (
<span style={{ color: "red" }}>导出失败</span>
) : (
<div>
正在导出
<Progress
percent={parseInt(item.progress)}
size="small"
status={
isNaN(parseInt(item.progress)) ? "exception" : "active"
}
/>
</div>
)
) : (
<Button
type="link"
size="small"
onClick={() => saveAs(item.fileUrl, item.fileName)}
>
下载文件
</Button>
)}
</div>
</List.Item>
)}
/>
</div>
);
return (
<>
<Dropdown
overlay={exportList}
trigger={["click"]}
className={styles.offlineExport}
destroyPopupOnHide
visible={showExportList}
onVisibleChange={changeVisibleExport}
>
{trigger}
</Dropdown>
</>
);
};
export default connect(({ global }) => ({
isInitiateExport: global.isInitiateExport,
showExportList: global.showExportList,
}))(OfflineExport);
===导出按钮页面===
//请求发起导出,打开导出列表,开始轮询是否导出完成
import React, { FC, SetStateAction,Dispatch, useState } from "react";
import { ConnectProps, Dispatch as ConnectDispatch } from "@/.umi/plugin-dva/connect";
const ExportList: FC<{
dispatch: ConnectDispatch;
}> = (props) => {
const { dispatch } = props;
const [loading, setLoading] = useState(false);
//接口请求成功后调用公共方法打开导出列表
const handleExport = (paramsIs) => {
if (dispatch) {
dispatch({
type: "global/changeIsInitiateExport",
payload: paramsIs,
});
dispatch({
type: "global/changeShowExportList",
payload: paramsIs,
});
}
};
//导出按钮的事件
const onClick = async () => {
try {
setLoading(true);
const res = await serviceAPI(state);
if (res.data) {
handleExport(true);
setLoading(false);
} else {
setLoading(false);
}
} catch (error) {
console.log(error);
setLoading(false);
}
};
return (
//导出按钮
<Button loading={loading} onClick={onClick}>
导出
</Button>
);
};
interface AcquisitionCardExtraProps extends ConnectProps {
setShowModal: Dispatch<SetStateAction<boolean>>;
}
const CardExtra: React.FC<AcquisitionCardExtraProps> = ({ dispatch }) => {
return (
<ExportList dispatch={dispatch} />
);
}
export default connect(({ global }) => ({
isInitiateExport: global.isInitiateExport,
}))(CardExtra);
使用方法
1.前提:前后端需要协定好,(使用导出的接口,需要通知到前端可以打开导出列表了)
<Button onClick={()=>downLoad(record)}>导出</Button >
2.收到通知后打开导出列表,(handleExport(true);此处无需修改,拿来即用)
import { history, connect } from "umi";
const { dispatch } = props;
// 正在导出的标记值(该方法是不可或缺的)
const handleExport = (paramsIs) => {
if (dispatch) {
dispatch({
type: "global/changeIsInitiateExport",
payload: paramsIs,
});
dispatch({
type: "global/changeShowExportList",
payload: paramsIs,
});
}
};
const downLoad = async (params) => {
try {
const res = await service(params);
if (res.data) {
handleExport(true);
}
} catch (error) {}
};
export default connect(({ global }) => ({
isInitiateExport: global.isInitiateExport, /* 拿来即用 */
}))(Xxx);
3.其余部分交给处理,无需修改,拿来即用
目前被问到的问题
1.在页面上多次点击导出按钮:添加多条导出记录,多次打开导出列表,
导出列表是Dropdown,点击其余地方会关闭,所以多次点击会多次执行关闭打开操作。
2.导出按钮的关闭“X”,仅仅执行了一个操作,就是把changeShowExportList变成false。
3.导出组件内部的接口:分别是导出列表的接口和清空导出列表的接口,
要托管导出的地方需要后端对接口进行处理。导出按钮只给后端传导出参数,导出进度及导出文件都在导出列表接口。