mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
Merge branch 'baidu:master' into master
This commit is contained in:
commit
e75a2d7f2d
@ -433,23 +433,50 @@ window.echarts = amisRequire('echarts');
|
||||
|
||||
然后通过 script 标签引入这两个文件。
|
||||
|
||||
## 动态处理 echart 配置
|
||||
|
||||
echarts 的 config 一般是静态配置的,支持简单的数据映射。如果你觉得还不够灵活可以通过自己手写逻辑代码来完成配置。
|
||||
|
||||
通过配置 dataFiler 来处理。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "chart",
|
||||
"data": {
|
||||
"line": [65, 63, 10, 73, 42, 21],
|
||||
"line2": [22, 33, 90, 20, 11, 33]
|
||||
},
|
||||
"config": {
|
||||
"xAxis": {
|
||||
"type": "category",
|
||||
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||||
},
|
||||
"yAxis": {
|
||||
"type": "value"
|
||||
}
|
||||
},
|
||||
"dataFilter": "config.series = [];Object.keys(data).forEach(function(key) {config.series.push({data: data[key], type: 'line'})})"
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | -------------------------------------------- | --------- | ------------------------------------------------------------------ |
|
||||
| type | `string` | `"chart"` | 指定为 chart 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| body | [SchemaNode](../../docs/types/schemanode) | | 内容容器 |
|
||||
| api | [api](../../docs/types/api) | | 配置项接口地址 |
|
||||
| source | [数据映射](../../docs/concepts/data-mapping) | | 通过数据映射获取数据链中变量值作为配置 |
|
||||
| initFetch | `boolean` | | 组件初始化时,是否请求接口 |
|
||||
| interval | `number` | | 刷新时间(最小 1000) |
|
||||
| config | `object` 或 `string` | | 设置 eschars 的配置项,当为`string`的时候可以设置 function 等配置项 |
|
||||
| style | `object` | | 设置根元素的 style |
|
||||
| width | `string` | | 设置根元素的宽度 |
|
||||
| height | `string` | | 设置根元素的高度 |
|
||||
| replaceChartOption | `boolean` | `false` | 每次更新是完全覆盖配置项还是追加? |
|
||||
| `trackExpression` | `string` | | 当这个表达式的值有变化时更新图表 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | -------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| type | `string` | `"chart"` | 指定为 chart 渲染器 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| body | [SchemaNode](../../docs/types/schemanode) | | 内容容器 |
|
||||
| api | [api](../../docs/types/api) | | 配置项接口地址 |
|
||||
| source | [数据映射](../../docs/concepts/data-mapping) | | 通过数据映射获取数据链中变量值作为配置 |
|
||||
| initFetch | `boolean` | | 组件初始化时,是否请求接口 |
|
||||
| interval | `number` | | 刷新时间(最小 1000) |
|
||||
| config | `object` 或 `string` | | 设置 eschars 的配置项,当为`string`的时候可以设置 function 等配置项 |
|
||||
| style | `object` | | 设置根元素的 style |
|
||||
| width | `string` | | 设置根元素的宽度 |
|
||||
| height | `string` | | 设置根元素的高度 |
|
||||
| replaceChartOption | `boolean` | `false` | 每次更新是完全覆盖配置项还是追加? |
|
||||
| trackExpression | `string` | | 当这个表达式的值有变化时更新图表 |
|
||||
| dataFilter | `string` | | 自定义 echart config 转换,函数签名:function(config, echarts, data) {return config;} 配置时直接写函数体。其中 config 是当前 echart 配置,echarts 就是 echarts 对象,data 为上下文数据。 |
|
||||
|
||||
## 动作表
|
||||
|
||||
|
@ -55,9 +55,9 @@ title: 快速开始
|
||||
使用方法:
|
||||
|
||||
- JS SDK
|
||||
- 引入 sdk 中的文件 `<link rel="stylesheet" href="sdk/ helper.css" />`
|
||||
- 引入 sdk 中的文件 `<link rel="stylesheet" href="sdk/helper.css" />`
|
||||
- React
|
||||
- `import 'amis/lib/ helper.css'`;
|
||||
- `import 'amis/lib/helper.css'`;
|
||||
|
||||
目前这个文件没有和主题文件合并在一起,用户可以选择性加载。
|
||||
|
||||
|
@ -66,8 +66,10 @@ class Preview extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// TODO: 会报错,可能得后续 amis 内部的 render 也改才行
|
||||
// this.roots.forEach(root => root.unmount());
|
||||
// 立即 unmout 会报错
|
||||
window.requestAnimationFrame(() => {
|
||||
this.roots.forEach(root => root.unmount());
|
||||
});
|
||||
}
|
||||
|
||||
divRef(ref) {
|
||||
|
@ -974,6 +974,9 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
if (!dispatcher?.prevented) {
|
||||
env.notify('error', __('Form.validateFailed'));
|
||||
}
|
||||
|
||||
/** 抛异常是为了在dialog中catch这个错误,避免弹窗直接关闭 */
|
||||
return Promise.reject(__('Form.validateFailed'));
|
||||
} else {
|
||||
dispatchEvent('validateSucc', this.props.data);
|
||||
this.handleAction(
|
||||
|
@ -106,6 +106,7 @@
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
background: var(--Tabs-content-bg);
|
||||
position: relative;
|
||||
|
||||
> .#{$ns}AnchorNav-section {
|
||||
display: block;
|
||||
|
@ -25,7 +25,7 @@ const collapseStyles: {
|
||||
|
||||
export interface CollapseProps {
|
||||
key?: string;
|
||||
id?: string;
|
||||
collapseId?: string;
|
||||
propKey?: string;
|
||||
mountOnEnter?: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
|
@ -64,7 +64,7 @@ class CollapseGroup extends React.Component<
|
||||
activeKey = [];
|
||||
} else {
|
||||
for (let i = 0; i < activeKey.length; i++) {
|
||||
if (activeKey[i] === item.id) {
|
||||
if (activeKey[i] === item.collapseId) {
|
||||
activeKey.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
@ -72,9 +72,9 @@ class CollapseGroup extends React.Component<
|
||||
}
|
||||
} else {
|
||||
if (this.props.accordion) {
|
||||
activeKey = [item.id as string];
|
||||
activeKey = [item.collapseId as string];
|
||||
} else {
|
||||
activeKey.push(item.id as string);
|
||||
activeKey.push(item.collapseId as string);
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@ -90,13 +90,13 @@ class CollapseGroup extends React.Component<
|
||||
return children.map((child: React.ReactElement, index: number) => {
|
||||
let props = child.props;
|
||||
|
||||
const id = props.propKey || String(index);
|
||||
const collapsed = this.state.activeKey.indexOf(id) === -1;
|
||||
const collapseId = props.propKey || String(index);
|
||||
const collapsed = this.state.activeKey.indexOf(collapseId) === -1;
|
||||
|
||||
return React.cloneElement(child as any, {
|
||||
...props,
|
||||
key: id,
|
||||
id,
|
||||
key: collapseId,
|
||||
collapseId,
|
||||
collapsed,
|
||||
expandIcon: this.props.expandIcon,
|
||||
propsUpdate: true,
|
||||
|
@ -239,7 +239,7 @@ export class FormulaEditor extends React.Component<
|
||||
handleVariableSelect(item: VariableItem) {
|
||||
const {evalMode, selfVariableName} = this.props;
|
||||
|
||||
if (item && item.value && selfVariableName === item.value) {
|
||||
if (item && item.value && (selfVariableName && selfVariableName === item.value)) {
|
||||
toast.warning('不能使用当前变量[self],避免循环引用。');
|
||||
return;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ function VariableList(props: VariableListProps) {
|
||||
: (option: Option, states: ItemRenderStates): JSX.Element => {
|
||||
return (
|
||||
<span className={cx(`${classPrefix}-item`, itemClassName)}>
|
||||
{option.label && option.value === selfVariableName && (
|
||||
{option.label && (selfVariableName && option.value === selfVariableName) && (
|
||||
<Badge
|
||||
classnames={cx}
|
||||
badge={{
|
||||
@ -58,7 +58,7 @@ function VariableList(props: VariableListProps) {
|
||||
<label>{option.label}</label>
|
||||
</Badge>
|
||||
)}
|
||||
{option.label && option.value !== selfVariableName && (
|
||||
{option.label && (!selfVariableName || option.value !== selfVariableName) && (
|
||||
<label>{option.label}</label>
|
||||
)}
|
||||
{option?.tag ? (
|
||||
|
@ -99,7 +99,7 @@ exports[`Renderer:chained-select 1`] = `
|
||||
<div
|
||||
class="cxd-Select-value"
|
||||
>
|
||||
B 0
|
||||
B 1
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
|
@ -1,11 +1,15 @@
|
||||
import React = require('react');
|
||||
import {render, waitForElementToBeRemoved} from '@testing-library/react';
|
||||
import {
|
||||
render,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved
|
||||
} from '@testing-library/react';
|
||||
import '../../../src';
|
||||
import {render as amisRender} from '../../../src';
|
||||
import {makeEnv, wait} from '../../helper';
|
||||
|
||||
test('Renderer:chained-select', async () => {
|
||||
const {container, findByText} = render(
|
||||
const {container, findByText, getByText, getByTestId} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'form',
|
||||
@ -24,26 +28,60 @@ test('Renderer:chained-select', async () => {
|
||||
},
|
||||
{},
|
||||
makeEnv({
|
||||
fetcher: async (config: any) => {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {},
|
||||
data: {
|
||||
status: 0,
|
||||
msg: '',
|
||||
data: [
|
||||
{label: 'A 0', value: 'a'},
|
||||
{label: 'B 0', value: 'b'},
|
||||
{label: 'C 0', value: 'c'},
|
||||
{label: 'D 0', value: 'd'}
|
||||
]
|
||||
}
|
||||
};
|
||||
async fetcher(config: any): Promise<any> {
|
||||
const level = parseInt(config.query.level, 10) || 0;
|
||||
const maxLevel = parseInt(config.query.maxLevel, 10) || 0;
|
||||
if (level >= maxLevel) {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {},
|
||||
data: {
|
||||
status: 0,
|
||||
data: null
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {},
|
||||
data: {
|
||||
status: 0,
|
||||
msg: '',
|
||||
data: [
|
||||
{
|
||||
label: `A ${level}`,
|
||||
value: 'a'
|
||||
},
|
||||
|
||||
{
|
||||
label: `B ${level}`,
|
||||
value: 'b'
|
||||
},
|
||||
|
||||
{
|
||||
label: `C ${level}`,
|
||||
value: 'c'
|
||||
},
|
||||
|
||||
{
|
||||
label: `D ${level}`,
|
||||
value: 'd'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await wait(500);
|
||||
await waitFor(() => {
|
||||
expect(getByText('B 1')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
@ -670,7 +670,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
} else if (action.actionType === 'reload') {
|
||||
} else if (action.actionType === 'reload' && !action.target) {
|
||||
this.reload();
|
||||
} else if (
|
||||
pickerMode &&
|
||||
@ -2067,6 +2067,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||
autoGenerateFilter,
|
||||
onSelect,
|
||||
autoFillHeight,
|
||||
onEvent,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
|
@ -122,11 +122,27 @@ const EVAL_CACHE: {[key: string]: Function} = {};
|
||||
/**
|
||||
* ECharts 中有些配置项可以写函数,但 JSON 中无法支持,为了实现这个功能,需要将看起来像函数的字符串转成函数类型
|
||||
* 目前 ECharts 中可能有函数的配置项有如下:interval、formatter、color、min、max、labelFormatter、pageFormatter、optionToContent、contentToOption、animationDelay、animationDurationUpdate、animationDelayUpdate、animationDuration、position、sort
|
||||
* 其中用得最多的是 formatter、sort,所以目前先只支持它们
|
||||
* @param config ECharts 配置
|
||||
*/
|
||||
function recoverFunctionType(config: object) {
|
||||
['formatter', 'sort', 'renderItem'].forEach((key: string) => {
|
||||
[
|
||||
'interval',
|
||||
'formatter',
|
||||
'color',
|
||||
'min',
|
||||
'max',
|
||||
'labelFormatter',
|
||||
'pageFormatter',
|
||||
'optionToContent',
|
||||
'contentToOption',
|
||||
'animationDelay',
|
||||
'animationDurationUpdate',
|
||||
'animationDelayUpdate',
|
||||
'animationDuration',
|
||||
'position',
|
||||
'sort',
|
||||
'renderItem'
|
||||
].forEach((key: string) => {
|
||||
const objects = findObjectsWithKey(config, key);
|
||||
for (const object of objects) {
|
||||
const code = object[key];
|
||||
|
@ -104,6 +104,13 @@ export default class TextAreaControl extends React.Component<
|
||||
this.inputRef.current?.focus();
|
||||
}
|
||||
|
||||
@autobind
|
||||
@bindRendererEvent<TextAreaProps, TextAreaRendererEvent>('change')
|
||||
handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
const {onChange} = this.props;
|
||||
onChange && onChange(e);
|
||||
}
|
||||
|
||||
@autobind
|
||||
@bindRendererEvent<TextAreaProps, TextAreaRendererEvent>('focus')
|
||||
handleFocus(e: React.FocusEvent<HTMLTextAreaElement>) {
|
||||
@ -142,7 +149,7 @@ export default class TextAreaControl extends React.Component<
|
||||
const {...rest} = this.props;
|
||||
|
||||
return (
|
||||
<Textarea {...rest} onFocus={this.handleFocus} onBlur={this.handleBlur} />
|
||||
<Textarea {...rest} onFocus={this.handleFocus} onBlur={this.handleBlur} onChange={this.handleChange} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -99,11 +99,18 @@ export function StepsCmpt(props: StepsProps) {
|
||||
useMobileUI
|
||||
} = props;
|
||||
|
||||
const stepsRow =
|
||||
(resolveVariableAndFilter(source, data, '| raw') as Array<StepSchema>) ||
|
||||
let sourceResult: Array<StepSchema> = resolveVariableAndFilter(
|
||||
source,
|
||||
data,
|
||||
'| raw'
|
||||
) as Array<StepSchema>;
|
||||
|
||||
const stepsRow: Array<StepSchema> =
|
||||
(Array.isArray(sourceResult) ? sourceResult : undefined) ||
|
||||
config ||
|
||||
steps ||
|
||||
[];
|
||||
|
||||
const resolveRender = (val?: string | SchemaCollection) =>
|
||||
typeof val === 'string' ? filter(val, data) : val && render('inner', val);
|
||||
const value = getPropValue(props) ?? 0;
|
||||
|
@ -2818,6 +2818,22 @@ export class TableRenderer extends Table {
|
||||
return scoped.send(subPath, values);
|
||||
}
|
||||
}
|
||||
|
||||
reload(subPath?: string, query?: any, ctx?: any) {
|
||||
const scoped = this.context as IScopedContext;
|
||||
const parents = scoped?.parent?.getComponents();
|
||||
|
||||
if (Array.isArray(parents) && parents.length) {
|
||||
// CRUD的name会透传给Table,这样可以保证找到CRUD
|
||||
const crud = parents.find(cmpt => cmpt?.props?.name === this.props?.name);
|
||||
|
||||
return crud?.reload?.(subPath, query, ctx);
|
||||
}
|
||||
|
||||
if (subPath) {
|
||||
return scoped.reload(subPath, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {TableCell};
|
||||
|
Loading…
Reference in New Issue
Block a user