From b3256870c6210c2f8fb1a778b285803ef3b404f7 Mon Sep 17 00:00:00 2001 From: RUNZE LU <36724300+lurunze1226@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:42:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20CRUD=E6=94=AF=E6=8C=81matchFunc?= =?UTF-8?q?=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=89=8D=E7=AB=AF=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E7=9A=84=E6=90=9C=E7=B4=A2=E5=8C=B9=E9=85=8D=E5=87=BD=E6=95=B0?= =?UTF-8?q?=20(#8556)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: CRUD支持matchFunc,用于前端分页的搜索匹配函数 * patch(feat: CRUD支持matchFunc): 搜索函数执行时机修改,支持source为上下文数据 --- docs/zh-CN/components/crud.md | 33 +- examples/components/CRUD/MatchFunc.jsx | 298 +++++++++++++ examples/components/Example.jsx | 18 +- mock/cfc/mock/crud/loadDataOnce.js | 552 +++++++++++++++++++++++++ packages/amis-core/src/store/crud.ts | 164 +++++--- packages/amis/src/renderers/CRUD.tsx | 24 +- 6 files changed, 1017 insertions(+), 72 deletions(-) create mode 100644 examples/components/CRUD/MatchFunc.jsx create mode 100644 mock/cfc/mock/crud/loadDataOnce.js diff --git a/docs/zh-CN/components/crud.md b/docs/zh-CN/components/crud.md index b6ab5a436ba..0069a64c41b 100755 --- a/docs/zh-CN/components/crud.md +++ b/docs/zh-CN/components/crud.md @@ -3085,6 +3085,33 @@ CRUD 中不限制有多少个单条操作、添加一个操作对应的添加一 } ``` +### 匹配函数 + +> `3.5.0` 及以上版本 + +支持自定义匹配函数`matchFunc`,当开启`loadDataOnce`时,会基于该函数计算的匹配结果进行过滤,主要用于处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景,函数签名如下: + +```typescript +interface CRUDMatchFunc { + ( + /* 当前列表的全量数据 */ + items: any, + /* 最近一次接口返回的全量数据 */ + itemsRaw: any, + /** 相关配置 */ + options?: { + /* 查询参数 */ + query: Record, + /* 列配置 */ + columns: any; + } + ): boolean; +} +``` + +具体效果请参考[示例](../../../examples/crud/match-func)。 + + ## 动态列 > since 1.1.6 @@ -3220,8 +3247,8 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据, ## 属性表 -| 属性名 | 类型 | 默认值 | 说明 | -| ------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| 属性名 | 类型 | 默认值 | 说明 | 版本 | +| ------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --- | | type | `string` | | `type` 指定为 CRUD 渲染器 | | mode | `string` | `"table"` | `"table" 、 "cards" 或者 "list"` | | title | `string` | `""` | 可设置成空,当设置成空时,没有标题栏 | @@ -3276,6 +3303,8 @@ itemAction 里的 onClick 还能通过 `data` 参数拿到当前行的数据, | resetPageAfterAjaxItemAction | `boolean` | `false` | 单条数据 ajax 操作后是否重置页码为第一页 | | autoFillHeight | `boolean` 丨 `{height: number}` | | 内容区域自适应高度 | | canAccessSuperData | `boolean` | `true` | 指定是否可以自动获取上层的数据并映射到表格行数据上,如果列也配置了该属性,则列的优先级更高 | +| matchFunc | `string` | [`CRUDMatchFunc`](#匹配函数) | 自定义匹配函数, 当开启`loadDataOnce`时,会基于该函数计算的匹配结果进行过滤,主要用于处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景 | `3.5.0` | + 注意除了上面这些属性,CRUD 在不同模式下的属性需要参考各自的文档,比如 diff --git a/examples/components/CRUD/MatchFunc.jsx b/examples/components/CRUD/MatchFunc.jsx new file mode 100644 index 00000000000..279cfcc4aa2 --- /dev/null +++ b/examples/components/CRUD/MatchFunc.jsx @@ -0,0 +1,298 @@ +export default { + type: 'page', + title: '匹配函数', + remark: '使用前端分页处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景', + body: [ + { + type: 'container', + style: { + padding: '8px', + marginBottom: '8px', + backgroundColor: '#f5f5f5', + borderRadius: '4px' + }, + body: [ + { + "type": "tpl", + tpl: '

匹配函数签名:

', + "inline": false + }, + { + "type": "code", + "language": "typescript", + "value": `interface CRUDMatchFunc { + ( + /* 当前列表的全量数据 */ + items: any, + /* 最近一次接口返回的全量数据 */ + itemsRaw: any, + /** 相关配置 */ + options?: { + /* 查询参数 */ + query: Record, + /* 列配置 */ + columns: any; + } + ): boolean; +}` + } + ] + }, + { + "type": "crud", + "syncLocation": false, + "api": "/api/mock2/crud/loadDataOnce", + "loadDataOnce": true, + "loadDataOnceFetchOnFilter": false, + "perPage": 5, + "matchFunc": ` + const {query = {}, columns} = options; + let result = items.concat(); + + Object.keys(query).forEach(key => { + const value = query[key]; + + if (value == null) { + return; + } + + if (key === 'status') { + result = result.filter(item => item.status === Boolean(value)); + } else if (key === 'time') { + if (typeof value === 'string') { + const [start, end] = value.split(","); + result = result.filter(item => { + const time = Number(item.time); + + return time >= Number(start) && time <= Number(end); + }); + } + } + }); + + return result;`, + "filter": { + "debug": true, + "body": [ + { + "type": "switch", + "name": "status", + "label": "已核验", + "size": "sm" + }, + { + "type": "input-datetime-range", + "name": "time", + "label": "时间", + "size": "full" + } + ], + "actions": [ + { + "type": "reset", + "label": "重置" + }, + { + "type": "submit", + "level": "primary", + "label": "查询" + } + ] + }, + "columns": [ + { + "name": "id", + "label": "ID" + }, + { + "name": "browser", + "label": "Browser" + }, + { + "name": "version", + "label": "Engine version", + "searchable": { + "type": "select", + "name": "version", + "label": "Engine version", + "clearable": true, + "multiple": true, + "searchable": true, + "checkAll": true, + "options": [ + "1.7", + "3.3", + "5.6" + ], + "maxTagCount": 10, + "extractValue": true, + "joinValues": false, + "delimiter": ",", + "defaultCheckAll": false, + "checkAllLabel": "全选" + } + }, + { + "name": "grade", + "label": "CSS grade" + }, + { + "name": "status", + "label": "已核验", + "type": "tpl", + "tpl": "${status === true ? '是' : '否'}", + "filterable": { + "options": [ + {"label": "是", "value": true}, + {"label": "否", "value": false} + ] + } + }, + { + "name": "time", + "type": "date", + "label": "时间", + "format": "YYYY-MM-DD HH:mm:ss" + } + ] + }, + { + "type": "divider", + }, + { + "type": "tpl", + tpl: '

使用数据域变量作为数据源:

', + "inline": false + }, + { + "type": "service", + "api": { + "url": "/api/mock2/crud/loadDataOnce", + "method": "get", + "responseData": { + "table": "${rows}" + } + }, + "body": [ + { + "type": "crud", + "syncLocation": false, + "source": "${table}", + "loadDataOnce": true, + "loadDataOnceFetchOnFilter": false, + "perPage": 5, + "matchFunc": ` + const {query = {}, columns} = options; + let result = itemsRaw.concat(); + + Object.keys(query).forEach(key => { + const value = query[key]; + + if (value == null) { + return; + } + + if (key === 'status') { + result = result.filter(item => item.status === Boolean(value)); + } else if (key === 'time') { + if (typeof value === 'string') { + const [start, end] = value.split(","); + result = result.filter(item => { + const time = Number(item.time); + + return time >= Number(start) && time <= Number(end); + }); + } + } + }); + + return result;`, + "filter": { + "debug": true, + "body": [ + { + "type": "switch", + "name": "status", + "label": "已核验", + "size": "sm" + }, + { + "type": "input-datetime-range", + "name": "time", + "label": "时间", + "size": "full" + } + ], + "actions": [ + { + "type": "reset", + "label": "重置" + }, + { + "type": "submit", + "level": "primary", + "label": "查询" + } + ] + }, + "columns": [ + { + "name": "id", + "label": "ID" + }, + { + "name": "browser", + "label": "Browser" + }, + { + "name": "version", + "label": "Engine version", + "searchable": { + "type": "select", + "name": "version", + "label": "Engine version", + "clearable": true, + "multiple": true, + "searchable": true, + "checkAll": true, + "options": [ + "1.7", + "3.3", + "5.6" + ], + "maxTagCount": 10, + "extractValue": true, + "joinValues": false, + "delimiter": ",", + "defaultCheckAll": false, + "checkAllLabel": "全选" + } + }, + { + "name": "grade", + "label": "CSS grade" + }, + { + "name": "status", + "label": "已核验", + "type": "tpl", + "tpl": "${status === true ? '是' : '否'}", + "filterable": { + "options": [ + {"label": "是", "value": true}, + {"label": "否", "value": false} + ] + } + }, + { + "name": "time", + "type": "date", + "label": "时间", + "format": "YYYY-MM-DD HH:mm:ss" + } + ] + } + ] + } + ] +}; diff --git a/examples/components/Example.jsx b/examples/components/Example.jsx index 45977d5a208..fa19ec4c42c 100644 --- a/examples/components/Example.jsx +++ b/examples/components/Example.jsx @@ -58,6 +58,7 @@ import ExportCSVExcelSchema from './CRUD/ExportCSVExcel'; import CRUDDynamicSchema from './CRUD/Dynamic'; import CRUDSimplePagerSchema from './CRUD/SimplePager'; import CRUDParsePrimitiveQuerySchema from './CRUD/ParsePrimitiveQuery'; +import CRUDMatchFuncSchema from './CRUD/MatchFunc'; import ItemActionchema from './CRUD/ItemAction'; import SdkTest from './Sdk/Test'; import JSONSchemaForm from './Form/Schem'; @@ -438,9 +439,20 @@ export const examples = [ component: makeSchemaRenderer(PopOverCrudSchema) }, { - label: '一次性加载', - path: '/examples/crud/load-once', - component: makeSchemaRenderer(LoadOnceTableCrudSchema) + label: '前端分页', + icon: 'fa fa-list-ol', + children: [ + { + label: '一次性加载', + path: '/examples/crud/load-once', + component: makeSchemaRenderer(LoadOnceTableCrudSchema) + }, + { + label: '匹配函数', + path: '/examples/crud/match-func', + component: makeSchemaRenderer(CRUDMatchFuncSchema) + } + ] }, { label: '点击联动', diff --git a/mock/cfc/mock/crud/loadDataOnce.js b/mock/cfc/mock/crud/loadDataOnce.js new file mode 100644 index 00000000000..3b3c4dcda7c --- /dev/null +++ b/mock/cfc/mock/crud/loadDataOnce.js @@ -0,0 +1,552 @@ +/** 前端分页的接口 */ +module.exports = function (req, res) { + const perPage = 10; + const page = req.query.page || 1; + let items = data.concat(); + + const validQueryKey = Object.keys(req.query).filter( + item => item !== 'keywords' && req.query[item] != null + ); + + if (validQueryKey.length > 0) { + items = items.filter(item => + validQueryKey.every(key => { + if (key === 'status') { + return item[key] === (req.query[key] === 'true' ? true : false); + } + + if (key === 'time') { + const [start, end] = req.query[key]; + + return Number(itme[key]) >= Number(start) && Number(itme[key]) <= Number(end); + } + + return !!~req.query[key].indexOf(item[key] || ''); + }) + ); + } + + const ret = { + status: 0, + msg: 'ok', + data: { + count: items.length, + rows: items + } + }; + + res.json(ret); +}; + +const data = [ + { + "browser": "Internet Explorer 4.0", + "platform": "Win 95+", + "version": "4", + "grade": "X", + "status": true, + "time": "1698364800" + }, + { + "browser": "Internet Explorer 5.0", + "platform": "Win 95+", + "version": "5", + "grade": "C", + "status": false, + "time": "1698364800" + }, + { + "browser": "Internet Explorer 5.5", + "platform": "Win 95+", + "version": "5.5", + "grade": "A", + "status": true, + "time": "1698364800" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 6", + "version": "6", + "grade": "A", + "status": false, + "time": "1698364800" + }, + { + "engine": "Trident", + "browser": "Internet Explorer 7", + "version": "7", + "grade": "A", + "status": true, + "time": "1698364800" + }, + { + "engine": "Trident", + "browser": "AOL browser (AOL desktop)", + "platform": "Win XP", + "grade": "A", + "version": "1", + "status": false, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Firefox 1.0", + "platform": "Win 98+ / OSX.2+", + "grade": "A", + "version": "1.8", + "status": true, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Firefox 1.5", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "status": false, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Firefox 2.0", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "status": true, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Firefox 3.0", + "platform": "Win 2k+ / OSX.3+", + "version": "1.9", + "grade": "A", + "status": false, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Camino 1.0", + "platform": "OSX.2+", + "version": "1.8", + "grade": "A", + "status": true, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Camino 1.5", + "platform": "OSX.3+", + "version": "1.8", + "grade": "A", + "status": false, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Netscape 7.2", + "platform": "Win 95+ / Mac OS 8.6-9.2", + "version": "1.7", + "grade": "A", + "status": true, + "time": "1698364800" + }, + { + "engine": "Gecko", + "browser": "Netscape Browser 8", + "platform": "Win 98SE+", + "version": "1.7", + "grade": "A", + "status": false, + "time": "1698796800" + }, + { + "engine": "Gecko", + "browser": "Netscape Navigator 9", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A", + "status": true, + "time": "1698796800" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.0", + "platform": "Win 95+ / OSX.1+", + "version": "1", + "grade": "A", + "status": false, + "time": "1698796800" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.1", + "platform": "Win 95+ / OSX.1+", + "version": "1.1", + "grade": "A", + "status": true, + "time": "1698796800" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.2", + "platform": "Win 95+ / OSX.1+", + "version": "1.2", + "grade": "A", + "status": false, + "time": "1698969600" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.3", + "platform": "Win 95+ / OSX.1+", + "version": "1.3", + "grade": "A", + "status": true, + "time": "1698969600" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.4", + "platform": "Win 95+ / OSX.1+", + "version": "1.4", + "grade": "A", + "status": false, + "time": "1698969600" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.5", + "platform": "Win 95+ / OSX.1+", + "version": "1.5", + "grade": "A", + "status": true, + "time": "1698969600" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.6", + "platform": "Win 95+ / OSX.1+", + "version": "1.6", + "grade": "A", + "status": false, + "time": "1698969600" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.7", + "platform": "Win 98+ / OSX.1+", + "version": "1.7", + "grade": "A", + "status": true, + "time": "1701388800" + }, + { + "engine": "Gecko", + "browser": "Mozilla 1.8", + "platform": "Win 98+ / OSX.1+", + "version": "1.8", + "grade": "A", + "status": false, + "time": "1701388800" + }, + { + "engine": "Gecko", + "browser": "Seamonkey 1.1", + "platform": "Win 98+ / OSX.2+", + "version": "1.8", + "grade": "A", + "status": true, + "time": "1701388800" + }, + { + "engine": "Gecko", + "browser": "Epiphany 2.20", + "platform": "Gnome", + "version": "1.8", + "grade": "A", + "status": false, + "time": "1701388800" + }, + { + "engine": "Webkit", + "browser": "Safari 1.2", + "platform": "OSX.3", + "version": "125.5", + "grade": "A", + "status": true, + "time": "1701388800" + }, + { + "engine": "Webkit", + "browser": "Safari 1.3", + "platform": "OSX.3", + "version": "312.8", + "grade": "A", + "status": false, + "time": "1702166400" + }, + { + "engine": "Webkit", + "browser": "Safari 2.0", + "platform": "OSX.4+", + "version": "419.3", + "grade": "A", + "status": true, + "time": "1702166400" + }, + { + "engine": "Webkit", + "browser": "Safari 3.0", + "platform": "OSX.4+", + "version": "522.1", + "grade": "A", + "status": false, + "time": "1702166400" + }, + { + "engine": "Webkit", + "browser": "OmniWeb 5.5", + "platform": "OSX.4+", + "version": "420", + "grade": "A", + "status": true, + "time": "1702166400" + }, + { + "engine": "Webkit", + "browser": "iPod Touch / iPhone", + "platform": "iPod", + "version": "420.1", + "grade": "A", + "status": false, + "time": "1702166400" + }, + { + "engine": "Webkit", + "browser": "S60", + "platform": "S60", + "version": "413", + "grade": "A", + "status": true, + "time": "1702166400" + }, + { + "engine": "Presto", + "browser": "Opera 7.0", + "platform": "Win 95+ / OSX.1+", + "version": "-", + "grade": "A", + "status": false, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 7.5", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A", + "status": true, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 8.0", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A", + "status": false, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 8.5", + "platform": "Win 95+ / OSX.2+", + "version": "-", + "grade": "A", + "status": true, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 9.0", + "platform": "Win 95+ / OSX.3+", + "version": "-", + "grade": "A", + "status": false, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 9.2", + "platform": "Win 88+ / OSX.3+", + "version": "-", + "grade": "A", + "status": true, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera 9.5", + "platform": "Win 88+ / OSX.3+", + "version": "-", + "grade": "A", + "status": false, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Opera for Wii", + "platform": "Wii", + "version": "-", + "grade": "A", + "status": true, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Nokia N800", + "platform": "N800", + "version": "-", + "grade": "A", + "status": false, + "time": "1702771200" + }, + { + "engine": "Presto", + "browser": "Nintendo DS browser", + "platform": "Nintendo DS", + "version": "8.5", + "grade": "C", + "status": true, + "time": "1703548800" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.1", + "platform": "KDE 3.1", + "version": "3.1", + "grade": "C", + "status": false, + "time": "1703548800" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.3", + "platform": "KDE 3.3", + "version": "3.3", + "grade": "A", + "status": true, + "time": "1703548800" + }, + { + "engine": "KHTML", + "browser": "Konqureror 3.5", + "platform": "KDE 3.5", + "version": "3.5", + "grade": "A", + "status": false, + "time": "1703548800" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 4.5", + "platform": "Mac OS 8-9", + "version": "-", + "grade": "X", + "status": true, + "time": "1703548800" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 5.1", + "platform": "Mac OS 7.6-9", + "version": "1", + "grade": "C", + "status": false, + "time": "1703548800" + }, + { + "engine": "Tasman", + "browser": "Internet Explorer 5.2", + "platform": "Mac OS 8-X", + "version": "1", + "grade": "C", + "status": true, + "time": "1703548800" + }, + { + "engine": "Misc", + "browser": "NetFront 3.1", + "platform": "Embedded devices", + "version": "-", + "grade": "C", + "status": false, + "time": "1703548800" + }, + { + "engine": "Misc", + "browser": "NetFront 3.4", + "platform": "Embedded devices", + "version": "-", + "grade": "A", + "status": true, + "time": "1703548800" + }, + { + "engine": "Misc", + "browser": "Dillo 0.8", + "platform": "Embedded devices", + "version": "-", + "grade": "X", + "status": false, + "time": "1703548800" + }, + { + "engine": "Misc", + "browser": "Links", + "platform": "Text only", + "version": "-", + "grade": "X", + "status": true, + "time": "1703721600" + }, + { + "engine": "Misc", + "browser": "Lynx", + "platform": "Text only", + "version": "-", + "grade": "X", + "status": false, + "time": "1703721600" + }, + { + "engine": "Misc", + "browser": "IE Mobile", + "platform": "Windows Mobile 6", + "version": "-", + "grade": "C", + "status": true, + "time": "1703721600" + }, + { + "engine": "Misc", + "browser": "PSP browser", + "platform": "PSP", + "version": "-", + "grade": "C", + "status": false, + "time": "1703721600" + }, + { + "engine": "Other browsers", + "browser": "All others", + "platform": "-", + "version": "-", + "grade": "U", + "status": true, + "time": "1703721600" + } +].map(function (item, index) { + return Object.assign({}, item, { + id: index + 1 + }); +}); diff --git a/packages/amis-core/src/store/crud.ts b/packages/amis-core/src/store/crud.ts index 606323ff15a..f89d4dcb0dd 100644 --- a/packages/amis-core/src/store/crud.ts +++ b/packages/amis-core/src/store/crud.ts @@ -18,6 +18,22 @@ import {normalizeApiResponseData} from '../utils/api'; import {matchSorter} from 'match-sorter'; import {filter} from '../utils/tpl'; +interface MatchFunc { + ( + /* 当前列表的全量数据 */ + items: any, + /* 最近一次接口返回的全量数据 */ + itemsRaw: any, + /** 相关配置 */ + options?: { + /* 查询参数 */ + query: Record; + /* 列配置 */ + columns: any; + } + ): any; +} + class ServerError extends Error { type = 'ServerError'; } @@ -161,10 +177,12 @@ export const CRUDStore = ServiceStore.named('CRUDStore') loadDataMode?: boolean; syncResponse2Query?: boolean; columns?: Array; + matchFunc?: MatchFunc; } = {} ) { try { if (!options.forceReload && options.loadDataOnce && self.total) { + const matchFunc = options.matchFunc; let items = options.source ? resolveVariableAndFilter( options.source, @@ -176,41 +194,49 @@ export const CRUDStore = ServiceStore.named('CRUDStore') ) : self.items.concat(); - if (Array.isArray(options.columns)) { - options.columns.forEach((column: any) => { - let value: any = - typeof column.name === 'string' - ? getVariable(self.query, column.name) - : undefined; - const key = column.name; - - if (value != null && key) { - // value可能为null、undefined、''、0 - if (Array.isArray(value)) { - if (value.length > 0) { - const arr = [...items]; - let arrItems: Array = []; - value.forEach(item => { - arrItems = [ - ...arrItems, - ...matchSorter(arr, item, { - keys: [key], - threshold: matchSorter.rankings.CONTAINS - }) - ]; + /** 字段的格式类型无法穷举,所以支持使用函数过滤 */ + if (matchFunc && typeof matchFunc === 'function') { + items = matchFunc(items, self.data.itemsRaw, { + query: self.query, + columns: options.columns + }); + } else { + if (Array.isArray(options.columns)) { + options.columns.forEach((column: any) => { + let value: any = + typeof column.name === 'string' + ? getVariable(self.query, column.name) + : undefined; + const key = column.name; + + if (value != null && key) { + // value可能为null、undefined、''、0 + if (Array.isArray(value)) { + if (value.length > 0) { + const arr = [...items]; + let arrItems: Array = []; + value.forEach(item => { + arrItems = [ + ...arrItems, + ...matchSorter(arr, item, { + keys: [key], + threshold: matchSorter.rankings.CONTAINS + }) + ]; + }); + items = items.filter((item: any) => + arrItems.find(a => a === item) + ); + } + } else { + items = matchSorter(items, value, { + keys: [key], + threshold: matchSorter.rankings.CONTAINS }); - items = items.filter((item: any) => - arrItems.find(a => a === item) - ); } - } else { - items = matchSorter(items, value, { - keys: [key], - threshold: matchSorter.rankings.CONTAINS - }); } - } - }); + }); + } } if (self.query.orderBy) { @@ -589,8 +615,10 @@ export const CRUDStore = ServiceStore.named('CRUDStore') source: string, options: { columns?: Array; + matchFunc?: MatchFunc | null; } ) { + const matchFunc = options.matchFunc; let items: Array = resolveVariableAndFilter(source, scope, '| raw'); if (!Array.isArray(items) && !self.items.length) { @@ -599,41 +627,49 @@ export const CRUDStore = ServiceStore.named('CRUDStore') items = Array.isArray(items) ? items : []; - if (Array.isArray(options.columns)) { - options.columns.forEach((column: any) => { - let value: any = - typeof column.name === 'string' - ? getVariable(self.query, column.name) - : undefined; - const key = column.name; - - if (value != null && key) { - // value可能为null、undefined、''、0 - if (Array.isArray(value)) { - if (value.length > 0) { - const arr = [...items]; - let arrItems: Array = []; - value.forEach(item => { - arrItems = [ - ...arrItems, - ...matchSorter(arr, item, { - keys: [key], - threshold: matchSorter.rankings.CONTAINS - }) - ]; + /** 字段的格式类型无法穷举,所以支持使用函数过滤 */ + if (matchFunc && typeof matchFunc === 'function') { + items = matchFunc(items, items.concat(), { + query: self.query, + columns: options.columns + }); + } else { + if (Array.isArray(options.columns)) { + options.columns.forEach((column: any) => { + let value: any = + typeof column.name === 'string' + ? getVariable(self.query, column.name) + : undefined; + const key = column.name; + + if (value != null && key) { + // value可能为null、undefined、''、0 + if (Array.isArray(value)) { + if (value.length > 0) { + const arr = [...items]; + let arrItems: Array = []; + value.forEach(item => { + arrItems = [ + ...arrItems, + ...matchSorter(arr, item, { + keys: [key], + threshold: matchSorter.rankings.CONTAINS + }) + ]; + }); + items = items.filter((item: any) => + arrItems.find(a => a === item) + ); + } + } else { + items = matchSorter(items, value, { + keys: [key], + threshold: matchSorter.rankings.CONTAINS }); - items = items.filter((item: any) => - arrItems.find(a => a === item) - ); } - } else { - items = matchSorter(items, value, { - keys: [key], - threshold: matchSorter.rankings.CONTAINS - }); } - } - }); + }); + } } if (self.query.orderBy) { diff --git a/packages/amis/src/renderers/CRUD.tsx b/packages/amis/src/renderers/CRUD.tsx index a140cf8d300..57098564088 100644 --- a/packages/amis/src/renderers/CRUD.tsx +++ b/packages/amis/src/renderers/CRUD.tsx @@ -329,6 +329,12 @@ export interface CRUDCommonSchema extends BaseSchema, SpinnerExtraProps { */ loadDataOnceFetchOnFilter?: boolean; + /** + * 自定义搜索匹配函数,当开启loadDataOnce时,会基于该函数计算的匹配结果进行过滤,主要用于处理列字段类型较为复杂或者字段值格式和后端返回不一致的场景 + * @since 3.5.0 + */ + matchFunc?: string | any; + /** * 也可以直接从环境变量中读取,但是不太推荐。 */ @@ -473,7 +479,8 @@ export default class CRUD extends React.Component { 'autoFillHeight', 'maxTagCount', 'overflowTagPopover', - 'parsePrimitiveQuery' + 'parsePrimitiveQuery', + 'matchFunc' ]; static defaultProps = { toolbarInline: true, @@ -1244,6 +1251,15 @@ export default class CRUD extends React.Component { ); this.lastQuery = store.query; const data = createObject(store.data, store.query); + const matchFunc = + this.props?.matchFunc && typeof this.props.matchFunc === 'string' + ? (str2function( + this.props.matchFunc, + 'items', + 'itemsRaw', + 'options' + ) as any) + : undefined; isEffectiveApi(api, data) ? store .fetchInitData(api, data, { @@ -1258,7 +1274,8 @@ export default class CRUD extends React.Component { perPageField, loadDataMode, syncResponse2Query, - columns: store.columns ?? columns + columns: store.columns ?? columns, + matchFunc }) .then(async value => { if (!isAlive(store)) { @@ -1332,7 +1349,8 @@ export default class CRUD extends React.Component { }) : source && store.initFromScope(data, source, { - columns: store.columns ?? columns + columns: store.columns ?? columns, + matchFunc }); }