查询表格业务是中后台系统最经常使用的业务系统之一,我相信该业务场景会在你的项目中会大量出现。既然该此场景在项目中大量的出现,因此对其进行必要的封装会极大的提高业务的复用性以及项目的可维护性。如下是不采起封装可能会带来的问题。git
以上的几点总结起来就是不利于项目的维护和造成规范。github
该业务场景如此常见,全部相信你们都有本身的实现。因此这里仅仅是提出一个设计思路,你能够用来参考案而后考虑是否对你的项目有帮助。设计图以下;redux
这里会在 HOC 中绑定到 Store小程序
const TableHoc = config => (WrappedComponent) => {
const {
store, // 绑定 store
className,
NoPager, // 是否须要外置翻页器
noNeedReloadPathname = [], // 不须要从新加载数据的返回页面
dealFormatData = data => data, // 清理列表数据方法
} = config || {};
@inject(store)
@observer
class BaseTable extends Component {
static defaultProps = {
fixClass: 'baseTable-wrapper',
};
static propTypes = {
fixClass: PropTypes.string,
className: PropTypes.string,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
};
componentDidMount() {
const {
match: { params: { id } = {} },
location: { pathname },
} = this.props;
/* eslint-disable */
const {
tableData: { count, needReload },
} = this.props[store];
const preLocation = window.RouterPathname.find((item) => item !== pathname); // [preLocation, curLocation]
const noNeedReloadTag = !preLocation
? false
: noNeedReloadPathname.some((item) => {
return preLocation.startsWith(item);
});
// 数据没有更新使用缓存数据
if (count !== 0 && !needReload && noNeedReloadTag) {
return null;
}
if (id) {
// 若是根据路由获取 id 则拿 id 进行调用
this.props[store].getData({ id });
} else {
this.props[store].getData();
}
return null;
}
/**
* 顶部搜索 接口
* 具体实如今 store 中
*/
handleSearch = (values) => {
this.props[store].handleSearch(values); // eslint-disable-line
};
/**
* 重置搜索 接口
* 具体实如今 store 中
*/
handleResetSearch = () => {
this.props[store].handleResetSearch(); // eslint-disable-line
};
/**
* 翻页 接口
* 具体实如今 store 中
*/
handlePageChange = (page) => {
this.props[store].handlePageChange(page); // eslint-disable-line
};
/**
* 改变pageSize 接口
* 具体实如今 store 中
*/
handlePageSizeChange = (page, pageSize) => {
this.props[store].handlePageSizeChange(page, pageSize); // eslint-disable-line
};
/**
* 排序 接口
* 具体实如今 store 中
*/
handleSort = (data) => {
this.props[store].handleSort(data); // eslint-disable-line
};
render() {
const { fixClass } = this.props;
// 传递 Store, 让页面可以调用 Store 中的自定义方法
const Store = this.props[store]; // eslint-disable-line
const { tableData: data } = Store;
const tableData = toJS(data);
const classes = classnames(fixClass, { [className]: className });
const { loading, count, listItems, pageNo, pageSize, query } = tableData;
const formatData = dealFormatData(listItems);
return (
<div className={classes}>
<WrappedComponent
loading={loading}
query={query}
tableData={formatData}
handleSort={this.handleSort}
handleSearch={this.handleSearch}
handleResetSearch={this.handleResetSearch}
store={Store}
{...this.props}
/>
{NoPager ? null : (
<div className="pagWrapper">
<Pagination
showQuickJumper
showSizeChanger
showTotal={() => `共 ${count} 条`}
onChange={this.handlePageChange}
onShowSizeChange={this.handlePageSizeChange}
current={pageNo}
total={count}
pageSize={pageSize}
/>
</div>
)}
</div>
);
}
}
return BaseTable;
};
复制代码
经过高阶组件属性代理:统一项目对于此类场景的具体调用方法。后端
经过传入 hoc 一些用户自定义处理方法api
例如:缓存
⚠️ 本文是基于mobx
进行数据流管理。redux管理的是纯JavaScript对象,应该更容易实现公共模型的抽离。bash
class TableModel {
constructor({ pageSize = 10 } = {}) {
this.tableData = {
loading: false, // 加载数据状态
count: 0, // 数据条目
pageNo: 1, // 当前页码
pageSize, // 单页数据条目
listItems: [], // 数据条目 id 集合
byId: {}, // 数据条目的映射
query: {}, // 其余请求参数对象
errorMessage: undefined, // 错误信息
needReload: false, 数据是否须要从新加载,用于数据缓存优化
};
}
// 获取请求参数
getParams(data) {
return {
pageNo: this.pageNo,
pageSize: this.pageSize,
...this.query,
...data,
};
}
}
复制代码
该模型是比较好的实践,具备广泛通用性;app
class Table {
@observable
tableData;
/**
* more observable to add
*/
constructor(Model) {
this.tableModel = new Model(); // 以前定义的模型
this.tableData = this.tableModel.tableData;
}
@action
handleSearch(values) {
const params = Object.assign(values, { pageNo: 1 });
this.getData(this.tableModel.getParams(params));
}
@action
handleResetSearch() {
this.getData({
pageNo: 1,
grade: undefined,
name: undefined,
startTime: undefined,
endTime: undefined,
});
}
@action
handlePageChange(pageNo) {
this.getData(this.tableModel.getParams({ pageNo }));
}
@action
handlePageSizeChange(pageNo, pageSize) {
this.getData(this.tableModel.getParams({ pageNo, pageSize }));
}
@action
getData({
name = undefined,
grade = undefined,
pageNo = 1,
pageSize = 10,
startTime = undefined,
endTime = undefined,
} = {}) {
this.tableData.loading = true;
api
.initTableData({
params: {
name,
grade,
pageNo,
itemsPerPage: pageSize,
startTime,
endTime,
},
})
.then((resp) => {
const { count, items: listItems } = resp;
const byId = listItems.map(item => item.id);
this.tableData = {
loading: false,
pageNo: pageNo || this.tableData.pageNo,
pageSize: pageSize || this.tableData.pageSize,
count,
listItems,
byId,
errorMessage: undefined,
needReload: false,
query: {
grade,
name,
startTime,
endTime,
},
};
});
}
/**
* more action to add
*/
}
复制代码
这里的页面组件固然是做为一个容器组件,内部一般包含;函数
组件开发的一种思想
,展现性组件对于同一调用一般会有不一样实现。基于下降组件的耦合度,一般只会定义调用接口具体实现由外部实现。
这里的页面组件会实现除公共业务之外的全部实现,同时也能够拓展其余store不调用定义好的业务。
若是你自定义了列表,而且内部没有封装翻页器,就是用外部翻页器。
// 可使用缓存数据的返回页面
const noNeedReloadPathname = ['/form/baseForm', '/detail/baseDetail/'];
// dealFormatData -> 清理列表数据方法
@TableHoc({ store: 'TableStore', dealFormatData, noNeedReloadPathname })
class SearchTable extends Component {
static defaultProps = {
titleValue: ['本次推广专属小程序二维码', '本次推广专属小程序连接'],
};
static propTypes = {
loading: PropTypes.bool,
tableData: PropTypes.array, // 表格数据
query: PropTypes.object, // 表单查询信息
titleValue: PropTypes.array, // 弹窗提示
store: PropTypes.object, // @TableHoc 高阶组件中绑定的 mobx store 对象
routerData: PropTypes.object.isRequired, // 路由数据
history: PropTypes.object.isRequired, // router history
handleSearch: PropTypes.func.isRequired, // @TableHoc 表单搜索接口
handleResetSearch: PropTypes.func.isRequired, // @TableHoc 表单重置接口
};
constructor(props) {
super(props);
this.state = {
visibleModal: false,
record: {},
};
}
get columns() {
return [
{
title: '建立时间',
dataIndex: 'createdAt',
key: 'createdAt',
},
{
title: '地区',
dataIndex: 'address',
key: 'address',
},
{
title: '学校',
dataIndex: 'school',
key: 'school',
},
{
title: '年级',
dataIndex: 'grade',
key: 'grade',
},
{
title: '班级',
dataIndex: 'className',
key: 'className',
},
{
title: '用户数',
dataIndex: 'registerNumber',
key: 'registerNumber',
},
{
title: '订单金额',
dataIndex: 'totalPayMoney',
key: 'totalPayMoney',
},
{
title: '个人收益',
dataIndex: 'totalShare',
key: 'totalShare',
},
{
title: '操做',
dataIndex: 'action',
key: 'action',
width: 155,
render: (text, record) => {
const shareStyle = {
width: 70,
color: '#1574D4',
marginRight: 5,
cursor: 'pointer',
};
const detailStyle = {
width: 70,
color: '#1574D4',
marginLeft: 5,
cursor: 'pointer',
};
return (
<div className="operations-orgGo">
<span style={shareStyle} onClick={() => this.handleOpenShareModal(record)}>
当即分享
</span>
<span style={detailStyle} onClick={() => this.redirectToDetail(record)}>
查看详情
</span>
</div>
);
},
},
];
}
redirectToCreatePromotion = () => {
const {
history: { push },
} = this.props;
push({ pathname: '/form/baseForm' });
};
redirectToDetail = (record) => {
const {
history: { push },
} = this.props;
push({ pathname: `/detail/baseDetail/${record.id}` });
};
handleOpenShareModal = (record) => {
this.setState({
visibleModal: true,
record,
});
const { store } = this.props;
store.getWeiCode({ promotionId: record.id, record });
};
handleCloseShareModal = () => {
const { store } = this.props;
this.setState(
{
visibleModal: false,
record: {},
},
() => store.delWeiCode(),
);
};
handleReset = () => {
const { handleResetSearch } = this.props;
handleResetSearch();
};
handleSearch = (value) => {
const { timeLimit = [undefined, undefined], grade } = value;
let { queryCond: name } = value;
const startTime = timeLimit[0] && timeLimit[0].format('YYYY-MM-DD HH:mm:ss');
const endTime = timeLimit[1] && timeLimit[1].format('YYYY-MM-DD HH:mm:ss');
name = name ? name.replace(/^(\s|\u00A0)+/, '').replace(/(\s|\u00A0)+$/, '') : undefined;
const { handleSearch } = this.props;
handleSearch({
startTime,
endTime,
name,
grade: grade || undefined,
});
};
render() {
const { visibleModal, record } = this.state;
const {
routerData: { config },
titleValue,
loading,
tableData,
query,
} = this.props;
return (
<WithBreadcrumb config={config}>
<Helmet>
<title>查询表格 - SPA</title>
<meta name="description" content="SPA" />
</Helmet>
<div className="table-search-wrapper">
<ModuleLine title="查询表格">
<Button
size="middle"
type="primary"
className="promotionBtn"
onClick={this.redirectToCreatePromotion}
>
新增
</Button>
</ModuleLine>
<SearchForm
handleReset={this.handleReset}
onSubmit={this.handleSearch}
initialValue={query}
/>
</div>
<Table
bordered
className="self-table-wrapper"
loading={loading}
dataSource={tableData}
pagination={false}
columns={this.columns}
/>
<ShareModal
key="base-table-modal"
width={600}
record={record}
showTitle={false}
titleDownImg="保存"
recordType="string"
visible={visibleModal}
titleValue={titleValue}
handleClose={this.handleCloseShareModal}
/>
</WithBreadcrumb>
);
}
}
复制代码
总结一下,这里管理查询列表的全部抽象和模块功能:
clone项目,查看项目的 表格页 -> 查询表格