feat: CRUD组件支持开启syncLocation后解析Query中的原始类型数据 (#8500)

This commit is contained in:
RUNZE LU 2023-10-25 20:37:19 +08:00 committed by GitHub
parent 13b07040a1
commit e1ee79032e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 637 additions and 10 deletions

View File

@ -200,6 +200,20 @@ CRUD 组件对数据源接口的数据结构要求如下:
| orderDir | 'asc'/'desc' | 排序方式 |
| keywords | string | 搜索关键字 |
### 解析Query原始类型
> `3.5.0`及以上版本
`syncLocation`开启后CRUD在初始化数据域时将会对url中的Query进行转换将原始类型的字符串格式的转化为同位类型目前仅支持**布尔类型**
```
"true" ==> true
"false" ==> false
```
如果只想保持字符串格式,可以设置`"parsePrimitiveQuery": false`关闭该特性,具体效果参考[示例](../../../examples/crud/parse-primitive-query)。
## 功能
既然这个渲染器叫增删改查,那接下来分开介绍这几个功能吧。
@ -2795,7 +2809,7 @@ CRUD 中不限制有多少个单条操作、添加一个操作对应的添加一
> 本文中的例子为了不相互影响都关闭了这个功能。
> 另外如果需要使用接口联动,需要设置`syncLocation: false`
`syncLocation`开启后数据域经过地址栏同步后原始值被转化为字符串同步回数据域但布尔值boolean同步后不符合预期数据结构导致组件渲染出错。比如查询条件表单中包含[Checkbox](./form/checkbox)组件,此时可以设置`{"trueValue": "1", "falseValue": "0"}`,将真值和假值设置为字符串格式规避。
`syncLocation`开启后数据域经过地址栏同步后原始值被转化为字符串同步回数据域但布尔值boolean同步后不符合预期数据结构导致组件渲染出错。比如查询条件表单中包含[Checkbox](./form/checkbox)组件,此时可以设置`{"trueValue": "1", "falseValue": "0"}`,将真值和假值设置为字符串格式规避。从`3.5.0`版本开始,已经支持[`parsePrimitiveQuery`](#解析query原始类型),该配置默认开启。
## 前端一次性加载

View File

@ -0,0 +1,73 @@
export default {
"title": "解析Query参数",
"body": [
{
"type": "alert",
"body": {
"type": "html",
"html": "<code>parsePrimitiveQuery</code>默认开启开启后会对url中的Query进行转换将原始类型的字符串格式的转化为同位类型目前仅支持<strong>布尔类型</strong>"
},
"level": "info",
"showCloseButton": true,
"showIcon": true,
"className": "mb-2"
},
{
"type": "crud",
"name": "crud",
"syncLocation": true,
"api": "/api/mock2/crud/table5",
"filter": {
"debug": true,
"title": "条件搜索",
"body": [
{
"type": "group",
"body": [
{
"type": "switch",
"name": "status",
"label": "已核验",
"size": "sm",
"value": false
}
]
}
],
"actions": [
{
"type": "reset",
"label": "重置"
},
{
"type": "submit",
"level": "primary",
"label": "查询"
}
]
},
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "status",
"label": "已核验",
"type": "tpl",
"tpl": "${status === true ? '是' : '否'}",
"filterable": {
"options": [
{"label": "是", "value": true},
{"label": "否", "value": false}
]
}
},
]
}
]
}

View File

@ -57,6 +57,7 @@ import LoadOnceTableCrudSchema from './CRUD/LoadOnce';
import ExportCSVExcelSchema from './CRUD/ExportCSVExcel';
import CRUDDynamicSchema from './CRUD/Dynamic';
import CRUDSimplePagerSchema from './CRUD/SimplePager';
import CRUDParsePrimitiveQuerySchema from './CRUD/ParsePrimitiveQuery';
import ItemActionchema from './CRUD/ItemAction';
import SdkTest from './Sdk/Test';
import JSONSchemaForm from './Form/Schem';
@ -460,6 +461,11 @@ export const examples = [
label: '简单分页',
path: '/examples/crud/simple-pager',
component: makeSchemaRenderer(CRUDSimplePagerSchema)
},
{
label: '解析Query参数',
path: '/examples/crud/parse-primitive-query',
component: makeSchemaRenderer(CRUDParsePrimitiveQuerySchema)
}
// {
// label: '',

View File

@ -0,0 +1,487 @@
/** 列字段为布尔值,开启查询 */
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);
}
return !!~req.query[key].indexOf(item[key] || '');
})
);
}
const ret = {
status: 0,
msg: 'ok',
data: {
count: items.length,
rows: items.concat().splice((page - 1) * perPage, perPage)
}
};
res.json(ret);
};
const data = [
{
"browser": "Internet Explorer 4.0",
"platform": "Win 95+",
"version": "4",
"grade": "X",
"status": true
},
{
"browser": "Internet Explorer 5.0",
"platform": "Win 95+",
"version": "5",
"grade": "C",
"status": false
},
{
"browser": "Internet Explorer 5.5",
"platform": "Win 95+",
"version": "5.5",
"grade": "A",
"status": true
},
{
"engine": "Trident",
"browser": "Internet Explorer 6",
"version": "6",
"grade": "A",
"status": false
},
{
"engine": "Trident",
"browser": "Internet Explorer 7",
"version": "7",
"grade": "A",
"status": true
},
{
"engine": "Trident",
"browser": "AOL browser (AOL desktop)",
"platform": "Win XP",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Firefox 1.0",
"platform": "Win 98+ / OSX.2+",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Firefox 1.5",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"status": false
},
{
"engine": "Gecko",
"browser": "Firefox 2.0",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"status": true
},
{
"engine": "Gecko",
"browser": "Firefox 3.0",
"platform": "Win 2k+ / OSX.3+",
"version": "1.9",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Camino 1.0",
"platform": "OSX.2+",
"version": "1.8",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Camino 1.5",
"platform": "OSX.3+",
"version": "1.8",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Netscape 7.2",
"platform": "Win 95+ / Mac OS 8.6-9.2",
"version": "1.7",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Netscape Browser 8",
"platform": "Win 98SE+",
"version": "1.7",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Netscape Navigator 9",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Mozilla 1.0",
"platform": "Win 95+ / OSX.1+",
"version": "1",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Mozilla 1.1",
"platform": "Win 95+ / OSX.1+",
"version": "1.1",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Mozilla 1.2",
"platform": "Win 95+ / OSX.1+",
"version": "1.2",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Mozilla 1.3",
"platform": "Win 95+ / OSX.1+",
"version": "1.3",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Mozilla 1.4",
"platform": "Win 95+ / OSX.1+",
"version": "1.4",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Mozilla 1.5",
"platform": "Win 95+ / OSX.1+",
"version": "1.5",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Mozilla 1.6",
"platform": "Win 95+ / OSX.1+",
"version": "1.6",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Mozilla 1.7",
"platform": "Win 98+ / OSX.1+",
"version": "1.7",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Mozilla 1.8",
"platform": "Win 98+ / OSX.1+",
"version": "1.8",
"grade": "A",
"status": false
},
{
"engine": "Gecko",
"browser": "Seamonkey 1.1",
"platform": "Win 98+ / OSX.2+",
"version": "1.8",
"grade": "A",
"status": true
},
{
"engine": "Gecko",
"browser": "Epiphany 2.20",
"platform": "Gnome",
"version": "1.8",
"grade": "A",
"status": false
},
{
"engine": "Webkit",
"browser": "Safari 1.2",
"platform": "OSX.3",
"version": "125.5",
"grade": "A",
"status": true
},
{
"engine": "Webkit",
"browser": "Safari 1.3",
"platform": "OSX.3",
"version": "312.8",
"grade": "A",
"status": false
},
{
"engine": "Webkit",
"browser": "Safari 2.0",
"platform": "OSX.4+",
"version": "419.3",
"grade": "A",
"status": true
},
{
"engine": "Webkit",
"browser": "Safari 3.0",
"platform": "OSX.4+",
"version": "522.1",
"grade": "A",
"status": false
},
{
"engine": "Webkit",
"browser": "OmniWeb 5.5",
"platform": "OSX.4+",
"version": "420",
"grade": "A",
"status": true
},
{
"engine": "Webkit",
"browser": "iPod Touch / iPhone",
"platform": "iPod",
"version": "420.1",
"grade": "A",
"status": false
},
{
"engine": "Webkit",
"browser": "S60",
"platform": "S60",
"version": "413",
"grade": "A",
"status": true
},
{
"engine": "Presto",
"browser": "Opera 7.0",
"platform": "Win 95+ / OSX.1+",
"version": "-",
"grade": "A",
"status": false
},
{
"engine": "Presto",
"browser": "Opera 7.5",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": true
},
{
"engine": "Presto",
"browser": "Opera 8.0",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": false
},
{
"engine": "Presto",
"browser": "Opera 8.5",
"platform": "Win 95+ / OSX.2+",
"version": "-",
"grade": "A",
"status": true
},
{
"engine": "Presto",
"browser": "Opera 9.0",
"platform": "Win 95+ / OSX.3+",
"version": "-",
"grade": "A",
"status": false
},
{
"engine": "Presto",
"browser": "Opera 9.2",
"platform": "Win 88+ / OSX.3+",
"version": "-",
"grade": "A",
"status": true
},
{
"engine": "Presto",
"browser": "Opera 9.5",
"platform": "Win 88+ / OSX.3+",
"version": "-",
"grade": "A",
"status": false
},
{
"engine": "Presto",
"browser": "Opera for Wii",
"platform": "Wii",
"version": "-",
"grade": "A",
"status": true
},
{
"engine": "Presto",
"browser": "Nokia N800",
"platform": "N800",
"version": "-",
"grade": "A",
"status": false
},
{
"engine": "Presto",
"browser": "Nintendo DS browser",
"platform": "Nintendo DS",
"version": "8.5",
"grade": "C",
"status": true
},
{
"engine": "KHTML",
"browser": "Konqureror 3.1",
"platform": "KDE 3.1",
"version": "3.1",
"grade": "C",
"status": false
},
{
"engine": "KHTML",
"browser": "Konqureror 3.3",
"platform": "KDE 3.3",
"version": "3.3",
"grade": "A",
"status": true
},
{
"engine": "KHTML",
"browser": "Konqureror 3.5",
"platform": "KDE 3.5",
"version": "3.5",
"grade": "A",
"status": false
},
{
"engine": "Tasman",
"browser": "Internet Explorer 4.5",
"platform": "Mac OS 8-9",
"version": "-",
"grade": "X",
"status": true
},
{
"engine": "Tasman",
"browser": "Internet Explorer 5.1",
"platform": "Mac OS 7.6-9",
"version": "1",
"grade": "C",
"status": false
},
{
"engine": "Tasman",
"browser": "Internet Explorer 5.2",
"platform": "Mac OS 8-X",
"version": "1",
"grade": "C",
"status": true
},
{
"engine": "Misc",
"browser": "NetFront 3.1",
"platform": "Embedded devices",
"version": "-",
"grade": "C",
"status": false
},
{
"engine": "Misc",
"browser": "NetFront 3.4",
"platform": "Embedded devices",
"version": "-",
"grade": "A",
"status": true
},
{
"engine": "Misc",
"browser": "Dillo 0.8",
"platform": "Embedded devices",
"version": "-",
"grade": "X",
"status": false
},
{
"engine": "Misc",
"browser": "Links",
"platform": "Text only",
"version": "-",
"grade": "X",
"status": true
},
{
"engine": "Misc",
"browser": "Lynx",
"platform": "Text only",
"version": "-",
"grade": "X",
"status": false
},
{
"engine": "Misc",
"browser": "IE Mobile",
"platform": "Windows Mobile 6",
"version": "-",
"grade": "C",
"status": true
},
{
"engine": "Misc",
"browser": "PSP browser",
"platform": "PSP",
"version": "-",
"grade": "C",
"status": false
},
{
"engine": "Other browsers",
"browser": "All others",
"platform": "-",
"version": "-",
"grade": "U",
"status": true
}
].map(function (item, index) {
return Object.assign({}, item, {
id: index + 1
});
});

View File

@ -2041,26 +2041,58 @@ export function isNumeric(value: any): boolean {
return /^[-+]?(?:\d*[.])?\d+$/.test(value);
}
/**
* Query字符串中的原始类型
*
* @param query
* @returns
*/
export function parsePrimitiveQueryString(rawQuery: Record<string, any>) {
if (!isPlainObject(rawQuery)) {
return rawQuery;
}
const query = JSONValueMap(rawQuery, value => {
/** 解析布尔类型,后续有需要在这里扩充 */
if (value === 'true' || value === 'false') {
return value === 'true';
}
return value;
});
return query;
}
/**
* URL链接中的query参数hash mode
*
* @param location Location对象Location结构的对象
* @param {Object} options
* @param {Boolean} options.parsePrimitive query的值解析为原始类型
*/
export function parseQuery(
location?: Location | {query?: any; search?: any; [propName: string]: any}
location?: Location | {query?: any; search?: any; [propName: string]: any},
options?: {parsePrimitive?: boolean}
): Record<string, any> {
const {parsePrimitive = false} = options || {};
const query =
(location && !(location instanceof Location) && location?.query) ||
(location && location?.search && qsparse(location.search.substring(1))) ||
(window.location.search && qsparse(window.location.search.substring(1)));
const normalizedQuery = isPlainObject(query)
? parsePrimitive
? parsePrimitiveQueryString(query)
: query
: {};
/* 处理hash中的query */
const hash = window.location?.hash;
let hashQuery = {};
let idx = -1;
if (typeof hash === 'string' && ~(idx = hash.indexOf('?'))) {
hashQuery = qsparse(hash.substring(idx + 1));
}
const normalizedQuery = isPlainObject(query) ? query : {};
return merge(normalizedQuery, hashQuery);
}

View File

@ -57,6 +57,7 @@ import {
isPureVariable,
resolveVariableAndFilter,
parseQuery,
parsePrimitiveQueryString,
isMobile
} from 'amis-core';
@ -362,6 +363,11 @@ export interface CRUDCommonSchema extends BaseSchema, SpinnerExtraProps {
*
*/
autoFillHeight?: TableSchema['autoFillHeight'];
/**
* Query信息转换url中的Query进行转换
*/
parsePrimitiveQuery?: boolean;
}
export type CRUDCardsSchema = CRUDCommonSchema & {
@ -461,7 +467,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'formStore',
'autoFillHeight',
'maxTagCount',
'overflowTagPopover'
'overflowTagPopover',
'parsePrimitiveQuery'
];
static defaultProps = {
toolbarInline: true,
@ -478,7 +485,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
filterTogglable: false,
filterDefaultVisible: true,
loadDataOnce: false,
autoFillHeight: false
autoFillHeight: false,
parsePrimitiveQuery: true
};
control: any;
@ -525,21 +533,22 @@ export default class CRUD extends React.Component<CRUDProps, any> {
pageField,
perPageField,
syncLocation,
loadDataOnce
loadDataOnce,
parsePrimitiveQuery
} = props;
this.mounted = true;
if (syncLocation && location && (location.query || location.search)) {
store.updateQuery(
parseQuery(location),
parseQuery(location, {parsePrimitive: parsePrimitiveQuery}),
undefined,
pageField,
perPageField
);
} else if (syncLocation && !location && window.location.search) {
store.updateQuery(
parseQuery(window.location),
parseQuery(window.location, {parsePrimitive: parsePrimitiveQuery}),
undefined,
pageField,
perPageField
@ -633,7 +642,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
) {
// 同步地址栏,那么直接检测 query 是否变了,变了就重新拉数据
store.updateQuery(
parseQuery(props.location),
parseQuery(props.location, {parsePrimitive: props.parsePrimitiveQuery}),
undefined,
props.pageField,
props.perPageField
@ -972,7 +981,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
env,
pageField,
perPageField,
loadDataOnceFetchOnFilter
loadDataOnceFetchOnFilter,
parsePrimitiveQuery
} = this.props;
/** 找出clearValueOnHidden的字段, 保证updateQuery时不会使用上次的保留值 */
@ -984,6 +994,11 @@ export default class CRUD extends React.Component<CRUDProps, any> {
? qsparse(qsstringify(values, undefined, true))
: values;
/** 把布尔值反解出来 */
if (parsePrimitiveQuery) {
values = parsePrimitiveQueryString(values);
}
store.updateQuery(
{
...values,