Browse Source

add: 确认取消悬赏的弹窗,命令式组件ModalCMD

Roxas 4 years ago
parent
commit
6ad9403336

+ 90
- 0
src/components/Common/Modal/index.tsx View File

@@ -1,4 +1,5 @@
1 1
 import React from "react";
2
+import ReactDOM from "react-dom";
2 3
 import { createPortal } from "react-dom";
3 4
 import { isBrowser } from "../Utils/utils";
4 5
 
@@ -48,4 +49,93 @@ export class Modal extends React.PureComponent<ModalProps> {
48 49
   }
49 50
 }
50 51
 
52
+export interface ModalCMDOptions {
53
+  children: any;
54
+  container?: HTMLElement;
55
+  options?: {
56
+    mask?: true;
57
+  };
58
+}
59
+
60
+export interface ModalCMDRecord {
61
+  modalInstance: HTMLElement;
62
+}
63
+
64
+export class ModalCMD {
65
+  static currentModal: ModalCMDRecord[] = [];
66
+
67
+  constructor() {
68
+    ModalCMD.currentModal = [];
69
+  }
70
+
71
+  private static recordModal(record: ModalCMDRecord) {
72
+    ModalCMD.currentModal.push(record);
73
+  }
74
+
75
+  private static delModal() {
76
+    return ModalCMD.currentModal.pop();
77
+  }
78
+
79
+  static show({ children, container }: ModalCMDOptions) {
80
+    const modalInstance = document.createElement("div");
81
+    let targetMountDom = container ? container : document.body;
82
+    targetMountDom.appendChild(modalInstance);
83
+    ReactDOM.render(
84
+      <>
85
+        <div
86
+          style={{
87
+            position: "fixed",
88
+            top: 0,
89
+            bottom: 0,
90
+            left: 0,
91
+            right: 0,
92
+            zIndex: 1000
93
+          }}
94
+        >
95
+          <div
96
+            style={{
97
+              position: "absolute",
98
+              top: 0,
99
+              bottom: 0,
100
+              left: 0,
101
+              right: 0,
102
+              background: "rgba(0,0,0,1)",
103
+              opacity: 0.6,
104
+              zIndex: -1
105
+            }}
106
+            onClick={() => {
107
+              ModalCMD.hide(ModalCMD.currentModal.length);
108
+            }}
109
+          />
110
+
111
+          <div
112
+            style={{
113
+              position: "absolute",
114
+              top: "50%",
115
+              left: "50%",
116
+              transform: "translate(-50%, -50%)",
117
+              backgroundColor: "white"
118
+            }}
119
+          >
120
+            {children}
121
+          </div>
122
+        </div>
123
+      </>,
124
+      modalInstance
125
+    );
126
+    this.recordModal({
127
+      modalInstance
128
+    });
129
+  }
130
+
131
+  static hide(targetId?: string | number) {
132
+    const popModalRecord: ModalCMDRecord | undefined = this.delModal();
133
+    if (popModalRecord && popModalRecord.modalInstance) {
134
+      ReactDOM.unmountComponentAtNode(popModalRecord.modalInstance);
135
+      const { parentNode } = popModalRecord.modalInstance;
136
+      parentNode && parentNode.removeChild(popModalRecord.modalInstance);
137
+    }
138
+  }
139
+}
140
+
51 141
 export default Modal;

+ 19
- 11
src/components/Payment/WaitPayInfoView/index.tsx View File

@@ -1,8 +1,8 @@
1
-import React, { Component } from 'react';
2
-import classnames from 'classnames';
3
-import styles from './WaitPayInfoView.less';
1
+import React, { Component } from "react";
2
+import classnames from "classnames";
3
+import styles from "./WaitPayInfoView.less";
4 4
 
5
-import infoIconImg from '../assets/icon/icon-info-blue@2x.svg';
5
+import infoIconImg from "../assets/icon/icon-info-blue@2x.svg";
6 6
 
7 7
 const Button = (...props: any) => <button {...props}>button</button>;
8 8
 
@@ -13,9 +13,13 @@ interface Props {
13 13
 
14 14
 export class WaitPayInfoView extends Component<Props, {}> {
15 15
   static defaultProps = {
16
-    onClickPayed: () => { console.log("onClickPayed"); },
17
-    onClickProblem: () => { console.log("onClickProblem"); }
18
-  }
16
+    onClickPayed: () => {
17
+      console.log("onClickPayed");
18
+    },
19
+    onClickProblem: () => {
20
+      console.log("onClickProblem");
21
+    }
22
+  };
19 23
 
20 24
   render() {
21 25
     const { onClickPayed, onClickProblem } = this.props;
@@ -33,7 +37,9 @@ export class WaitPayInfoView extends Component<Props, {}> {
33 37
             <Button
34 38
               className={classnames(styles.pay_btn, styles.btn_default)}
35 39
               size="small"
36
-              onClick={() => { onClickPayed && onClickPayed() }}
40
+              onClick={() => {
41
+                onClickPayed && onClickPayed();
42
+              }}
37 43
             >
38 44
               {"live.course_info.pay.payed"}
39 45
             </Button>
@@ -41,9 +47,11 @@ export class WaitPayInfoView extends Component<Props, {}> {
41 47
               className={styles.btn_default}
42 48
               size="small"
43 49
               style={{
44
-                marginLeft: '16px',
50
+                marginLeft: "16px"
51
+              }}
52
+              onClick={() => {
53
+                onClickProblem && onClickProblem();
45 54
               }}
46
-              onClick={() => { onClickProblem && onClickProblem() }}
47 55
             >
48 56
               {"live.course_info.pay.help"}
49 57
             </Button>
@@ -54,4 +62,4 @@ export class WaitPayInfoView extends Component<Props, {}> {
54 62
   }
55 63
 }
56 64
 
57
-export default WaitPayInfoView;
65
+export default WaitPayInfoView;

+ 10
- 1
src/components/Payment/WantedPublishModal/index.tsx View File

@@ -6,6 +6,9 @@ import styles from "./WantedPublishModal.less";
6 6
 
7 7
 interface WantedPublishModalProps {
8 8
   modalConfig?: ModalProps;
9
+  viewConfig?: {
10
+    showInputWantedClear?: boolean;
11
+  };
9 12
   handleConfirm?: Function;
10 13
 }
11 14
 
@@ -83,8 +86,14 @@ export class WantedPublishModal extends Component<
83 86
           this.handleUpdateCurrentWanted(v, this.handleClose)
84 87
         }
85 88
         InputWantedOnBlur={(v: string) => this.handleUpdateCurrentWanted(v)}
86
-        InputWantedClear={() => this.clearCurrentWanted(this.handleClose)}
89
+        InputWantedClear={() => {
90
+          this.clearCurrentWanted(() => {
91
+            if (this.props.handleConfirm) this.props.handleConfirm(null);
92
+            this.setState({ modalVisible: false });
93
+          });
94
+        }}
87 95
         CloseFunction={this.handleClose}
96
+        {...this.props.viewConfig}
88 97
       />
89 98
     );
90 99
   }

+ 18
- 4
src/components/Payment/WantedPublishPopover/index.tsx View File

@@ -8,6 +8,9 @@ import styles from "./WantedPublishPopover.less";
8 8
 
9 9
 export interface WantedPublishPopoverProp {
10 10
   popoverConfig?: PopoverProps;
11
+  viewConfig?: {
12
+    showInputWantedClear?: boolean;
13
+  };
11 14
   handleConfirm?: Function;
12 15
 }
13 16
 
@@ -24,7 +27,6 @@ export class WantedPublishPopover extends React.Component<
24 27
     super(props);
25 28
     this.state = {
26 29
       visible: false,
27
-
28 30
       current_wanted: null
29 31
     };
30 32
   }
@@ -35,7 +37,7 @@ export class WantedPublishPopover extends React.Component<
35 37
       this.handleUpdateCurrentWanted(
36 38
         this.state.current_wanted,
37 39
         (value: string) => {
38
-          handleConfirm(Number(value).toFixed(2));
40
+          handleConfirm(value ? Number(value).toFixed(2) : value);
39 41
         }
40 42
       );
41 43
     }
@@ -55,7 +57,13 @@ export class WantedPublishPopover extends React.Component<
55 57
     value: string | number | null,
56 58
     afterUpdate?: Function
57 59
   ) => {
58
-    if (!value) return;
60
+    if (!value) {
61
+      this.setState(
62
+        { current_wanted: null },
63
+        () => afterUpdate && afterUpdate(null)
64
+      );
65
+      return;
66
+    }
59 67
     let result: string | number;
60 68
     result = value;
61 69
     if (parseInt(`${value}`, 10) === value) {
@@ -84,8 +92,14 @@ export class WantedPublishPopover extends React.Component<
84 92
           this.handleUpdateCurrentWanted(v, this.handleClose)
85 93
         }
86 94
         InputWantedOnBlur={(v: string) => this.handleUpdateCurrentWanted(v)}
87
-        InputWantedClear={() => this.clearCurrentWanted(this.handleClose)}
95
+        InputWantedClear={() => {
96
+          this.clearCurrentWanted(() => {
97
+            if (this.props.handleConfirm) this.props.handleConfirm(null);
98
+            this.setState({ visible: false });
99
+          });
100
+        }}
88 101
         CloseFunction={this.handleClose}
102
+        {...this.props.viewConfig}
89 103
       />
90 104
     );
91 105
   }

+ 74
- 16
src/components/Payment/WantedPublishView/WantedPublishView.less View File

@@ -1,11 +1,11 @@
1 1
 .wrapper {
2 2
   .wanted_middle {
3
-    color: #E1AD28;
4
-    font-size: 17px; 
3
+    color: #e1ad28;
4
+    font-size: 17px;
5 5
     margin-top: 21px;
6 6
 
7 7
     .wanted_cover {
8
-        width: 95px;
8
+      width: 95px;
9 9
     }
10 10
   }
11 11
 
@@ -22,21 +22,22 @@
22 22
 
23 23
       :global {
24 24
         .ant-input {
25
-          color: #FB4B56;
25
+          color: #fb4b56;
26 26
           font-size: 16px;
27 27
           text-align: center;
28
-          
29
-          &:focus, &:hover {
28
+
29
+          &:focus,
30
+          &:hover {
30 31
             outline: none;
31 32
             box-shadow: none;
32
-            border-color: #FB4B56;
33
+            border-color: #fb4b56;
33 34
           }
34 35
         }
35 36
       }
36 37
     }
37 38
 
38 39
     .wanted_bottom_tips {
39
-      color: #9B9B9B;
40
+      color: #9b9b9b;
40 41
       font-size: 12px;
41 42
     }
42 43
   }
@@ -47,27 +48,84 @@
47 48
     .wanted_confirm_button {
48 49
       font-size: 16px;
49 50
       color: white;
50
-      background-color: #71C135;
51
+      background-color: #71c135;
51 52
 
52
-      &:hover, &:focus {
53
+      &:hover,
54
+      &:focus {
53 55
         color: white;
54 56
         outline: none;
55 57
         box-shadow: none;
56
-        background-color: lighten(#71C135, 10%);
57
-        border-color: lighten(#71C135, 50%);
58
+        background-color: lighten(#71c135, 10%);
59
+        border-color: lighten(#71c135, 50%);
58 60
       }
59 61
     }
60 62
 
61 63
     .wanted_cancel_button {
62 64
       margin-top: 12px;
63
-      color: #787F8C;
65
+      color: #787f8c;
64 66
       font-size: 12px;
65 67
       cursor: pointer;
66 68
 
67
-      &:hover, &:focus {
68
-        color: #787F8C;
69
+      &:hover,
70
+      &:focus {
71
+        color: #787f8c;
69 72
         text-shadow: #565656;
70 73
       }
71 74
     }
72 75
   }
73
-}
76
+
77
+  .wantedCancelModal {
78
+    width: 320px;
79
+    height: 211px;
80
+    padding: 32px;
81
+    box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.2);
82
+    border-radius: 4px;
83
+
84
+    .top {
85
+      display: flex;
86
+      .questionIcon {
87
+        width: 22px;
88
+        height: 22px;
89
+
90
+        img {
91
+          width: 100%;
92
+          height: 100%;
93
+        }
94
+      }
95
+      .title {
96
+        font-size: 16px;
97
+        color: rgba(0, 0, 0, 0.85);
98
+        font-weight: bold;
99
+        margin-left: 16px;
100
+      }
101
+    }
102
+
103
+    .content {
104
+      margin-left: 38px;
105
+      font-size: 14px;
106
+    }
107
+
108
+    footer {
109
+      display: flex;
110
+      justify-content: flex-end;
111
+      align-items: center;
112
+
113
+      .modalCancel {
114
+      }
115
+      .modalOk {
116
+        margin-left: 8px;
117
+        background-color: #71c135;
118
+        color: white;
119
+
120
+        &:hover,
121
+        &:focus {
122
+          color: white;
123
+          outline: none;
124
+          box-shadow: none;
125
+          background-color: lighten(#71c135, 10%);
126
+          border-color: lighten(#71c135, 50%);
127
+        }
128
+      }
129
+    }
130
+  }
131
+}

+ 92
- 27
src/components/Payment/WantedPublishView/index.tsx View File

@@ -1,14 +1,18 @@
1
-import React from 'react';
2
-import classnames from 'classnames';
3
-import { Divider, Input, Button } from 'antd';
1
+import React from "react";
2
+import classnames from "classnames";
3
+import { Divider, Input, Button } from "antd";
4 4
 
5
-import wantedCover from '../assets/cover_wanted@2x.png';
6
-import styles from './WantedPublishView.less';
5
+import wantedCover from "../assets/cover_wanted@2x.png";
6
+import QuestionIcon from "../assets/icon/question-circle-o@2x.png";
7
+import styles from "./WantedPublishView.less";
8
+import { createPortal } from "react-dom";
9
+import { ModalCMD } from "@components/Common/Modal";
7 10
 
8 11
 interface WantedPublishViewProps {
9
-  type?: 'pop'|'modal';
12
+  type?: "pop" | "modal";
10 13
   wrapperClass?: string;
11
-  current_wanted: number|string|null;
14
+  current_wanted: number | string | null;
15
+  showInputWantedClear?: boolean;
12 16
   InputWantedValueChange: Function;
13 17
   InputWantedPressEnter: Function;
14 18
   InputWantedOnBlur: Function;
@@ -16,33 +20,58 @@ interface WantedPublishViewProps {
16 20
   CloseFunction: Function;
17 21
 }
18 22
 interface WantedPublishViewState {
19
-  payment?: number
23
+  payment?: number;
20 24
 }
21 25
 
22
-class WantedPublishView extends React.Component<WantedPublishViewProps, WantedPublishViewState> {
26
+class WantedPublishView extends React.Component<
27
+  WantedPublishViewProps,
28
+  WantedPublishViewState
29
+> {
30
+  private containerRef: React.RefObject<any>;
23 31
   constructor(props: WantedPublishViewProps) {
24 32
     super(props);
33
+    this.containerRef = React.createRef();
25 34
     this.state = {};
26 35
   }
27 36
 
28 37
   render() {
29
-    const { current_wanted, wrapperClass } = this.props; 
38
+    const {
39
+      current_wanted,
40
+      wrapperClass,
41
+      showInputWantedClear = true
42
+    } = this.props;
30 43
     return (
31
-      <div className={classnames(styles.wrapper, {
32
-        [wrapperClass ? wrapperClass : '']: true
33
-      })}>
44
+      <div
45
+        className={classnames(styles.wrapper, {
46
+          [wrapperClass ? wrapperClass : ""]: true
47
+        })}
48
+        ref={this.containerRef}
49
+      >
34 50
         <header>设置悬赏金额</header>
35 51
         <section className={styles.wanted_middle}>
36
-          <img className={styles.wanted_cover} src={wantedCover} alt="wanted_image"/>
52
+          <img
53
+            className={styles.wanted_cover}
54
+            src={wantedCover}
55
+            alt="wanted_image"
56
+          />
37 57
           <div>悬赏提问</div>
38 58
         </section>
39
-        <Divider style={{ minWidth: 'auto', marginLeft: 'auto', marginRight: 'auto', width: '266px'}} />
59
+        <Divider
60
+          style={{
61
+            minWidth: "auto",
62
+            marginLeft: "auto",
63
+            marginRight: "auto",
64
+            width: "266px"
65
+          }}
66
+        />
40 67
         <section className={styles.wanted_bottom}>
41
-          <div className={styles.wanted_bottom_title}>设置悬赏金额范围¥5.00~¥10000</div>
68
+          <div className={styles.wanted_bottom_title}>
69
+            设置悬赏金额范围¥5.00~¥10000
70
+          </div>
42 71
           <Input
43 72
             className={styles.wanted_input_number}
44 73
             suffix="元"
45
-            value={current_wanted || ''}
74
+            value={current_wanted || ""}
46 75
             onChange={v => {
47 76
               if (!/^[.0-9]*$/g.test(v.target.value)) return;
48 77
               this.props.InputWantedValueChange(v.target.value);
@@ -66,18 +95,54 @@ class WantedPublishView extends React.Component<WantedPublishViewProps, WantedPu
66 95
           >
67 96
             确定
68 97
           </Button>
69
-          <div
70
-            role="button"
71
-            tabIndex={-1}
72
-            onClick={() => this.props.InputWantedClear()}
73
-            className={styles.wanted_cancel_button}
74
-          >
75
-            取消悬赏
76
-          </div>
98
+          {showInputWantedClear && (
99
+            <div
100
+              role="button"
101
+              tabIndex={-1}
102
+              onClick={() => {
103
+                ModalCMD.show({
104
+                  container: this.containerRef.current || document.body,
105
+                  children: (
106
+                    <div className={styles.wantedCancelModal}>
107
+                      <div className={styles.top}>
108
+                        <div className={styles.questionIcon}>
109
+                          <img src={QuestionIcon} alt="question" />
110
+                        </div>
111
+                        <div className={styles.title}>确认减少悬赏金额</div>
112
+                      </div>
113
+                      <div className={styles.content}>
114
+                        若减少悬赏金额,您之前支付的金额中,已分配的赏金无法退回,剩余的¥2.00会退还至您的钱包。
115
+                      </div>
116
+                      <footer>
117
+                        <Button
118
+                          className={styles.modalCancel}
119
+                          onClick={() => ModalCMD.hide()}
120
+                        >
121
+                          取消
122
+                        </Button>
123
+                        <Button
124
+                          className={styles.modalOk}
125
+                          onClick={() => {
126
+                            this.props.InputWantedClear();
127
+                            ModalCMD.hide();
128
+                          }}
129
+                        >
130
+                          确认
131
+                        </Button>
132
+                      </footer>
133
+                    </div>
134
+                  )
135
+                });
136
+              }}
137
+              className={styles.wanted_cancel_button}
138
+            >
139
+              取消悬赏
140
+            </div>
141
+          )}
77 142
         </footer>
78 143
       </div>
79
-    )
144
+    );
80 145
   }
81 146
 }
82 147
 
83
-export default WantedPublishView;
148
+export default WantedPublishView;

BIN
src/components/Payment/assets/icon/question-circle-o@2x.png View File


+ 43
- 0
stories/Common.stories.tsx View File

@@ -0,0 +1,43 @@
1
+import { storiesOf } from "@storybook/react";
2
+import { number, select, text, withKnobs } from "@storybook/addon-knobs";
3
+import { withInfo } from "@storybook/addon-info";
4
+import { addReadme } from "storybook-readme";
5
+import React from "react";
6
+import Modal, { ModalCMD } from "@components/Common/Modal";
7
+import { Button } from "antd";
8
+
9
+const stories = storiesOf("Common", module);
10
+stories.addDecorator(storyFn => (
11
+  <div style={{ padding: "0px 40px" }}>{storyFn()}</div>
12
+));
13
+stories.addDecorator(withKnobs);
14
+stories.addDecorator(withInfo);
15
+stories.addDecorator(addReadme);
16
+
17
+stories.add(
18
+  "Modal",
19
+  () => {
20
+    const [vis, setVis] = React.useState(false);
21
+    return (
22
+      <div>
23
+        <Button onClick={() => setVis(true)}>Modal</Button>
24
+        <Modal visible={vis} onCancel={() => setVis(false)}>
25
+          NormalModal
26
+        </Modal>
27
+        <Button
28
+          onClick={() =>
29
+            ModalCMD.show({
30
+              children: <div>Hide</div>
31
+            })
32
+          }
33
+        >
34
+          ModalCMD.show
35
+        </Button>
36
+      </div>
37
+    );
38
+  },
39
+  {
40
+    info: { inline: true },
41
+    notes: "A very simple example of addon notes"
42
+  }
43
+);

+ 21
- 2
stories/Wanted.stories.tsx View File

@@ -1,5 +1,11 @@
1 1
 import { storiesOf } from "@storybook/react";
2
-import { number, select, text, withKnobs } from "@storybook/addon-knobs";
2
+import {
3
+  number,
4
+  select,
5
+  text,
6
+  boolean,
7
+  withKnobs
8
+} from "@storybook/addon-knobs";
3 9
 import { withInfo } from "@storybook/addon-info";
4 10
 import { addReadme } from "storybook-readme";
5 11
 import React from "react";
@@ -92,7 +98,14 @@ stories.add(
92 98
     return (
93 99
       <div>
94 100
         <div>
95
-          <WantedPublishModal>
101
+          <WantedPublishModal
102
+            viewConfig={{
103
+              showInputWantedClear: boolean(
104
+                "viewConfig.showInputWantedClear",
105
+                false
106
+              )
107
+            }}
108
+          >
96 109
             <div>ModalClick</div>
97 110
           </WantedPublishModal>
98 111
         </div>
@@ -134,6 +147,12 @@ stories.add(
134 147
             handleConfirm={(value: any) => {
135 148
               setWanted(value);
136 149
             }}
150
+            viewConfig={{
151
+              showInputWantedClear: boolean(
152
+                "viewConfig.showInputWantedClear",
153
+                false
154
+              )
155
+            }}
137 156
           >
138 157
             <Button>HoverIt{wanted}</Button>
139 158
           </WantedPublishPopover>