工作分享两不误
第四季度,突然就忙碌了起来,除了完成业务需求,不断实现自我能力提升,以及制定前端新的季度规划,还要关注团队成员的技术成长。所以我开始以我自己的领导为榜样(我*很睿智稳重,能力超级强),学习他处理问题的方法。感谢我领导,我可以不用摸着石头过河。
虽然日常事情比较多,但是有了好的想法或者功能,我还是忍不住想分享出来。因为我的同事对我的帮助也是温暖而细致的。(我的天使同事们,我的问题必然是完完全全,从实现到原理,都让我理解的通透了。)
这不最近的需求里有一个批量导入的功能,我翻了一下项目代码,大部分批量导入功能都是直接加到页面里的,没有做模块化处理,于是我结合对业务的理解以及未来可扩展的大致预测,把批量导入封装成为一个业务组件。
项目基于React框架开发的,所以代码写法是JSX语法,组件开发使用的hooks函数式组件,UI框架使用的是antd。
灵感源于快乐
把流水的业务需求,改变成主动的寻找开发灵感,每次有新的开发收获,我就会给自己一朵小红花,谁会嫌小红花多呢。
多个批量导入功能的流程
我梳理的流程图如下:
组件封装
业务场景的思考
有时候同一个产品,对于相同的功能,设计成不同的交互。我们系统里的批量导入有两种,一种是弹窗交互的方式进行文件导入,一种是页面直接展示导入按钮组。
弹窗交互方式
页面上直接展示导入按钮组
上面无论哪种,选择文件功能和导入确定功能是一样的。但是交互方式不一样,前端进行布局处理也会不一样。所以我在产品同事评审需求的时候会提前沟通交互方式。另外对于公司内部的后台管理系统,我个人更倾向简约设计,相似功能不推荐交互方式做的五花八门。所以目前我们系统里的批量导入功能只有这两种交互方式。今天分享的是页面上直接操作上传导入的这种方式的业务功能组件化处理。
功能的可拓展
批量导入的功能相对简单,目前可拓展的有以下几个点
- 导入按钮支持单个或者多个;
- 下载模板按钮支持单个或者多个;
- 支持多个导入只需要一个下载模板按钮的存在。
功能实现
- 选择文件功能使用antd提供的上传组件;
- 导入按钮使用的是数组对象,通过map方法直接循环渲染;
- 根据产品的需求,导入和模板是分开的,所以前端布局也就分开, 不过数据和导入是同一个数组对象。
- 正如上面流程图的设计,当页面存在多个导入按钮的时候,会进行按钮操作的控制。每个导入按钮设置了可操作开关,一次导入操作中只有一个按钮是可以操作的。
/**
* @description 公共业务组件-批量上传 目前多个批量上传操作
*/
import React, { useState, Fragment } from 'react';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
import _ from 'lodash';
import axios from 'axios';
import { Button, Row, Col, Upload, Space, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import style from './style';
const CommonMultipleBatchImport = ({ ...props }) => {
const { importList, callback } = props;
const history = useHistory();
const [fileList, setFileList] = useState([]);
const [file, setFile] = useState(null);
const [importListInit, setImportListInit] = useState(_.cloneDeep(importList));
/**
* 上传前事件
* @param {Object} file 上传的文件对象
* @return {void} 无
*/
const beforeUpload = file => {
setFile(file);
setFileList([file]);
return false;
};
/**
* 上传事件
* @param {Object} item 当前操作的上传对象
* @return {void} 无
*/
const upload = (item, index) => {
if (!file) {
return message.error('请选择文件');
}
message.info('祝福上传中,请稍后');
let list = _.cloneDeep(importListInit);
list.map((itm, inx) => {
// 上传操作项加载和不可点击的处理
itm.loading = inx === index ? true : false;
itm.disabled = true;
});
setImportListInit(list);
const formData = new FormData();
formData.append('file', file);
axios
.post(item.httpMethod, formData, { nobody: true })
.then(() => {
message.success('祝福批量导入成功,您的家人朋友不久便能收到~');
})
.finally(() => {
let listFinally = _.cloneDeep(importListInit);
listFinally.map(itm => {
itm.loading = false;
itm.disabled = false;
});
setImportListInit(listFinally);
setFileList([]);
setFile(null);
callback && callback(true);
});
};
/**
* 文件移除事件
* @param {void} 无
* @return {void} 无
*/
const onRemove = () => {
setFile(null);
setFileList([]);
};
/**
* 下载模板
* @param {Object} item 当前操作的上传对象
* @return {void} 无
*/
const downTemplate = item => {
location.href = item.downUrl;
};
return (
<Row align="middle" className={style['multiple-datch-import']}>
<Col className="file-col mr10">
<Upload accept=".xlsx,.xls" onRemove={onRemove} fileList={fileList} beforeUpload={beforeUpload}>
<Button icon={<UploadOutlined />}>选择文件</Button>
</Upload>
</Col>
<Col span={6}>
<Space>
{/* 操作按钮 */}
{importListInit.map((item, index) => {
return (
<Button key={item.key} type="primary" onClick={() => upload(item, index)} loading={item.loading} disabled={item.disabled || !file}>
{item.name}
</Button>
);
})}
{/* 下载模板 */}
{importListInit.map(item => {
return (
<Fragment key={item.key}>
{item.downUrl ? (
<Button type="primary" onClick={() => downTemplate(item)}>
{item.downName}
</Button>
) : null}
</Fragment>
);
})}
</Space>
</Col>
</Row>
);
};
CommonMultipleBatchImport.propTypes = {
importList: PropTypes.array.isRequired, // 上传内容的数组对象
callback: PropTypes.func, // 操作的回调
};
CommonMultipleBatchImport.defaultProps = {
importList: [],
};
export default CommonMultipleBatchImport;
组件使用
组件引入
我们的业务组件都放到了/bundleComponents下,所以引入的时候也会找对应的文件位置。
list.jsx
// 组件引入
import { CommonMultipleBatchImport } from '@/bundleComponents';
// 页面使用
<CommonMultipleBatchImport callback={listQuery} importList={importList} />
组件通信
props传参
- 下载模板:值可以为空,当它的值为空时,页面不展示下载模板按钮;
- 导入接口请求地址:我们的导入处理,是直接拿到文件流通过接口传给后端,后端再对文件数据做处理。不同的公司可能有不同的处理方式,此处可以根据实际情况重新处理。
// 导入 操作数组对象
const importList = [
{
key: 'fuqi', // 导入 操作key值
name: '批量导入福气', // 导入 操作按钮名称
downName: '下载模版', // 下载模板按钮名称
downUrl: '实际的下载模板链接', // 下载模板打开链接
httpMethod: '实际的接口请求地址', // 导入接口请求地址
},
{
key: 'zhufu',
name: '批量导入祝福',
downName: '下载删除模版',
downUrl: '', // 下载模板打开链接 值为空的按钮不展示
httpMethod: '实际的接口请求地址',
},
];
回调函数
因为数据导入成功之后需要更新当前页面,所以在导入成功的逻辑里做了回调处理,回调后会进行列表页面的刷新。
const listQuery = () => {
list.query();
};
总结
批量导入的功能,不是很复杂,尤其把操作梳理清楚,每一种情况都模拟清楚,其实代码量是很少的。
重要的是,遇到相似的业务功能,可以先做功能设计,再敲代码。有些功能虽然展示内容不一样,但是流程和核心功能是一致的,就可以考虑功能模块化封装。这样的好处是,既能在业务开发中得到技术提升,又能提高效率,看似复杂的功能,其实我们早就做成组件了,页面直接引入使用,几行代码就能搞定。