feat:部分组件支持init事件&部分选项组件支持selectedItems参数 (#5700)

* feat:部分组件支持didMount事件&部分选项组件支持selectedItems参数

* feat:部分组件支持didMount事件&部分选项组件支持selectedItems参数

* feat:部分组件支持初始化事件&部分选项组件支持selectedItems参数

* feat:部分组件支持初始化事件&部分选项组件支持selectedItems参数

* feat:部分组件支持初始化事件&部分选项组件支持selectedItems参数
This commit is contained in:
hsm-lv 2022-11-14 09:28:03 +08:00 committed by GitHub
parent 710aae67a4
commit dfb9a738ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 139 additions and 71 deletions

View File

@ -38,7 +38,7 @@ order: 99
}
]
}
],
]
...
}
```
@ -71,6 +71,14 @@ order: 99
- `visible` 有些页面可能不想出现在菜单中,可以配置成 `false`,另外带参数的路由无需配置,直接就是不可见的。
- `className` 菜单类名。
## 事件表
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`来获取事件产生的数据(`< 2.3.2 及以下版本 ${event.data.[事件参数名]}`详细请查看[事件动作](../../docs/concepts/event-action)
| 事件名称 | 事件参数 | 说明 |
| -------- | -------- | --------------------------------------------------- |
| init | - | 组件实例被创建并插入 DOM 中时触发。2.4.1 及以上版本 |
## 动作表
| 动作名称 | 动作配置 | 说明 |

View File

@ -663,7 +663,14 @@ echarts 的 config 一般是静态配置的,支持简单的数据映射。如
> 2.4.1 及以上版本
当前组件会对外派发`click`、`mouseover`、`legendselectchanged`事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。事件参数提供通用的字段,可以查看[ECharst 事件与行为文档](https://echarts.apache.org/handbook/zh/concepts/event/)。
当前组件会对外派发以下事件,可以通过`onEvent`来监听这些事件,并通过`actions`来配置执行的动作,在`actions`中可以通过`${事件参数名}`来获取事件产生的数据,详细请查看[事件动作](../../docs/concepts/event-action)。
| 事件名称 | 事件参数 | 说明 |
| ------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------- |
| init | - | 组件实例被创建并插入 DOM 中时触发。2.4.1 及以上版本 |
| click | 查看[ECharst 事件与行为文档](https://echarts.apache.org/handbook/zh/concepts/event/) | 鼠标点击时触发 |
| mouseover | 查看[ECharst 事件与行为文档](https://echarts.apache.org/handbook/zh/concepts/event/) | 鼠标悬浮时触发 |
| legendselectchanged | 查看[ECharst 事件与行为文档](https://echarts.apache.org/handbook/zh/concepts/event/) | 切换图例选中状态时触发 |
## 动作表

View File

@ -1443,8 +1443,8 @@ Form 支持轮询初始化接口,步骤如下:
| promptPageLeave | `boolean` | `false` | form 还没保存,即将离开页面前是否弹框确认。 |
| columnCount | `number` | 0 | 表单项显示为几列 |
| inheritData | `boolean` | `true` | 默认表单是采用数据链的形式创建个自己的数据域,表单提交的时候只会发送自己这个数据域的数据,如果希望共用上层数据域可以设置这个属性为 false这样上层数据域的数据不需要在表单中用隐藏域或者显式映射才能发送了。 |
| static | `boolean` | | `2.4.0` 整个表单静态方式展示,详情请查看[示例页](../../../examples/form/switchDisplay) |
| staticClassName | `string` | | `2.4.0` 表单静态展示时使用的类名 |
| static | `boolean` | | `2.4.0` 整个表单静态方式展示,详情请查看[示例页](../../../examples/form/switchDisplay) |
| staticClassName | `string` | | `2.4.0` 表单静态展示时使用的类名 |
## 事件表
@ -1465,13 +1465,13 @@ Form 支持轮询初始化接口,步骤如下:
当前组件对外暴露以下特性动作,其他组件可以通过指定`actionType: 动作名称`、`componentId: 该组件id`来触发这些动作,动作配置可以通过`args: {动作配置项名称: xxx}`来配置具体的参数,详细请查看[事件动作](../../docs/concepts/event-action#触发其他组件的动作)。
| 动作名称 | 动作配置 | 说明 |
| -------- | ------------------------------ | -------------------------- |
| submit | - | 提交表单 |
| reset | - | 重置表单 |
| clear | - | 清空表单 |
| validate | - | 校验表单 |
| reload | - | 刷新(重新加载) |
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
| static | - | 表单切换为静态展示 |
| nonstatic | - | 表单切换为普通输入态 |
| 动作名称 | 动作配置 | 说明 |
| --------- | ------------------------------ | -------------------------- |
| submit | - | 提交表单 |
| reset | - | 重置表单 |
| clear | - | 清空表单 |
| validate | - | 校验表单 |
| reload | - | 刷新(重新加载) |
| setValue | `value: object` 更新的表单数据 | 更新数据,对数据进行 merge |
| static | - | 表单切换为静态展示 |
| nonstatic | - | 表单切换为普通输入态 |

View File

@ -138,11 +138,11 @@ order: 55
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
| 事件名称 | 事件参数 | 说明 |
| -------- | -------------------------------------------------------------------- | -------------------- |
| change | `[name]: string` 组件的值 | 选中值变化时触发 |
| blur | `[name]: string` 组件的值<br/>`items: object` \| `object[]` 所有选项 | 输入框失去焦点时触发 |
| focus | `[name]: string` 组件的值<br/>`items: object` \| `object[]` 所有选项 | 输入框获取焦点时触发 |
| 事件名称 | 事件参数 | 说明 |
| -------- | ----------------------------------------------------------------------------------------------------------------- | -------------------- |
| change | `[name]: string` 组件的值(多个以逗号分割)<br/>`selectedItems: Option[]` 选中的项<br/>`items: Option[]` 所有选项 | 选中值变化时触发 |
| blur | `[name]: string` 组件的值<br/>`selectedItems: Option[]` 选中的项<br/>`items: Option[]` 所有选项 | 输入框失去焦点时触发 |
| focus | `[name]: string` 组件的值<br/>`selectedItems: Option[]` 选中的项<br/>`items: Option[]` 所有选项 | 输入框获取焦点时触发 |
## 动作表

View File

@ -256,9 +256,9 @@ api 返回内容需要包含 options 字段
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
| 事件名称 | 事件参数 | 说明 |
| -------- | -------------------------------------------------------------------- | ---------------- |
| change | `[name]: string` 组件的值<br/>`items: object` \| `object[]` 选项集合 | 选中值变化时触发 |
| 事件名称 | 事件参数 | 说明 |
| -------- | --------------------------------------------------------------------------------------------- | ---------------- |
| change | `[name]: string` 组件的值<br/>`selectedItems: Option` 选中的项<br/>`items: Option[]` 选项集合 | 选中值变化时触发 |
## 动作表

View File

@ -1045,14 +1045,14 @@ leftOptions 动态加载,默认 source 接口是返回 options 部分,而 le
> `[name]`表示当前组件绑定的名称,即`name`属性,如果没有配置`name`属性,则通过`value`取值。
| 事件名称 | 事件参数 | 说明 |
| -------- | -------------------------------------------------------------------------------------------- | -------------------- |
| change | `[name]: string` 组件的值<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 选中值变化时触发 |
| blur | `[name]: string` 组件的值<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 输入框失去焦点时触发 |
| focus | `[name]: string` 组件的值<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 输入框获取焦点时触发 |
| add | `[name]: Option` 新增的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 新增选项提交时触发 |
| edit | `[name]: Option` 编辑的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 编辑选项提交时触发 |
| delete | `[name]: Option` 删除的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 删除选项提交时触发 |
| 事件名称 | 事件参数 | 说明 |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| change | `[name]: string` 组件的值<br/>`selectedItems: Option \| Option[]` 选中的项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 选中值变化时触发 |
| blur | `[name]: string` 组件的值<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 输入框失去焦点时触发 |
| focus | `[name]: string` 组件的值<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 输入框获取焦点时触发 |
| add | `[name]: Option` 新增的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 新增选项提交时触发 |
| edit | `[name]: Option` 编辑的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 编辑选项提交时触发 |
| delete | `[name]: Option` 删除的选项<br/>`items: Option[]` 选项集合(< 2.3.2 及以下版本 `options` | 删除选项提交时触发 |
## 动作表

View File

@ -286,10 +286,11 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`**、**
> `[name]`为当前数据域中的字段名,例如:当前数据域为 {username: 'amis'},则可以通过${username}获取对应的值。
| 事件名称 | 事件参数 | 说明 |
| ----------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------- |
| inited | `event.data` initApi 远程请求返回的初始化数据<br/>`[name]: any` 当前数据域中指定字段的值 | 远程初始化接口请求成功时触发 |
| pullRefresh | - | 开启下拉刷新后,下拉释放后触发(仅用于移动端) |
| 事件名称 | 事件参数 | 说明 |
| ----------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------- |
| init | - | 组件实例被创建并插入 DOM 中时触发。2.4.1 及以上版本 |
| inited | `event.data` initApi 远程请求返回的初始化数据<br/>`[name]: any` 当前数据域中指定字段的值 | 远程初始化接口请求成功时触发 |
| pullRefresh | - | 开启下拉刷新后,下拉释放后触发(仅用于移动端) |
## 动作表

View File

@ -703,10 +703,11 @@ ws.on('connection', function connection(ws) {
> `[name]`为当前数据域中的字段名,例如:当前数据域为 {username: 'amis'},则可以通过${username}获取对应的值。
| 事件名称 | 事件参数 | 说明 |
| ----------------- | ---------------------------------------------------------------------------------------- | ---------------------------------- |
| fetchInited | `event.data` api 远程请求返回的初始化数据<br/>`[name]: any` 当前数据域中指定字段的值 | 远程初始化接口请求成功时触发 |
| fetchSchemaInited | `event.data` schemaApi 远程请求返回的 UI 内容<br/>`[name]: any` 当前数据域中指定字段的值 | 远程 schemaApi UI 内容接口请求成功 |
| 事件名称 | 事件参数 | 说明 |
| ----------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------- |
| init | - | 组件实例被创建并插入 DOM 中时触发。2.4.1 及以上版本 |
| fetchInited | `event.data` api 远程请求返回的初始化数据<br/>`[name]: any` 当前数据域中指定字段的值 | 远程初始化接口请求成功时触发 |
| fetchSchemaInited | `event.data` schemaApi 远程请求返回的 UI 内容<br/>`[name]: any` 当前数据域中指定字段的值 | 远程 schemaApi UI 内容接口请求成功 |
## 动作表

View File

@ -87,9 +87,7 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
() =>
`${props.topStore.visibleState[props.schema.id || props.$path]}${
props.topStore.disableState[props.schema.id || props.$path]
}${
props.topStore.staticState[props.schema.id || props.$path]
}`,
}${props.topStore.staticState[props.schema.id || props.$path]}`,
() => this.forceUpdate()
);
}
@ -206,9 +204,10 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
async dispatchEvent(
e: React.MouseEvent<any>,
data: any
data: any,
renderer?: React.Component<RendererProps> // for didmount
): Promise<RendererEvent<any> | void> {
return await dispatchEvent(e, this.cRef, this.context, data);
return await dispatchEvent(e, this.cRef || renderer, this.context, data);
}
renderChild(

View File

@ -17,7 +17,7 @@ import cx from 'classnames';
export function getExprProperties(
schema: PlainObject,
data: object = {},
blackList: Array<string> = ['addOn'],
blackList: Array<string> = ['addOn', 'ref'],
props?: any
): PlainObject {
const exprProps: PlainObject = {};

View File

@ -106,7 +106,10 @@ test('Renderer:Page initApi error show Message', async () => {
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
expect(container.querySelector('.cxd-Alert')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -1355,6 +1358,7 @@ test('Renderer:Page initApi reload by Form submit', async () => {
await waitFor(() => {
expect(getByText(/Submit/)).toBeInTheDocument();
expect(getByText('The variable value is 1')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();

View File

@ -190,6 +190,14 @@ export default class App extends React.Component<AppProps, object> {
}
async componentDidMount() {
const {data, dispatchEvent} = this.props;
const rendererEvent = await dispatchEvent('init', data, this);
if (rendererEvent?.prevented) {
return;
}
this.reload();
}

View File

@ -236,8 +236,14 @@ export class Chart extends React.Component<ChartProps> {
props.config && this.renderChart(props.config);
}
componentDidMount() {
const {api, data, initFetch, source} = this.props;
async componentDidMount() {
const {api, data, initFetch, source, dispatchEvent} = this.props;
const rendererEvent = await dispatchEvent('init', data, this);
if (rendererEvent?.prevented) {
return;
}
if (source && isPureVariable(source)) {
const ret = resolveVariableAndFilter(source, data, '| raw');

View File

@ -273,7 +273,8 @@ export default class TagControl extends React.PureComponent<
const newValueRes = this.getValue('push', option);
const isPrevented = await this.dispatchEvent('change', {
value: newValueRes
value: newValueRes,
selectedItems: selectedOptions.concat(option)
});
isPrevented || onChange(newValueRes);
}
@ -287,7 +288,8 @@ export default class TagControl extends React.PureComponent<
const newValueRes = this.getValue('normal');
const isPrevented = await this.dispatchEvent('focus', {
value: newValueRes
value: newValueRes,
selectedItems: this.props.selectedOptions
});
isPrevented || this.props.onFocus?.(e);
}
@ -305,7 +307,8 @@ export default class TagControl extends React.PureComponent<
const newValueRes = this.normalizeMergedValue(value);
const isPrevented = await this.dispatchEvent('blur', {
value: newValueRes
value: newValueRes,
selectedItems: selectedOptions
});
isPrevented || this.props.onBlur?.(e);
@ -353,7 +356,8 @@ export default class TagControl extends React.PureComponent<
}
const isPrevented = await this.dispatchEvent('change', {
value: newValue
value: newValue,
selectedItems: value
});
isPrevented || onChange(newValue);
}
@ -369,11 +373,13 @@ export default class TagControl extends React.PureComponent<
const {selectedOptions, onChange, delimiter} = this.props;
const value = this.state.inputValue.trim();
const selectedItems = selectedOptions.concat({label: value, value});
if (selectedOptions.length && !value && evt.key == 'Backspace') {
const newValueRes = this.getValue('pop');
const isPrevented = await this.dispatchEvent('change', {
value: newValueRes
value: newValueRes,
selectedItems
});
isPrevented || onChange(newValueRes);
} else if (value && (evt.key === 'Enter' || evt.key === delimiter)) {
@ -382,7 +388,8 @@ export default class TagControl extends React.PureComponent<
const newValueRes = this.normalizeMergedValue(value);
const isPrevented = await this.dispatchEvent('change', {
value: newValueRes
value: newValueRes,
selectedItems
});
if (!this.validateInputValue(value)) {

View File

@ -63,11 +63,12 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
onChange,
dispatchEvent,
options,
data
selectedOptions
} = this.props;
let value = option;
if (option && (joinValues || extractValue)) {
option = option[valueField || 'value'];
value = option[valueField || 'value'];
}
const rendererEvent = await dispatchEvent(
@ -75,9 +76,10 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
resolveEventData(
this.props,
{
value: option,
value,
options,
items: options // 为了保持名字统一
items: options, // 为了保持名字统一
selectedItems: option
},
'value'
)
@ -86,7 +88,7 @@ export default class RadiosControl extends React.Component<RadiosProps, any> {
return;
}
onChange && onChange(option);
onChange && onChange(value);
}
reload() {

View File

@ -251,7 +251,9 @@ export default class SelectControl extends React.Component<SelectProps, any> {
async dispatchEvent(eventName: SelectRendererEvent, eventData: any = {}) {
const event = 'on' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
const {dispatchEvent, options, data} = this.props;
const {dispatchEvent, options, data, multiple, selectedOptions} =
this.props;
// 触发渲染器事件
const rendererEvent = await dispatchEvent(
eventName,
@ -262,7 +264,8 @@ export default class SelectControl extends React.Component<SelectProps, any> {
items: options, // 为了保持名字统一
value: ['onEdit', 'onDelete'].includes(event)
? eventData
: eventData && eventData.value
: eventData && eventData.value,
selectedItems: multiple ? selectedOptions : selectedOptions[0]
},
'value'
)
@ -293,7 +296,8 @@ export default class SelectControl extends React.Component<SelectProps, any> {
{
value: newValue,
options,
items: options // 为了保持名字统一
items: options, // 为了保持名字统一
selectedItems: value
},
'value'
)

View File

@ -376,12 +376,33 @@ export default class Page extends React.Component<PageProps> {
}
}
componentDidMount() {
const {initApi, initFetch, initFetchOn, store, messages, asideSticky} =
this.props;
async componentDidMount() {
const {
initApi,
initFetch,
initFetchOn,
store,
messages,
asideSticky,
data,
dispatchEvent
} = this.props;
this.mounted = true;
if (asideSticky && this.asideInner.current) {
const dom = this.asideInner.current!;
dom.style.cssText += `position: sticky; top: ${
scrollPosition(dom).top
}px;`;
}
const rendererEvent = await dispatchEvent('init', data, this);
if (rendererEvent?.prevented) {
return;
}
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
store
.fetchInitData(initApi, store.data, {
@ -390,13 +411,6 @@ export default class Page extends React.Component<PageProps> {
})
.then(this.initInterval);
}
if (asideSticky && this.asideInner.current) {
const dom = this.asideInner.current!;
dom.style.cssText += `position: sticky; top: ${
scrollPosition(dom).top
}px;`;
}
}
componentDidUpdate(prevProps: PageProps) {

View File

@ -182,8 +182,15 @@ export default class Service extends React.Component<ServiceProps> {
this.dataProviderSetData = this.dataProviderSetData.bind(this);
}
componentDidMount() {
async componentDidMount() {
const {data, dispatchEvent} = this.props;
this.mounted = true;
const rendererEvent = await dispatchEvent('init', data, this);
if (rendererEvent?.prevented) {
return;
}
this.initFetch();
}