From 63f3f8aa91568c4cd7b37d86830e53349194ade0 Mon Sep 17 00:00:00 2001 From: liaoxuezhi Date: Mon, 13 May 2019 14:14:04 +0800 Subject: [PATCH] =?UTF-8?q?CRUD=20=E6=B7=BB=E5=8A=A0=20loadDataOnce=20?= =?UTF-8?q?=E5=B1=9E=E6=80=A7,=20=E7=94=A8=E6=9D=A5=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=88=86=E9=A1=B5=20=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/components/App.jsx | 6 + examples/components/CRUD/LoadOnce.jsx | 202 ++++++++++++++++++++++++++ examples/components/CRUD/Table.jsx | 2 +- mock/sample.db.js | 2 +- mock/sample.js | 4 +- src/renderers/CRUD.tsx | 19 ++- src/store/crud.ts | 53 ++++++- src/utils/helper.ts | 16 ++ 8 files changed, 288 insertions(+), 16 deletions(-) create mode 100644 examples/components/CRUD/LoadOnce.jsx diff --git a/examples/components/App.jsx b/examples/components/App.jsx index 148751d3..6504d177 100644 --- a/examples/components/App.jsx +++ b/examples/components/App.jsx @@ -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', diff --git a/examples/components/CRUD/LoadOnce.jsx b/examples/components/CRUD/LoadOnce.jsx new file mode 100644 index 00000000..5e84bdf7 --- /dev/null +++ b/examples/components/CRUD/LoadOnce.jsx @@ -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: + "

添加其他 Html 片段 需要支持变量替换(todo).

" + } + ] + } + } + }, + { + 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 + } + ] + } +}; diff --git a/examples/components/CRUD/Table.jsx b/examples/components/CRUD/Table.jsx index a3015e4a..bec7b059 100644 --- a/examples/components/CRUD/Table.jsx +++ b/examples/components/CRUD/Table.jsx @@ -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: [ diff --git a/mock/sample.db.js b/mock/sample.db.js index 55dbdd8c..9d15698a 100644 --- a/mock/sample.db.js +++ b/mock/sample.db.js @@ -298,7 +298,7 @@ module.exports = [ "browser": "Nintendo DS browser", "platform": "Nintendo DS", "version": "8.5", - "grade": "C/A1" + "grade": "C" }, { "engine": "KHTML", diff --git a/mock/sample.js b/mock/sample.js index 6d06179f..1367a91d 100644 --- a/mock/sample.js +++ b/mock/sample.js @@ -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() } }); } diff --git a/src/renderers/CRUD.tsx b/src/renderers/CRUD.tsx index 7be3dc47..9e08d07d 100644 --- a/src/renderers/CRUD.tsx +++ b/src/renderers/CRUD.tsx @@ -65,6 +65,7 @@ interface CRUDProps extends RendererProps { filterDefaultVisible?: boolean; syncResponse2Query?: boolean; keepItemSelectionOnPageChange?: boolean; + loadDataOnce: boolean; } export default class CRUD extends React.Component { @@ -108,6 +109,7 @@ export default class CRUD extends React.Component { 'keepItemSelectionOnPageChange', 'labelTpl', 'labelField', + 'loadDataOnce' ]; static defaultProps: Partial = { toolbarInline: true, @@ -122,12 +124,13 @@ export default class CRUD extends React.Component { 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 { } 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 { 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 { }); } - 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 { 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 { 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 { pageField, perPageField ); - this.search(); + this.search(undefined, undefined, undefined, false); } reload(subpath?: string, query?: any) { diff --git a/src/store/crud.ts b/src/store/crud.ts index c365ecea..dccafd31 100644 --- a/src/store/crud.ts +++ b/src/store/crud.ts @@ -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 = 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 }; }); diff --git a/src/utils/helper.ts b/src/utils/helper.ts index cc50c4fd..42800a56 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -617,4 +617,20 @@ export const bulkBindFunctions = function(context:T, funNames:Array>) { funNames.forEach(key => context[key] = context[key].bind(context)); +} + +export function sortArray(items:Array, field:string, dir: -1 | 1):Array { + 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; + }) } \ No newline at end of file