CRUD 添加 loadDataOnce 属性, 用来实现前端分页 排序功能

This commit is contained in:
liaoxuezhi 2019-05-13 14:14:04 +08:00
parent 1b28a905c1
commit 63f3f8aa91
8 changed files with 288 additions and 16 deletions

View File

@ -49,6 +49,7 @@ import KeyboardsCrudSchema from './CRUD/Keyboards';
import FootableCrudSchema from './CRUD/Footable';
import NestedCrudSchema from './CRUD/Nested';
import MergeCellSchema from './CRUD/MergeCell';
import LoadOnceTableCrudSchema from './CRUD/LoadOnce';
import SdkTest from './Sdk/Test';
import JSONSchemaForm from './Form/Schem';
import SimpleDialogSchema from './Dialog/Simple';
@ -315,6 +316,11 @@ const navigations = [
path: 'crud/jump-next',
component: makeSchemaRenderer(JumpNextCrudSchema)
},
{
label: '一次性加载',
path: 'crud/load-once',
component: makeSchemaRenderer(LoadOnceTableCrudSchema)
},
{
label: '测试',
path: 'crud/test',

View File

@ -0,0 +1,202 @@
export default {
$schema: "https://houtai.baidu.com/v2/schemas/page.json#",
title: "一次性加载,前端分页,前端排序",
body: {
type: "crud",
loadDataOnce: true,
api: "/api/sample?waitSeconds=1",
filter: {
title: "条件搜索",
submitText: "",
controls: [
{
type: "text",
name: "keywords",
placeholder: "通过关键字搜索",
addOn: {
label: "搜索",
type: "submit"
}
}
]
},
columns: [
{
name: "id",
label: "ID",
width: 20,
sortable: true,
type: "text",
toggled: true,
remark: 'Bla bla Bla'
},
{
name: "engine",
label: "Rendering engine",
sortable: true,
type: "text",
toggled: true
},
{
name: "browser",
label: "Browser",
sortable: true,
type: "text",
toggled: false
},
{
name: "platform",
label: "Platform(s)",
sortable: true,
type: "text",
toggled: true
},
{
name: "version",
label: "Engine version",
type: "text",
toggled: true
},
{
name: "grade",
label: "CSS grade",
type: "text",
toggled: true
},
{
type: "operation",
label: "操作",
width: 100,
buttons: [
{
type: "button",
icon: "fa fa-eye",
actionType: "dialog",
tooltip: "查看",
dialog: {
title: "查看",
body: {
type: "form",
controls: [
{
type: "static",
name: "engine",
label: "Engine"
},
{
type: "divider"
},
{
type: "static",
name: "browser",
label: "Browser"
},
{
type: "divider"
},
{
type: "static",
name: "platform",
label: "Platform(s)"
},
{
type: "divider"
},
{
type: "static",
name: "version",
label: "Engine version"
},
{
type: "divider"
},
{
type: "static",
name: "grade",
label: "CSS grade"
},
{
type: "divider"
},
{
type: "html",
html:
"<p>添加其他 <span>Html 片段</span> 需要支持变量替换todo.</p>"
}
]
}
}
},
{
type: "button",
icon: "fa fa-pencil",
tooltip: "编辑",
actionType: "drawer",
drawer: {
position: 'left',
size: 'lg',
title: "编辑",
body: {
type: "form",
name: "sample-edit-form",
api: "/api/sample/$id",
controls: [
{
type: "text",
name: "engine",
label: "Engine",
required: true
},
{
type: "divider"
},
{
type: "text",
name: "browser",
label: "Browser",
required: true
},
{
type: "divider"
},
{
type: "text",
name: "platform",
label: "Platform(s)",
required: true
},
{
type: "divider"
},
{
type: "text",
name: "version",
label: "Engine version"
},
{
type: "divider"
},
{
type: "select",
name: "grade",
label: "CSS grade",
options: ["A", "B", "C", "D", "X"],
}
]
}
}
},
{
type: "button",
icon: "fa fa-times text-danger",
actionType: "ajax",
tooltip: "删除",
confirmText: "您确认要删除?",
api: "delete:/api/sample/$id"
}
],
toggled: true
}
]
}
};

View File

@ -1,5 +1,5 @@
export default {
$schema: "https://houtai.baidu.com/v2/schemas/page.json#",
$schema: "http://amis.baidu.com/v2/schemas/page.json#",
title: "增删改查示例",
remark: "bla bla bla",
toolbar: [

View File

@ -298,7 +298,7 @@ module.exports = [
"browser": "Nintendo DS browser",
"platform": "Nintendo DS",
"version": "8.5",
"grade": "C/A<sup>1</sup>"
"grade": "C"
},
{
"engine": "KHTML",

View File

@ -22,7 +22,7 @@ module.exports = function(req, res) {
function index(req, res) {
const perPage = parseInt(req.query.perPage, 10) || 10;
const perPage = parseInt(req.query.perPage, 10);
const page = req.query.page || 1;
let items = DB.concat();
@ -62,7 +62,7 @@ function index(req, res) {
msg: 'ok',
data: {
count: items.length,
rows: items.splice((page -1) * perPage, perPage)
rows: perPage ? items.splice((page -1) * perPage, perPage) : items.concat()
}
});
}

View File

@ -65,6 +65,7 @@ interface CRUDProps extends RendererProps {
filterDefaultVisible?: boolean;
syncResponse2Query?: boolean;
keepItemSelectionOnPageChange?: boolean;
loadDataOnce: boolean;
}
export default class CRUD extends React.Component<CRUDProps, any> {
@ -108,6 +109,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'keepItemSelectionOnPageChange',
'labelTpl',
'labelField',
'loadDataOnce'
];
static defaultProps: Partial<CRUDProps> = {
toolbarInline: true,
@ -122,12 +124,13 @@ export default class CRUD extends React.Component<CRUDProps, any> {
silentPolling: false,
filterTogglable: false,
filterDefaultVisible: true,
loadDataOnce: false,
};
control: any;
lastQuery: any;
dataInvalid: boolean = false;
timer: NodeJS.Timer;
timer: number;
mounted: boolean;
constructor(props: CRUDProps) {
super(props);
@ -156,9 +159,10 @@ export default class CRUD extends React.Component<CRUDProps, any> {
}
componentWillMount() {
const {location, store, pageField, perPageField, syncLocation} = this.props;
const {location, store, pageField, perPageField, syncLocation, loadDataOnce} = this.props;
this.mounted = true;
store.setLoadDataOnce(loadDataOnce);
if (syncLocation && location && (location.query || location.search)) {
store.updateQuery(
@ -205,6 +209,10 @@ export default class CRUD extends React.Component<CRUDProps, any> {
store.setFilterTogglable(!!nextProps.filterTogglable, nextProps.filterDefaultVisible);
}
if (this.props.loadDataOnce !== nextProps.loadDataOnce) {
store.setLoadDataOnce(!!nextProps.loadDataOnce);
}
if (props.syncLocation && props.location && props.location.search !== nextProps.location.search) {
// 同步地址栏,那么直接检测 query 是否变了,变了就重新拉数据
store.updateQuery(
@ -514,7 +522,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
});
}
search(values?: any, silent?: boolean, clearSelection?: boolean) {
search(values?: any, silent?: boolean, clearSelection?: boolean, forceReload = true) {
const {
store,
api,
@ -561,6 +569,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
successMessage: messages && messages.fetchSuccess,
errorMessage: messages && messages.fetchFailed,
autoAppend: true,
forceReload,
silent,
pageField,
perPageField,
@ -604,7 +613,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
pageField,
perPageField
);
this.search();
this.search(undefined, undefined, undefined, false);
if (autoJumpToTopOnPagerChange && this.control) {
(findDOMNode(this.control) as HTMLElement).scrollIntoView();
@ -819,7 +828,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
pageField,
perPageField
);
this.search();
this.search(undefined, undefined, undefined, false);
}
reload(subpath?: string, query?: any) {

View File

@ -9,7 +9,7 @@ import {
IRendererStore
} from './index';
import { ServiceStore } from './service';
import { extendObject, createObject, isObjectShallowModified } from '../utils/helper';
import { extendObject, createObject, isObjectShallowModified, sortArray } from '../utils/helper';
import {
Api,
Payload,
@ -32,7 +32,8 @@ export const CRUDStore = ServiceStore
prevPage: 1,
page: 1,
perPage: 10,
total: 1,
total: 0,
loadDataOnce: false, // 配置数据是否一次性加载,如果是这样,由前端来完成分页,排序等功能。
mode: 'normal',
hasNext: false,
selectedAction: types.frozen(),
@ -45,7 +46,7 @@ export const CRUDStore = ServiceStore
})
.views(self => ({
get lastPage() {
return Math.ceil(self.total / (self.perPage < 1 ? 10 : self.perPage));
return Math.max(Math.ceil(self.total / (self.perPage < 1 ? 10 : self.perPage)), 1);
},
get filterData() {
@ -100,10 +101,27 @@ export const CRUDStore = ServiceStore
const fetchInitData:(api:Api, data?:object, options?:fetchOptions & {
forceReload?: boolean;
loadDataMode?: boolean;
syncResponse2Query?: boolean;
}) => Promise<any> = flow(function *getInitData(api:string, data:object, options?:fetchOptions) {
try {
if (options && options.forceReload === false && self.loadDataOnce && self.total) {
let items = self.items.concat();
if (self.query.orderBy) {
const dir = /desc/i.test(self.query.orderDir) ? -1 : 1;
items = sortArray(items, self.query.orderBy, dir);
}
const data = {
...self.data,
items: items.slice((self.page - 1) * self.perPage, self.page * self.perPage)
}
self.reInitData(data);
return;
}
if (fetchCancel) {
fetchCancel();
fetchCancel = null;
@ -111,12 +129,19 @@ export const CRUDStore = ServiceStore
}
options && options.silent || self.markFetching(true);
const json:Payload = yield (getRoot(self) as IRendererStore).fetcher(api, createObject(self.data, {
const ctx:any = createObject(self.data, {
...self.query,
[options && options.pageField || 'page']: self.page,
[options && options.perPageField || 'perPage']: self.perPage,
...data
}), {
});
// 一次性加载不要发送 perPage 属性
if (self.loadDataOnce) {
delete ctx[options && options.perPageField || 'perPage'];
}
const json:Payload = yield (getRoot(self) as IRendererStore).fetcher(api, ctx, {
...options,
cancelExecutor: (executor:Function) => fetchCancel = executor
});
@ -174,12 +199,21 @@ export const CRUDStore = ServiceStore
...rest
};
if (self.loadDataOnce) {
if (self.query.orderBy) {
const dir = /desc/i.test(self.query.orderDir) ? -1 : 1;
rowsData = sortArray(rowsData, self.query.orderBy, dir);
}
data.items = rowsData.slice((self.page - 1) * self.perPage, self.page * self.perPage);
data.count = data.total = self.total = rowsData.length;
}
self.items.replace(rowsData);
self.reInitData(data);
options && options.syncResponse2Query !== false && updateQuery(pick(rest, Object.keys(self.query)), undefined, options && options.pageField || 'page', options && options.perPageField || 'perPage');
self.total = parseInt(total || count, 10) || 0;
typeof page !== 'undefined' && (self.page = self.pageNum = parseInt(page, 10));
typeof page !== 'undefined' && (self.page = parseInt(page, 10));
// 分页情况不清楚,只能知道有没有下一页。
if (typeof hasNext !== 'undefined') {
@ -279,6 +313,10 @@ export const CRUDStore = ServiceStore
self.hasInnerModalOpen = value;
}
const setLoadDataOnce = function(value:boolean) {
self.loadDataOnce = value;
}
return {
setPristineQuery,
updateQuery,
@ -290,7 +328,8 @@ export const CRUDStore = ServiceStore
setFilterVisible,
setSelectedItems,
setUnSelectedItems,
setInnerModalOpened
setInnerModalOpened,
setLoadDataOnce
};
});

View File

@ -617,4 +617,20 @@ export const bulkBindFunctions = function<T extends {
[propName:string]: any
}>(context:T, funNames:Array<FunctionPropertyNames<T>>) {
funNames.forEach(key => context[key] = context[key].bind(context));
}
export function sortArray<T extends any>(items:Array<T>, field:string, dir: -1 | 1):Array<T> {
return items.sort((a, b) => {
let ret: number;
const a1 = a[field];
const b1 = b[field];
if (typeof a1 === 'number' && typeof b1 === 'number') {
ret = a1 < b1 ? -1 : a1 === b1 ? 0 : 1;
} else {
ret = String(a1).localeCompare(String(b1));
}
return ret * dir;
})
}