Browse Source

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

Roxas 4 years ago
parent
commit
6ad9403336

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

1
 import React from "react";
1
 import React from "react";
2
+import ReactDOM from "react-dom";
2
 import { createPortal } from "react-dom";
3
 import { createPortal } from "react-dom";
3
 import { isBrowser } from "../Utils/utils";
4
 import { isBrowser } from "../Utils/utils";
4
 
5
 
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
 export default Modal;
141
 export default Modal;

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

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
 const Button = (...props: any) => <button {...props}>button</button>;
7
 const Button = (...props: any) => <button {...props}>button</button>;
8
 
8
 
13
 
13
 
14
 export class WaitPayInfoView extends Component<Props, {}> {
14
 export class WaitPayInfoView extends Component<Props, {}> {
15
   static defaultProps = {
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
   render() {
24
   render() {
21
     const { onClickPayed, onClickProblem } = this.props;
25
     const { onClickPayed, onClickProblem } = this.props;
33
             <Button
37
             <Button
34
               className={classnames(styles.pay_btn, styles.btn_default)}
38
               className={classnames(styles.pay_btn, styles.btn_default)}
35
               size="small"
39
               size="small"
36
-              onClick={() => { onClickPayed && onClickPayed() }}
40
+              onClick={() => {
41
+                onClickPayed && onClickPayed();
42
+              }}
37
             >
43
             >
38
               {"live.course_info.pay.payed"}
44
               {"live.course_info.pay.payed"}
39
             </Button>
45
             </Button>
41
               className={styles.btn_default}
47
               className={styles.btn_default}
42
               size="small"
48
               size="small"
43
               style={{
49
               style={{
44
-                marginLeft: '16px',
50
+                marginLeft: "16px"
51
+              }}
52
+              onClick={() => {
53
+                onClickProblem && onClickProblem();
45
               }}
54
               }}
46
-              onClick={() => { onClickProblem && onClickProblem() }}
47
             >
55
             >
48
               {"live.course_info.pay.help"}
56
               {"live.course_info.pay.help"}
49
             </Button>
57
             </Button>
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
 
7
 interface WantedPublishModalProps {
7
 interface WantedPublishModalProps {
8
   modalConfig?: ModalProps;
8
   modalConfig?: ModalProps;
9
+  viewConfig?: {
10
+    showInputWantedClear?: boolean;
11
+  };
9
   handleConfirm?: Function;
12
   handleConfirm?: Function;
10
 }
13
 }
11
 
14
 
83
           this.handleUpdateCurrentWanted(v, this.handleClose)
86
           this.handleUpdateCurrentWanted(v, this.handleClose)
84
         }
87
         }
85
         InputWantedOnBlur={(v: string) => this.handleUpdateCurrentWanted(v)}
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
         CloseFunction={this.handleClose}
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
 
8
 
9
 export interface WantedPublishPopoverProp {
9
 export interface WantedPublishPopoverProp {
10
   popoverConfig?: PopoverProps;
10
   popoverConfig?: PopoverProps;
11
+  viewConfig?: {
12
+    showInputWantedClear?: boolean;
13
+  };
11
   handleConfirm?: Function;
14
   handleConfirm?: Function;
12
 }
15
 }
13
 
16
 
24
     super(props);
27
     super(props);
25
     this.state = {
28
     this.state = {
26
       visible: false,
29
       visible: false,
27
-
28
       current_wanted: null
30
       current_wanted: null
29
     };
31
     };
30
   }
32
   }
35
       this.handleUpdateCurrentWanted(
37
       this.handleUpdateCurrentWanted(
36
         this.state.current_wanted,
38
         this.state.current_wanted,
37
         (value: string) => {
39
         (value: string) => {
38
-          handleConfirm(Number(value).toFixed(2));
40
+          handleConfirm(value ? Number(value).toFixed(2) : value);
39
         }
41
         }
40
       );
42
       );
41
     }
43
     }
55
     value: string | number | null,
57
     value: string | number | null,
56
     afterUpdate?: Function
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
     let result: string | number;
67
     let result: string | number;
60
     result = value;
68
     result = value;
61
     if (parseInt(`${value}`, 10) === value) {
69
     if (parseInt(`${value}`, 10) === value) {
84
           this.handleUpdateCurrentWanted(v, this.handleClose)
92
           this.handleUpdateCurrentWanted(v, this.handleClose)
85
         }
93
         }
86
         InputWantedOnBlur={(v: string) => this.handleUpdateCurrentWanted(v)}
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
         CloseFunction={this.handleClose}
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
 .wrapper {
1
 .wrapper {
2
   .wanted_middle {
2
   .wanted_middle {
3
-    color: #E1AD28;
4
-    font-size: 17px; 
3
+    color: #e1ad28;
4
+    font-size: 17px;
5
     margin-top: 21px;
5
     margin-top: 21px;
6
 
6
 
7
     .wanted_cover {
7
     .wanted_cover {
8
-        width: 95px;
8
+      width: 95px;
9
     }
9
     }
10
   }
10
   }
11
 
11
 
22
 
22
 
23
       :global {
23
       :global {
24
         .ant-input {
24
         .ant-input {
25
-          color: #FB4B56;
25
+          color: #fb4b56;
26
           font-size: 16px;
26
           font-size: 16px;
27
           text-align: center;
27
           text-align: center;
28
-          
29
-          &:focus, &:hover {
28
+
29
+          &:focus,
30
+          &:hover {
30
             outline: none;
31
             outline: none;
31
             box-shadow: none;
32
             box-shadow: none;
32
-            border-color: #FB4B56;
33
+            border-color: #fb4b56;
33
           }
34
           }
34
         }
35
         }
35
       }
36
       }
36
     }
37
     }
37
 
38
 
38
     .wanted_bottom_tips {
39
     .wanted_bottom_tips {
39
-      color: #9B9B9B;
40
+      color: #9b9b9b;
40
       font-size: 12px;
41
       font-size: 12px;
41
     }
42
     }
42
   }
43
   }
47
     .wanted_confirm_button {
48
     .wanted_confirm_button {
48
       font-size: 16px;
49
       font-size: 16px;
49
       color: white;
50
       color: white;
50
-      background-color: #71C135;
51
+      background-color: #71c135;
51
 
52
 
52
-      &:hover, &:focus {
53
+      &:hover,
54
+      &:focus {
53
         color: white;
55
         color: white;
54
         outline: none;
56
         outline: none;
55
         box-shadow: none;
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
     .wanted_cancel_button {
63
     .wanted_cancel_button {
62
       margin-top: 12px;
64
       margin-top: 12px;
63
-      color: #787F8C;
65
+      color: #787f8c;
64
       font-size: 12px;
66
       font-size: 12px;
65
       cursor: pointer;
67
       cursor: pointer;
66
 
68
 
67
-      &:hover, &:focus {
68
-        color: #787F8C;
69
+      &:hover,
70
+      &:focus {
71
+        color: #787f8c;
69
         text-shadow: #565656;
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
-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
 interface WantedPublishViewProps {
11
 interface WantedPublishViewProps {
9
-  type?: 'pop'|'modal';
12
+  type?: "pop" | "modal";
10
   wrapperClass?: string;
13
   wrapperClass?: string;
11
-  current_wanted: number|string|null;
14
+  current_wanted: number | string | null;
15
+  showInputWantedClear?: boolean;
12
   InputWantedValueChange: Function;
16
   InputWantedValueChange: Function;
13
   InputWantedPressEnter: Function;
17
   InputWantedPressEnter: Function;
14
   InputWantedOnBlur: Function;
18
   InputWantedOnBlur: Function;
16
   CloseFunction: Function;
20
   CloseFunction: Function;
17
 }
21
 }
18
 interface WantedPublishViewState {
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
   constructor(props: WantedPublishViewProps) {
31
   constructor(props: WantedPublishViewProps) {
24
     super(props);
32
     super(props);
33
+    this.containerRef = React.createRef();
25
     this.state = {};
34
     this.state = {};
26
   }
35
   }
27
 
36
 
28
   render() {
37
   render() {
29
-    const { current_wanted, wrapperClass } = this.props; 
38
+    const {
39
+      current_wanted,
40
+      wrapperClass,
41
+      showInputWantedClear = true
42
+    } = this.props;
30
     return (
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
         <header>设置悬赏金额</header>
50
         <header>设置悬赏金额</header>
35
         <section className={styles.wanted_middle}>
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
           <div>悬赏提问</div>
57
           <div>悬赏提问</div>
38
         </section>
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
         <section className={styles.wanted_bottom}>
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
           <Input
71
           <Input
43
             className={styles.wanted_input_number}
72
             className={styles.wanted_input_number}
44
             suffix="元"
73
             suffix="元"
45
-            value={current_wanted || ''}
74
+            value={current_wanted || ""}
46
             onChange={v => {
75
             onChange={v => {
47
               if (!/^[.0-9]*$/g.test(v.target.value)) return;
76
               if (!/^[.0-9]*$/g.test(v.target.value)) return;
48
               this.props.InputWantedValueChange(v.target.value);
77
               this.props.InputWantedValueChange(v.target.value);
66
           >
95
           >
67
             确定
96
             确定
68
           </Button>
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
         </footer>
142
         </footer>
78
       </div>
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

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