fix: CRUD列字段无返回值时会错误展示字段值问题; chore: 修复InputRange单测 (#5094)

This commit is contained in:
RUNZE LU 2022-08-09 17:09:25 +08:00 committed by GitHub
parent 16b1677942
commit e3a4f1a715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 568 additions and 70 deletions

View File

@ -534,60 +534,85 @@ Cards 模式支持 [Cards](./cards) 中的所有功能。
## 查询条件表单
大部分表格展示有对数据进行检索的需求CRUD 自身支持通过配置`filter`,实现查询条件过滤表单
大部分表格展示有对数据进行检索的需求CRUD 自身支持通过配置`filter`,实现查询条件过滤表单。`filter` 配置实际上同 [Form](./form/index) 组件,因此支持绝大部分`form`的功能。
`filter` 配置实际上同 [Form](./form/index) 组件,因此支持绝大部分`form`的功能
在条件搜索区的 `Engine` 输入框中输入任意值查询会发现结果中 `ID` 为 1 - 3 的 `Rendering engine` 列因为返回值中没有对应字段值,被错误填入了与 `filter` 中相同 `name` 的字段值,这是因为表格 Cell 通过[数据链](../../docs/concepts/datascope-and-datachain)获取到了上层数据域 `filter` 中相同字段的数据值。这种情况可以在 CRUD `columns` 对应列配置`"canAccessSuperData": false`禁止访问父级数据域(比如: `Platform`列)
```schema: scope="body"
{
"type": "crud",
"name": "crud",
"syncLocation": false,
"api": "/api/mock2/sample",
"filter": {
"api": "/api/mock2/crud/table4",
"filter": {
"debug": true,
"title": "条件搜索",
"body": [
{
"type": "flex",
"justify": "space-between",
"alignItems": "center",
"items": [
"type": "group",
"body": [
{
"type": "input-text",
"name": "keywords",
"label": "关键字",
"clearable": true,
"placeholder": "通过关键字搜索",
"size": "sm"
},
{
"type": "button",
"actionType": "drawer",
"icon": "fa fa-plus",
"label": "创建记录",
"target": "crud",
"closeOnOutside": true,
"drawer": {
"title": "创建记录",
"body": {
"type": "form",
"api": "post:/api/mock2/sample",
"body": [
{
"type": "input-text",
"name": "engine",
"label": "Engine"
},
{
"type": "input-text",
"name": "browser",
"label": "Browser"
}
]
}
}
"type": "input-text",
"name": "engine",
"label": "Engine",
"clearable": true,
"size": "sm"
},
{
"type": "input-text",
"name": "platform",
"label": "Platform",
"clearable": true,
"size": "sm"
}
]
}
],
actions: [
{
"type": "button",
"actionType": "drawer",
"icon": "fa fa-plus",
"label": "创建记录",
"target": "crud",
"closeOnOutside": true,
"drawer": {
"title": "创建记录",
"body": {
"type": "form",
"api": "post:/api/mock2/sample",
"body": [
{
"type": "input-text",
"name": "engine",
"label": "Engine"
},
{
"type": "input-text",
"name": "browser",
"label": "Browser"
}
]
}
}
},
{
"type": "reset",
"label": "重置"
},
{
"type": "submit",
"level": "primary",
"label": "查询"
}
]
},
"columns": [
@ -605,7 +630,8 @@ Cards 模式支持 [Cards](./cards) 中的所有功能。
},
{
"name": "platform",
"label": "Platform(s)"
"label": "Platform(s)",
"canAccessSuperData": false
},
{
"name": "version",

View File

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

View File

@ -1472,10 +1472,14 @@ export function getScrollbarWidth() {
}
// 后续改用 FormulaExec['formula']
function resolveValueByName(data: any, name?: string) {
function resolveValueByName(
data: any,
name?: string,
canAccessSuper?: boolean
) {
return isPureVariable(name)
? resolveVariableAndFilter(name, data)
: resolveVariable(name, data);
: resolveVariable(name, data, canAccessSuper);
}
// 统一的获取 value 值方法
@ -1486,10 +1490,13 @@ export function getPropValue<
data?: any;
defaultValue?: any;
}
>(props: T, getter?: (props: T) => any) {
>(props: T, getter?: (props: T) => any, canAccessSuper?: boolean) {
const {name, value, data, defaultValue} = props;
return (
value ?? getter?.(props) ?? resolveValueByName(data, name) ?? defaultValue
value ??
getter?.(props) ??
resolveValueByName(data, name, canAccessSuper) ??
defaultValue
);
}

View File

@ -1,14 +1,22 @@
import {Evaluator, parse} from 'amis-formula';
import {getVariable} from './getVariable';
export function resolveVariable(path?: string, data: any = {}): any {
export function resolveVariable(
path?: string,
data: any = {},
canAccessSuper?: boolean
): any {
if (path === '&' || path == '$$') {
return data;
} else if (!path || typeof path !== 'string') {
return undefined;
} else if (!~path.indexOf(':')) {
// 简单用法直接用 getVariable
return getVariable(data, path[0] === '$' ? path.substring(1) : path);
return getVariable(
data,
path[0] === '$' ? path.substring(1) : path,
canAccessSuper
);
}
// window:xxx ls:xxx.xxx

View File

@ -277,7 +277,7 @@ exports[`Renderer:range with min & max & step & joinValues 1`] = `
class="cxd-TplField"
>
<span>
The form
表单
</span>
</span>
</h3>
@ -293,18 +293,6 @@ exports[`Renderer:range with min & max & step & joinValues 1`] = `
style="display: none;"
type="submit"
/>
<pre>
<code
class="cxd-Form--debug"
>
{
"range": {
"min": 0.2,
"max": 0.8
}
}
</code>
</pre>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
@ -634,6 +622,22 @@ exports[`Renderer:range with min & max & step & joinValues 1`] = `
</div>
</form>
</div>
<div
class="cxd-Panel-footerWrap"
>
<div
class="cxd-Panel-btnToolbar cxd-Panel-footer"
>
<button
class="cxd-Button cxd-Button--primary"
type="submit"
>
<span>
Submit
</span>
</button>
</div>
</div>
<div
class="resize-sensor"
style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: scroll; z-index: -1; visibility: hidden;"

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import {render, fireEvent, screen, waitFor} from '@testing-library/react';
import '../../../src';
import {render as amisRender} from '../../../src';
import {makeEnv, wait} from '../../helper';
@ -233,13 +233,15 @@ test('Renderer:range with tooltipVisible & tooltipPlacement', async () => {
});
test('Renderer:range with min & max & step & joinValues', async () => {
const onSubmit = jest.fn();
const submitBtnText = 'Submit';
const {container} = render(
amisRender(
{
type: 'form',
debug: true,
api: '/api/xxx',
controls: [
submitText: submitBtnText,
api: '/api/mock/saveForm?waitSeconds=1',
body: [
{
type: 'input-range',
name: 'range',
@ -250,11 +252,9 @@ test('Renderer:range with min & max & step & joinValues', async () => {
showInput: true,
joinValues: false
}
],
title: 'The form',
actions: []
]
},
{},
{onSubmit},
makeEnv({})
)
);
@ -280,13 +280,23 @@ test('Renderer:range with min & max & step & joinValues', async () => {
).getAttribute('style')
).toContain('width: 60%; left: 20%;');
expect((container.querySelector('.cxd-Form--debug') as Element).innerHTML)
.toBe(`{
"range": {
"min": 0.2,
"max": 0.8
}
}`);
await wait(500);
const submitBtn = screen.getByRole('button', {name: submitBtnText});
await waitFor(() => {
expect(submitBtn).toBeInTheDocument();
});
fireEvent.click(submitBtn);
await wait(500);
const formData = onSubmit.mock.calls[0][0];
expect(onSubmit).toHaveBeenCalled();
expect(formData).toEqual({
range: {
min: 0.2,
max: 0.8
}
});
expect(container).toMatchSnapshot();
});

View File

@ -66,6 +66,7 @@ export class TableCell extends React.Component<RendererProps> {
className: innerClassName,
type: (column && column.type) || 'plain'
};
const canAccessSuperData = schema?.canAccessSuperData !== false;
// 如果本来就是 type 为 button不要删除其他情况下都应该删除。
if (schema.type !== 'button' && schema.type !== 'dropdown-button') {
@ -77,7 +78,8 @@ export class TableCell extends React.Component<RendererProps> {
: render('field', schema, {
...omit(rest, Object.keys(schema)),
inputOnly: true,
value,
/** value没有返回值时设置默认值避免错误获取到父级数据域的值 */
value: canAccessSuperData ? value : value ?? '',
data
});

View File

@ -165,6 +165,11 @@ export type TableColumnObject = {
* , inputTable
*/
unique?: boolean;
/**
* true
*/
canAccessSuperData?: boolean;
};
export type TableColumnWithType = SchemaObject & TableColumnObject;
@ -293,6 +298,11 @@ export interface TableSchema extends BaseSchema {
* searchable属性值
*/
autoGenerateFilter?: boolean;
/**
* false
*/
canAccessSuperData?: boolean;
}
export interface TableProps extends RendererProps {