查詢表格業務是中後臺系統最經常使用的業務系統之一,我相信該業務場景會在你的項目中會大量出現。既然該此場景在項目中大量的出現,因此對其進行必要的封裝會極大的提高業務的複用性以及項目的可維護性。如下是不採起封裝可能會帶來的問題。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項目,查看項目的 表格頁 -> 查詢表格