Merge branch 'master' into fix-service

This commit is contained in:
lvxiaojiao 2023-07-21 11:02:21 +08:00
commit a0a1f971f4
19 changed files with 718 additions and 107 deletions

View File

@ -98,7 +98,13 @@ import Overlay from './components/Overlay';
import PopOver from './components/PopOver';
import {FormRenderer} from './renderers/Form';
import type {FormHorizontal, FormSchemaBase} from './renderers/Form';
import {enableDebug, promisify, replaceText, wrapFetcher} from './utils/index';
import {
enableDebug,
disableDebug,
promisify,
replaceText,
wrapFetcher
} from './utils/index';
import type {OnEventProps} from './utils/index';
import {valueMap as styleMap} from './utils/style-helper';
import {RENDERER_TRANSMISSION_OMIT_PROPS} from './SchemaRenderer';
@ -195,7 +201,9 @@ export {
OnEventProps,
FormSchemaBase,
filterTarget,
CustomStyle
CustomStyle,
enableDebug,
disableDebug
};
export function render(
@ -257,13 +265,6 @@ function AMISRenderer({
translate
} as any;
if (options.enableAMISDebug) {
// 因为里面还有 render
setTimeout(() => {
enableDebug();
}, 10);
}
store = RendererStore.create({}, options);
stores[options.session || 'global'] = store;
} else {
@ -291,6 +292,11 @@ function AMISRenderer({
}
env.theme = getTheme(theme);
React.useEffect(() => {
env.enableAMISDebug ? enableDebug() : disableDebug();
return () => env.enableAMISDebug || disableDebug();
}, [env.enableAMISDebug]);
if (props.locale !== undefined) {
env.translate = translate;
env.locale = locale;

View File

@ -223,7 +223,10 @@ export interface ApiObject extends BaseApiObject {
api: ApiObject,
context: any
) => any;
requestAdaptor?: (api: ApiObject, context: any) => ApiObject;
requestAdaptor?: (
api: ApiObject,
context: any
) => ApiObject | Promise<ApiObject>;
/** 是否过滤为空字符串的 query 参数 */
filterEmptyQuery?: boolean;
}

View File

@ -76,7 +76,7 @@ export function buildApi(
}
if (api.requestAdaptor && typeof api.requestAdaptor === 'string') {
api.requestAdaptor = str2function(
api.requestAdaptor = str2AsyncFunction(
api.requestAdaptor,
'api',
'context'
@ -84,7 +84,7 @@ export function buildApi(
}
if (api.adaptor && typeof api.adaptor === 'string') {
api.adaptor = str2function(
api.adaptor = str2AsyncFunction(
api.adaptor,
'payload',
'response',
@ -464,12 +464,16 @@ export function wrapFetcher(
return fn as any;
}
const wrappedFetcher = function (api: Api, data: object, options?: object) {
const wrappedFetcher = async function (
api: Api,
data: object,
options?: object
) {
api = buildApi(api, data, options) as ApiObject;
if (api.requestAdaptor) {
debug('api', 'before requestAdaptor', api);
api = api.requestAdaptor(api, data) || api;
api = (await api.requestAdaptor(api, data)) || api;
debug('api', 'after requestAdaptor', api);
}

View File

@ -2,9 +2,10 @@
* amis amis
*/
import React, {Component, useEffect, useRef, useState} from 'react';
import React, {Component, useEffect, useRef, useState, version} from 'react';
import cx from 'classnames';
import {findDOMNode, render} from 'react-dom';
import {findDOMNode, render, unmountComponentAtNode} from 'react-dom';
import {createRoot} from 'react-dom/client';
import {autorun, observable} from 'mobx';
import {observer} from 'mobx-react';
import {uuidv4} from './helper';
@ -84,16 +85,18 @@ const LogView = observer(({store}: {store: AMISDebugStore}) => {
[{log.cat}] {log.msg}
</div>
{log.ext ? (
<JsonView
name={null}
theme="monokai"
src={JSON.parse(log.ext)}
collapsed={true}
enableClipboard={false}
displayDataTypes={false}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle="square"
/>
<React.Suspense fallback={<div>Loading...</div>}>
<JsonView
name={null}
theme="monokai"
src={JSON.parse(log.ext)}
collapsed={true}
enableClipboard={false}
displayDataTypes={false}
collapseStringsAfterLength={ellipsisThreshold}
iconStyle="square"
/>
</React.Suspense>
) : null}
</div>
);
@ -126,16 +129,18 @@ const AMISDebug = observer(({store}: {store: AMISDebugStore}) => {
stackDataView.push(
<div key={`data-${level}`}>
<h3>Data Level-{level}</h3>
<JsonView
key={`dataview-${stack}`}
name={null}
theme="monokai"
src={stack}
collapsed={level === 0 ? false : true}
enableClipboard={false}
displayDataTypes={false}
iconStyle="square"
/>
<React.Suspense fallback={<div>Loading...</div>}>
<JsonView
key={`dataview-${stack}`}
name={null}
theme="monokai"
src={stack}
collapsed={level === 0 ? false : true}
enableClipboard={false}
displayDataTypes={false}
iconStyle="square"
/>
</React.Suspense>
</div>
);
level += 1;
@ -319,7 +324,7 @@ function handleMouseclick(e: MouseEvent) {
}
const dom = e.target as HTMLElement;
const target = dom.closest(`[data-debug-id]`);
if (target) {
if (target && !target.closest('.AMISDebug')) {
store.activeId = target.getAttribute('data-debug-id')!;
store.tab = 'inspect';
}
@ -366,6 +371,7 @@ autorun(() => {
// 页面中只能有一个实例
let isEnabled = false;
let unmount: () => void;
export function enableDebug() {
if (isEnabled) {
@ -376,7 +382,21 @@ export function enableDebug() {
const amisDebugElement = document.createElement('div');
document.body.appendChild(amisDebugElement);
const element = <AMISDebug store={store} />;
render(element, amisDebugElement);
if (parseInt(version.split('.')[0], 10) >= 18) {
const root = createRoot(amisDebugElement);
root.render(element);
unmount = () => {
root.unmount();
document.body.removeChild(amisDebugElement);
};
} else {
render(element, amisDebugElement);
unmount = () => {
unmountComponentAtNode(amisDebugElement);
document.body.removeChild(amisDebugElement);
};
}
document.body.appendChild(amisHoverBox);
document.body.appendChild(amisActiveBox);
@ -384,6 +404,18 @@ export function enableDebug() {
document.addEventListener('click', handleMouseclick);
}
export function disableDebug() {
if (!isEnabled) {
return;
}
isEnabled = false;
unmount?.();
document.body.removeChild(amisHoverBox);
document.body.removeChild(amisActiveBox);
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('click', handleMouseclick);
}
interface DebugWrapperProps {
renderer: any;
children?: React.ReactNode;

View File

@ -40,6 +40,7 @@
&-tab {
overflow: hidden;
border-bottom: 1px solid #3d3d3d;
}
&-tab > button {
@ -90,6 +91,7 @@
&-content {
pointer-events: all;
display: none;
height: 100%;
}
&-resize {
@ -122,7 +124,7 @@
&.is-expanded {
width: 420px;
overflow: auto;
background: #272821;
color: #cccccc;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
@ -168,6 +170,37 @@
padding: var(--gap-sm);
}
&-log,
&-inspect {
height: 100%;
overflow: auto;
// 火狐浏览器
scrollbar-width: thin;
scrollbar-color: #6b6b6b #2b2b2b;
&::-webkit-scrollbar {
position: relative;
z-index: 10;
background-color: #2c2c2c;
width: 16px;
height: 16px;
border-left: 1px solid #3d3d3d;
// border-top: 1px solid #3d3d3d;
}
&::-webkit-scrollbar-thumb {
background: #6b6b6b;
background-clip: content-box;
border: 4px solid transparent;
border-radius: 500px;
&:hover {
background: #939393;
background-clip: content-box;
}
}
}
&-logLine {
overflow-x: hidden;
}

View File

@ -89,11 +89,20 @@
}
&-body {
padding: var(--drawer-content-paddingTop) var(--drawer-content-paddingRight)
padding: 0 var(--drawer-content-paddingRight)
var(--drawer-content-paddingBottom) var(--drawer-content-paddingLeft);
flex-basis: 0;
flex-grow: 1;
overflow: auto;
// 因为如果成员里面有 position:sticky 的内容
// padding 会导致位置不正确
// 所以改成这种写法
&:before {
content: '';
display: block;
height: var(--drawer-content-paddingTop);
}
}
&-footer {

View File

@ -350,6 +350,7 @@
&-submenu-title {
display: flex;
align-items: center;
justify-content: space-between;
.#{$ns}Nav-Menu-item-wrap {

View File

@ -3,7 +3,7 @@
* @author fex
*/
import React from 'react';
import React, {version} from 'react';
import {render} from 'react-dom';
import Modal from './Modal';
import Button from './Button';
@ -11,6 +11,7 @@ import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import Html from './Html';
import type {PlainObject} from 'amis-core';
import {createRoot} from 'react-dom/client';
export interface AlertProps extends ThemeProps, LocaleProps {
container?: any;
confirmText?: string;
@ -52,13 +53,21 @@ export interface AlertState {
export class Alert extends React.Component<AlertProps, AlertState> {
static instance: any = null;
static getInstance() {
static async getInstance() {
if (!Alert.instance) {
console.warn('Alert 组件应该没有被渲染,所以隐性的渲染到 body 了');
const container = document.body;
const div = document.createElement('div');
container.appendChild(div);
render(<FinnalAlert />, div);
if (parseInt(version.split('.')[0], 10) >= 18) {
const root = createRoot(div);
await new Promise<void>(resolve =>
root.render(<FinnalAlert ref={() => resolve()} />)
);
} else {
render(<FinnalAlert />, div);
}
}
return Alert.instance;
@ -346,23 +355,35 @@ function renderForm(
return renderSchemaFn?.(controls, value, callback, scopeRef, theme);
}
export const alert: (content: string, title?: string) => void = (
export const alert: (content: string, title?: string) => Promise<void> = async (
content,
title
) => Alert.getInstance().alert(content, title);
) => {
const instance = await Alert.getInstance();
return instance.alert(content, title);
};
export const confirm: (
content: string | React.ReactNode,
title?: string,
optionsOrCofnrimText?: string | ConfirmOptions,
cancelText?: string
) => Promise<any> = (content, title, optionsOrCofnrimText, cancelText) =>
Alert.getInstance().confirm(content, title, optionsOrCofnrimText, cancelText);
) => Promise<any> = async (
content,
title,
optionsOrCofnrimText,
cancelText
) => {
const instance = await Alert.getInstance();
return instance.confirm(content, title, optionsOrCofnrimText, cancelText);
};
export const prompt: (
controls: any,
defaultvalue?: any,
title?: string,
confirmText?: string
) => Promise<any> = (controls, defaultvalue, title, confirmText) =>
Alert.getInstance().prompt(controls, defaultvalue, title, confirmText);
) => Promise<any> = async (controls, defaultvalue, title, confirmText) => {
const instance = await Alert.getInstance();
return instance.prompt(controls, defaultvalue, title, confirmText);
};
export const FinnalAlert = themeable(localeable(Alert));
export default FinnalAlert;

View File

@ -1,5 +1,5 @@
import {ClassNamesFn, themeable} from 'amis-core';
import React from 'react';
import React, {version} from 'react';
import {render} from 'react-dom';
import {autobind, calculatePosition} from 'amis-core';
import Transition, {
@ -7,6 +7,7 @@ import Transition, {
ENTERING,
EXITING
} from 'react-transition-group/Transition';
import {createRoot} from 'react-dom/client';
const fadeStyles: {
[propName: string]: string;
} = {
@ -49,12 +50,20 @@ export class ContextMenu extends React.Component<
ContextMenuState
> {
static instance: any = null;
static getInstance() {
static async getInstance() {
if (!ContextMenu.instance) {
const container = document.body;
const div = document.createElement('div');
container.appendChild(div);
render(<ThemedContextMenu />, div);
if (parseInt(version.split('.')[0], 10) >= 18) {
const root = createRoot(div);
await new Promise<void>(resolve =>
root.render(<ThemedContextMenu ref={() => resolve()} />)
);
} else {
render(<ThemedContextMenu />, div);
}
}
return ContextMenu.instance;
@ -309,5 +318,7 @@ export function openContextMenus(
menus: Array<MenuItem | MenuDivider>,
onClose?: () => void
) {
return ContextMenu.getInstance().openContextMenus(info, menus, onClose);
return ContextMenu.getInstance().then(instance =>
instance.openContextMenus(info, menus, onClose)
);
}

View File

@ -75,7 +75,7 @@ exports[`doAction:service reload 1`] = `
placeholder=""
size="10"
type="text"
value="Amis Renderer"
value="amis"
/>
</div>
</div>
@ -321,7 +321,7 @@ exports[`doAction:service reload 2`] = `
placeholder=""
size="10"
type="text"
value="Amis Renderer"
value="amis"
/>
</div>
</div>

View File

@ -342,7 +342,7 @@ test('Renderers:Action tooltip', async () => {
// });
// 14. confirmText
test('Renderers:Action with confirmText & actionType ajax', () => {
test('Renderers:Action with confirmText & actionType ajax', async () => {
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
@ -372,7 +372,7 @@ test('Renderers:Action with confirmText & actionType ajax', () => {
)
);
fireEvent.click(container.querySelector('.cxd-Button'));
wait(500);
await wait(500);
expect(baseElement).toMatchSnapshot();
expect(baseElement.querySelector('.cxd-Modal-content')!).toHaveTextContent(
@ -380,14 +380,16 @@ test('Renderers:Action with confirmText & actionType ajax', () => {
);
fireEvent.click(getByText('取消'));
wait(500);
await wait(500);
expect(fetcher).not.toBeCalled();
// fireEvent.click(container.querySelector('.cxd-Button'));
// wait(500);
// fireEvent.click(getByText('确认'));
// fetcher 不生效
// expect(fetcher).toBeCalled();
fireEvent.click(container.querySelector('.cxd-Button'));
await wait(500);
fireEvent.click(getByText('确认'));
await wait(200);
// fetcher 该被执行了
expect(fetcher).toBeCalled();
});
// 15.Action 作为容器组件

View File

@ -136,13 +136,15 @@ test('Renderer: input-table with default value column', async () => {
await wait(200);
expect(onSubmitCallbackFn).toHaveBeenCalledTimes(1);
expect(onSubmitCallbackFn.mock.calls[0][0]).toEqual({
table: [
{a: 'a1', b: 'b1', c: 'a1'},
{a: 'a2', b: 'b2', c: 'a2'},
{a: 'a3', b: 'b3', c: 'a3'}
]
});
expect(onSubmitCallbackFn.mock.calls[0][0]).toEqual(
expect.objectContaining({
table: [
{a: 'a1', b: 'b1', c: 'a1'},
{a: 'a2', b: 'b2', c: 'a2'},
{a: 'a3', b: 'b3', c: 'a3'}
]
})
);
}, 10000);
test('Renderer:input table add', async () => {

View File

@ -517,3 +517,181 @@ test('Renderer:Nav with itemActions', async () => {
expect(container).toMatchSnapshot();
expect(getByText('编辑')).toBeInTheDocument();
});
// 8.各种图标展示
test('Renderer:Nav with icons', async () => {
const {container} = render(
amisRender(
{
type: 'page',
body: {
type: 'nav',
stacked: true,
links: [
{
label: 'Nav 1',
to: '?cat=1',
value: '1',
icon: 'fa fa-user',
__id: 1
},
{
label: 'Nav 2',
__id: 2,
unfolded: true,
children: [
{
__id: 2.1,
label: 'Nav 2-1',
icon: [
{
icon: 'star',
position: 'before'
},
{
icon: 'search',
position: 'before'
},
{
icon: 'https://suda.cdn.bcebos.com/images%2F2021-01%2Fdiamond.svg',
position: 'after'
}
],
children: [
{
label: 'Nav 2-1-1',
to: '?cat=2-1',
value: '2-1',
__id: 2.11
}
]
}
]
}
]
}
},
{},
makeEnv({})
)
);
expect(container).toMatchSnapshot();
expect(container.querySelectorAll('.fa-user').length).toBe(1);
expect(container.querySelectorAll('[icon=search]').length).toBe(1);
expect(container.querySelectorAll('img').length).toBe(1);
});
// 9.Nav在Dialog里
test('Renderer:Nav with Dialog', async () => {
const {container, getByText} = render(
amisRender(
{
type: 'page',
body: {
type: 'button',
label: '点击弹框',
actionType: 'dialog',
dialog: {
title: '弹框',
body: [
{
type: 'nav',
stacked: true,
className: 'w-md',
draggable: true,
saveOrderApi: '/api/options/nav',
source: '/api/options/nav?parentId=${value}',
itemActions: [
{
type: 'icon',
icon: 'cloud',
visibleOn: "this.to === '?cat=1'"
},
{
type: 'dropdown-button',
level: 'link',
icon: 'fa fa-ellipsis-h',
hideCaret: true,
buttons: [
{
type: 'button',
label: '编辑'
},
{
type: 'button',
label: '删除'
}
]
}
],
links: [
{
label: 'Nav 1',
to: '?cat=1',
value: '1',
icon: 'fa fa-user',
__id: 1
},
{
label: 'Nav 2',
__id: 2,
unfolded: true,
children: [
{
__id: 2.1,
label: 'Nav 2-1',
children: [
{
label: 'Nav 2-1-1',
to: '?cat=2-1',
value: '2-1',
__id: 2.11
}
]
},
{
label: 'Nav 2-2',
to: '?cat=2-2',
value: '2-2',
__id: 2.2
}
]
},
{
label: 'Nav 3',
to: '?cat=3',
value: '3',
defer: true,
__id: 3
}
]
}
]
}
}
},
{},
makeEnv({
getModalContainer: () => container
})
)
);
expect(container).toMatchSnapshot();
fireEvent.click(getByText('点击弹框'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
});
fireEvent.click(
container.querySelector(
'[role="dialog"] .cxd-Nav-Menu-item-extra .cxd-Button'
)!
);
await waitFor(() => {
expect(
container.querySelector('[role="dialog"] .cxd-PopOver')
).toBeInTheDocument();
});
});

View File

@ -208,6 +208,228 @@ exports[`Renderer:Nav 1`] = `
</div>
`;
exports[`Renderer:Nav with Dialog 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<button
class="cxd-Button cxd-Button--default cxd-Button--size-default"
type="button"
>
<span>
点击弹框
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Nav with icons 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
role="page-body"
>
<div
class="cxd-Nav"
>
<ul
class="cxd-Nav-Menu cxd-Nav-Menu-root cxd-Nav-Menu-inline cxd-Nav-Menu-ltr cxd-Nav-Menu-light cxd-Nav-Menu-expand-before"
data-menu-list="true"
dir="ltr"
role="menu"
tabindex="0"
>
<ul
class="cxd-Nav-Menu-item-tooltip-wrap"
style="order: 0;"
>
<li
aria-disabled="false"
class="cxd-Nav-Menu-item"
data-menu-id="rc-menu-uuid-test-1"
role="menuitem"
style="padding-left: 16px;"
tabindex="-1"
>
<div
class="cxd-Nav-Menu-item-wrap"
>
<a
class="cxd-Nav-Menu-item-link"
data-depth="1"
data-id="1"
title="Nav 1"
>
<i
class="cxd-Nav-Menu-item-icon"
>
<i
class="fa fa-user fa fa-user"
/>
</i>
<span
class="cxd-Nav-Menu-item-label"
title="Nav 1"
>
Nav 1
</span>
</a>
</div>
</li>
</ul>
<li
class="cxd-Nav-Menu-submenu cxd-Nav-Menu-submenu-inline cxd-Nav-Menu-submenu cxd-Nav-Menu-submenu-open"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-2-popup"
aria-expanded="true"
aria-haspopup="true"
class="cxd-Nav-Menu-submenu-title"
data-menu-id="rc-menu-uuid-test-2"
role="menuitem"
style="padding-left: 16px;"
tabindex="-1"
>
<div
class="cxd-Nav-Menu-item-wrap"
>
<a
class="cxd-Nav-Menu-item-link"
data-depth="1"
data-id="2"
>
<span
class="cxd-Nav-Menu-item-label cxd-Nav-Menu-item-label-subTitle"
title="Nav 2"
>
Nav 2
</span>
</a>
</div>
<span
class="cxd-Nav-Menu-submenu-arrow"
>
<icon-mock
classname="icon icon-right-arrow-bold"
icon="right-arrow-bold"
/>
</span>
</div>
<ul
class="cxd-Nav-Menu cxd-Nav-Menu-sub cxd-Nav-Menu-inline"
data-menu-list="true"
id="rc-menu-uuid-test-2-popup"
role="menu"
>
<li
class="cxd-Nav-Menu-submenu cxd-Nav-Menu-submenu-inline cxd-Nav-Menu-submenu"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-3-popup"
aria-expanded="false"
aria-haspopup="true"
class="cxd-Nav-Menu-submenu-title"
data-menu-id="rc-menu-uuid-test-3"
role="menuitem"
style="padding-left: 32px;"
tabindex="-1"
>
<div
class="cxd-Nav-Menu-item-wrap"
>
<a
class="cxd-Nav-Menu-item-link"
data-depth="2"
data-id="2.1"
>
<i
class="cxd-Nav-Menu-item-icon"
>
<icon-mock
classname="icon-star"
icon="star"
/>
<icon-mock
classname="icon-search"
icon="search"
/>
</i>
<span
class="cxd-Nav-Menu-item-label cxd-Nav-Menu-item-label-subTitle"
title="Nav 2-1"
>
Nav 2-1
</span>
<i
class="cxd-Nav-Menu-item-icon-after"
>
<img
class="cxd-Icon"
src="https://suda.cdn.bcebos.com/images%2F2021-01%2Fdiamond.svg"
/>
</i>
</a>
</div>
<span
class="cxd-Nav-Menu-submenu-arrow"
>
<icon-mock
classname="icon icon-right-arrow-bold"
icon="right-arrow-bold"
/>
</span>
</div>
</li>
</ul>
</li>
</ul>
<div
aria-hidden="true"
style="display: none;"
>
<ul
class="cxd-Nav-Menu-item-tooltip-wrap"
style="order: 0;"
/>
<ul
class="cxd-Nav-Menu-item-tooltip-wrap"
style="order: 0;"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Nav with itemActions 1`] = `
<div>
<div

View File

@ -1,6 +1,6 @@
import {render as amisRender} from '../../src';
import {wait, makeEnv} from '../helper';
import {render, fireEvent, cleanup} from '@testing-library/react';
import {render, fireEvent, cleanup, waitFor} from '@testing-library/react';
import {buildApi, isApiOutdated, isValidApi} from 'amis-core';
test('api:buildApi', () => {
@ -358,3 +358,74 @@ test('api:isvalidapi', () => {
)
).toBeTruthy();
});
test('api:requestAdaptor', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
data: {
id: 1
}
}
})
);
const requestAdaptor = jest.fn().mockImplementation(api => {
return Promise.resolve({
...api,
data: {
...api.data,
email: 'appended@test.com'
}
});
});
const {container, getByText} = render(
amisRender(
{
type: 'page',
body: [
{
type: 'form',
id: 'form_submit',
submitText: '提交表单',
api: {
method: 'post',
url: '/api/mock2/form/saveForm',
requestAdaptor: requestAdaptor
},
body: [
{
type: 'input-text',
name: 'name',
label: '姓名:',
value: 'fex'
}
]
}
]
},
{},
makeEnv({
notify,
fetcher
})
)
);
await waitFor(() => {
expect(getByText('提交表单')).toBeInTheDocument();
});
fireEvent.click(getByText(/提交表单/));
await wait(300);
expect(requestAdaptor).toHaveBeenCalled();
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0].data).toMatchObject({
name: 'fex',
email: 'appended@test.com'
});
});

View File

@ -465,6 +465,10 @@ export default class Dialog extends React.Component<DialogProps> {
syncLocation: false // 弹框中的 crud 一般不需要同步地址栏
};
if (this.props.size === 'full') {
subProps.affixOffsetTop = 0;
}
if (!(body as Schema).type) {
return render(`body${key ? `/${key}` : ''}`, body, subProps);
}

View File

@ -465,7 +465,8 @@ export default class Drawer extends React.Component<DrawerProps> {
onInit: this.handleFormInit,
onSaved: this.handleFormSaved,
onActionSensor: this.handleActionSensor,
syncLocation: false
syncLocation: false,
affixOffsetTop: 0
};
if (schema.type === 'form') {

View File

@ -1,6 +1,7 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import isEqual from 'lodash/isEqual';
import isString from 'lodash/isString';
import {
Renderer,
RendererEnv,
@ -27,7 +28,7 @@ import {
} from 'amis-core';
import {isEffectiveApi} from 'amis-core';
import {themeable, ThemeProps} from 'amis-core';
import {Icon, getIcon, SpinnerExtraProps} from 'amis-ui';
import {Icon, SpinnerExtraProps} from 'amis-ui';
import {BadgeObject} from 'amis-ui';
import {RemoteOptionsProps, withRemoteConfig} from 'amis-ui';
import {Spinner, Menu} from 'amis-ui';
@ -143,11 +144,6 @@ export interface NavOverflow {
*
*/
style?: React.CSSProperties;
/**
* DOM挂载点
*/
popOverContainer?: any;
}
/**
@ -319,6 +315,10 @@ export interface NavigationProps
data: Object;
reload?: any;
overflow?: NavOverflow;
/**
* DOM挂载点
*/
popOverContainer?: () => HTMLElement;
}
export interface IDropInfo {
@ -550,6 +550,8 @@ export class Navigation extends React.Component<
mode,
itemActions,
render,
popOverContainer,
env,
classnames: cx,
data
} = this.props;
@ -562,32 +564,28 @@ export class Navigation extends React.Component<
}
return links.map((link: Link) => {
let beforeIcon = null;
let afterIcon = null;
if (Array.isArray(link.icon)) {
beforeIcon = link.icon
.filter(item => item.position === 'before')
.map(item => {
const beforeIcon: Array<any> = [];
const afterIcon: Array<any> = [];
link.icon &&
(Array.isArray(link.icon) ? link.icon : [link.icon]).forEach(
(item, i) => {
if (React.isValidElement(item)) {
return item;
beforeIcon.push(item);
} else if (isString(item)) {
beforeIcon.push(<Icon key={`icon-${i}`} cx={cx} icon={item} />);
} else if (item && isObject(item)) {
const icon = (
<Icon key={`icon-${i}`} cx={cx} icon={item['icon']} />
);
if (item['position'] === 'after') {
afterIcon.push(icon);
} else {
beforeIcon.push(icon);
}
}
return <Icon cx={cx} icon={link.icon} />;
});
afterIcon = link.icon
.filter(item => item.position === 'after')
.map(item => {
if (React.isValidElement(item)) {
return item;
}
return <Icon cx={cx} icon={item.icon} />;
});
} else if (link.icon) {
if (React.isValidElement(link.icon)) {
beforeIcon = link.icon;
} else {
beforeIcon = <Icon cx={cx} icon={link.icon} />;
}
}
}
);
const label =
typeof link.label === 'string'
@ -642,10 +640,10 @@ export class Navigation extends React.Component<
return {
link,
label,
labelExtra: afterIcon ? (
labelExtra: afterIcon.length ? (
<i className={cx('Nav-Menu-item-icon-after')}>{afterIcon}</i>
) : null,
icon: beforeIcon ? <i>{beforeIcon}</i> : null,
icon: beforeIcon.length ? <i>{beforeIcon}</i> : null,
children: children
? this.normalizeNavigations(children, depth + 1)
: [],
@ -654,7 +652,11 @@ export class Navigation extends React.Component<
extra: itemActions
? render('inline', itemActions, {
data: createObject(data, link),
popOverContainer: () => document.body,
popOverContainer: popOverContainer
? popOverContainer
: env.getModalContainer
? env.getModalContainer
: () => document.body,
// 点击操作之后 就关闭 因为close方法里执行了preventDefault
closeOnClick: true
})
@ -693,7 +695,9 @@ export class Navigation extends React.Component<
popupClassName,
disabled,
id,
render
render,
popOverContainer,
env
} = this.props;
const {dropIndicator} = this.state;
@ -796,6 +800,13 @@ export class Navigation extends React.Component<
data={data}
disabled={disabled}
onDragStart={this.handleDragStart}
popOverContainer={
popOverContainer
? popOverContainer
: env.getModalContainer
? env.getModalContainer
: () => document.body
}
></Menu>
) : null}
<Spinner show={!!loading} overlay loadingConfig={loadingConfig} />

View File

@ -272,7 +272,7 @@ export class TableBody extends React.Component<TableBodyProps> {
return (
<Com
key={index}
colSpan={item.colSpan}
colSpan={item.colSpan == 1 ? undefined : item.colSpan}
className={item.cellClassName}
>
{render(`summary-row/${index}`, item, {