Browse Source

feat: modal declarative promise auto update loading (#1373)

* feat: modal declarative promise auto update loading
代强 2 years ago
parent
commit
f70d554bc7

+ 37 - 37
content/show/modal/index.md

@@ -569,44 +569,44 @@ function Demo(props = {}) {
 
 ### Modal
 
-| 属性 | 说明 | 类型 | 默认值 |
-| --- | --- | --- | --- |
-| afterClose | 对话框完全关闭后的回调函数 <br/>**v1.16.0 后提供** | () => void | 无 |
-| bodyStyle | 对话框内容的样式 | CSSProperties | 无 |
-| cancelButtonProps | 取消按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
-| cancelText | 取消按钮的文字 | string | 无 |
-| centered | 是否居中显示 | boolean | false |
-| className | 可用于设置样式类名 | string | 无 |
-| closable | 是否显示右上角的关闭按钮 | boolean | true |
-| closeIcon | 关闭按钮的 icon <br/>**v1.0.0 后提供** | ReactNode | <IconClose /\> |
-| closeOnEsc | 允许通过键盘事件 Esc 触发关闭 <br/>**v1.0.0 后提供** | boolean | true |
-| confirmLoading | 确认按钮 loading | boolean | false |
-| content | 对话框内容 | ReactNode | 无 |
-| footer | 对话框底部 | ReactNode | 无 |
-| fullScreen | 对话是否是全屏(会覆盖 width height) <br/>**v1.18.0 后提供** | boolean | false |
-| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` <br/>**v0.33.0 后提供** | () => HTMLElement | () => document.body |
-| hasCancel | 是否显示取消按钮 | boolean | true |
-| header | 对话框头部 | ReactNode | 无 |
-| height | 高度 | number | 无 |
-| icon | 自定义 icon <br/>**v1.1.0 后提供** | ReactNode | - |
-| keepDOM | 关闭对话框时是否销毁 <br/>**v1.0.0 后提供** | boolean | false |
-| lazyRender | 配合 keepDOM 使用,为 true 时挂载时不会渲染对话框组件 <br/>**v1.0.0 后提供** | boolean | true |
-| mask | 是否显示遮罩 | boolean | true |
-| maskClosable | 是否允许通过点击遮罩来关闭对话框 | boolean | true |
-| maskStyle | 遮罩的样式 | CSSProperties | 无 |
-| motion | 动画效果开关 | boolean | true |
-| okButtonProps | 确认按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
-| okText | 确认按钮的文字 | string | 无 |
-| okType | 确认按钮的类型, 可选: 'primary'、'secondary'、'tertiary'、'warning'、'danger' | string | primary |
-| preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法,不包含用户传入的组件 | boolean |  |  |
+| 属性 | 说明                                                                                                        | 类型 | 默认值 |
+| --- |-----------------------------------------------------------------------------------------------------------| --- | --- |
+| afterClose | 对话框完全关闭后的回调函数 <br/>**v1.16.0 后提供**                                                                        | () => void | 无 |
+| bodyStyle | 对话框内容的样式                                                                                                  | CSSProperties | 无 |
+| cancelButtonProps | 取消按钮的 props                                                                                               | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| cancelText | 取消按钮的文字                                                                                                   | string | 无 |
+| centered | 是否居中显示                                                                                                    | boolean | false |
+| className | 可用于设置样式类名                                                                                                 | string | 无 |
+| closable | 是否显示右上角的关闭按钮                                                                                              | boolean | true |
+| closeIcon | 关闭按钮的 icon <br/>**v1.0.0 后提供**                                                                            | ReactNode | <IconClose /\> |
+| closeOnEsc | 允许通过键盘事件 Esc 触发关闭 <br/>**v1.0.0 后提供**                                                                     | boolean | true |
+| confirmLoading | 确认按钮 loading                                                                                              | boolean | false |
+| content | 对话框内容                                                                                                     | ReactNode | 无 |
+| footer | 对话框底部                                                                                                     | ReactNode | 无 |
+| fullScreen | 对话是否是全屏(会覆盖 width height) <br/>**v1.18.0 后提供**                                                            | boolean | false |
+| getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` <br/>**v0.33.0 后提供**                                 | () => HTMLElement | () => document.body |
+| hasCancel | 是否显示取消按钮                                                                                                  | boolean | true |
+| header | 对话框头部                                                                                                     | ReactNode | 无 |
+| height | 高度                                                                                                        | number | 无 |
+| icon | 自定义 icon <br/>**v1.1.0 后提供**                                                                              | ReactNode | - |
+| keepDOM | 关闭对话框时是否销毁 <br/>**v1.0.0 后提供**                                                                            | boolean | false |
+| lazyRender | 配合 keepDOM 使用,为 true 时挂载时不会渲染对话框组件 <br/>**v1.0.0 后提供**                                                    | boolean | true |
+| mask | 是否显示遮罩                                                                                                    | boolean | true |
+| maskClosable | 是否允许通过点击遮罩来关闭对话框                                                                                          | boolean | true |
+| maskStyle | 遮罩的样式                                                                                                     | CSSProperties | 无 |
+| motion | 动画效果开关                                                                                                    | boolean | true |
+| okButtonProps | 确认按钮的 props                                                                                               | [ButtonProps](/zh-CN/input/button#API参考) | 无 |
+| okText | 确认按钮的文字                                                                                                   | string | 无 |
+| okType | 确认按钮的类型, 可选: 'primary'、'secondary'、'tertiary'、'warning'、'danger'                                          | string | primary |
+| preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法,不包含用户传入的组件                                                         | boolean |  |  |
 | size | 对话框宽度尺寸,支持 `small`(448px), `medium`(684px), `large`(920px),`full-width`(100vw - 64px) <br/>**v1.0.0 后提供** | string | 'small' |
-| style | 可用于设置样式 | CSSProperties | 无 |
-| title | 对话框的标题 | ReactNode | 无 |
-| visible | 对话框是否可见 | boolean | false |
-| width | 宽度 | number | 448 |
-| zIndex | 遮罩的 z-index 值 | number | 1000 |
-| onCancel | 取消对话框时的回调函数 | (e: any) => void \| Promise<any\> | 无 |
-| onOk | 点击确认按钮时的回调函数 | (e: any) => void \| Promise<any\> | 无 |
+| style | 可用于设置样式                                                                                                   | CSSProperties | 无 |
+| title | 对话框的标题                                                                                                    | ReactNode | 无 |
+| visible | 对话框是否可见                                                                                                   | boolean | false |
+| width | 宽度                                                                                                        | number | 448 |
+| zIndex | 遮罩的 z-index 值                                                                                             | number | 1000 |
+| onCancel | 取消对话框时的回调函数,返回 Promise 时,取消按钮会出现 loading 态                                                                | (e: any) => void \| Promise<any\> | 无 |
+| onOk | 点击确认按钮时的回调函数,返回 Promise 时,确认按钮会出现 loading 态                                                               | (e: any) => void \| Promise<any\> | 无 |
 
 ### Modal.method()
 

+ 2 - 2
packages/semi-foundation/base/foundation.ts

@@ -16,7 +16,7 @@ export interface DefaultAdapter<P = Record<string, any>, S = Record<string, any>
     getProps(): P;
     getState(key: string): any;
     getStates(): S;
-    setState(s: Pick<S, keyof S>, callback?: any): void;
+    setState(s: Partial<S>, callback?: any): void;
     getCache(c: string): any;
     getCaches(): any;
     setCache(key: any, value: any): void;
@@ -88,7 +88,7 @@ class BaseFoundation<T extends Partial<DefaultAdapter<P, S>>, P = Record<string,
         return this._adapter.getStates();
     }
 
-    setState(states: S, cb?: (...args: any) => void) {
+    setState(states: Partial<S>, cb?: (...args: any) => void) {
         return this._adapter.setState({ ...states }, cb);
     }
 

+ 26 - 5
packages/semi-foundation/modal/modalFoundation.ts

@@ -1,4 +1,5 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isPromise from "../utils/isPromise";
 
 export type OKType = 'primary' | 'secondary' | 'tertiary' | 'warning' | 'danger';
 export type Size = 'small' | 'medium' | 'large' | 'full-width';
@@ -6,8 +7,8 @@ export type Size = 'small' | 'medium' | 'large' | 'full-width';
 export interface ModalAdapter extends DefaultAdapter<ModalProps, ModalState> {
     disabledBodyScroll: () => void;
     enabledBodyScroll: () => void;
-    notifyCancel: (e: any) => void;
-    notifyOk: (e: any) => void;
+    notifyCancel: (e: any) => void | Promise<any>;
+    notifyOk: (e: any) => void | Promise<any>;
     notifyClose: () => void;
     toggleDisplayNone: (displayNone: boolean, callback?: (displayNone: boolean) => void) => void;
     notifyFullScreen: (isFullScreen: boolean) => void;
@@ -58,7 +59,9 @@ export interface ModalProps {
 
 export interface ModalState {
     displayNone: boolean;
-    isFullScreen: boolean
+    isFullScreen: boolean;
+    onOKReturnPromiseStatus?:"pending"|"fulfilled"|"rejected";
+    onCancelReturnPromiseStatus?:"pending"|"fulfilled"|"rejected"
 }
 
 export default class ModalFoundation extends BaseFoundation<ModalAdapter> {
@@ -74,11 +77,29 @@ export default class ModalFoundation extends BaseFoundation<ModalAdapter> {
     }
 
     handleCancel(e: any) {
-        this._adapter.notifyCancel(e);
+        const result = this._adapter.notifyCancel(e);
+        if (isPromise(result)) {
+            this._adapter.setState({ onCancelReturnPromiseStatus: "pending" });
+            (result as Promise<any>)?.then(()=>{
+                this._adapter.setState({ onCancelReturnPromiseStatus: "fulfilled" });
+            })?.catch(e=>{
+                this._adapter.setState({ onCancelReturnPromiseStatus: "rejected" });
+                throw e;
+            });
+        }
     }
 
     handleOk(e: any) {
-        this._adapter.notifyOk(e);
+        const result = this._adapter.notifyOk(e);
+        if (isPromise(result)){
+            this._adapter.setState({ onOKReturnPromiseStatus: "pending" });
+            (result as Promise<any>)?.then(()=>{
+                this._adapter.setState({ onOKReturnPromiseStatus: "fulfilled" });
+            })?.catch(e=>{
+                this._adapter.setState({ onOKReturnPromiseStatus: "rejected" });
+                throw e;
+            });
+        }
     }
 
     beforeShow() {

+ 4 - 6
packages/semi-ui/modal/Modal.tsx

@@ -92,8 +92,6 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
         centered: false,
         closable: true,
         visible: false,
-        confirmLoading: false,
-        cancelLoading: false,
         okType: 'primary',
         maskClosable: true,
         hasCancel: true,
@@ -150,10 +148,10 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                 }
             },
             notifyCancel: (e: React.MouseEvent) => {
-                this.props.onCancel(e);
+                return this.props.onCancel(e);
             },
             notifyOk: (e: React.MouseEvent) => {
-                this.props.onOk(e);
+                return this.props.onOk(e);
             },
             notifyClose: () => {
                 this.props.afterClose();
@@ -286,7 +284,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                     <Button
                         aria-label="cancel"
                         onClick={this.handleCancel}
-                        loading={cancelLoading}
+                        loading={cancelLoading === undefined ? this.state.onCancelReturnPromiseStatus === "pending" : cancelLoading}
                         type="tertiary"
                         autoFocus={true}
                         {...this.props.cancelButtonProps}
@@ -307,7 +305,7 @@ class Modal extends BaseComponent<ModalReactProps, ModalState> {
                             aria-label="confirm"
                             type={okType}
                             theme="solid"
-                            loading={confirmLoading}
+                            loading={confirmLoading === undefined ? this.state.onOKReturnPromiseStatus === "pending" : confirmLoading}
                             onClick={this.handleOk}
                             {...this.props.okButtonProps}
                             x-semi-children-alias="okText"