Sfoglia il codice sorgente

refactor: 优化编辑器

node 6 anni fa
parent
commit
f1c69886e9

+ 15
- 14
src/App.js Vedi File

@@ -8,6 +8,7 @@ import { isFunction } from "./helper";
8 8
 import CommentInput from "./components/CommentInput";
9 9
 import CommentList from "./components/CommentList";
10 10
 import Editor from "./components/Editor";
11
+import lang from "./lang";
11 12
 import "./App.css";
12 13
 
13 14
 class App extends Component {
@@ -82,10 +83,10 @@ class App extends Component {
82 83
       })
83 84
       .catch(error => {
84 85
         if (error.response && error.response.data && error.response.data.msg) {
85
-          message.error(error.response.data.msg || ERROR_DEFAULT);
86
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
86 87
           return;
87 88
         }
88
-        message.error(error.message || ERROR_DEFAULT);
89
+        message.error(lang[error.message] || ERROR_DEFAULT);
89 90
       })
90 91
       .finally(() => {
91 92
         this.handleChangeLoading("sGetComment", false);
@@ -127,10 +128,10 @@ class App extends Component {
127 128
       })
128 129
       .catch(error => {
129 130
         if (error.response && error.response.data && error.response.data.msg) {
130
-          message.error(error.response.data.msg || ERROR_DEFAULT);
131
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
131 132
           return;
132 133
         }
133
-        message.error(error.message || ERROR_DEFAULT);
134
+        message.error(lang[error.message] || ERROR_DEFAULT);
134 135
       })
135 136
       .finally(() => {
136 137
         this.handleChangeLoading("sGetReply", false);
@@ -139,9 +140,9 @@ class App extends Component {
139 140
 
140 141
   /**
141 142
    * 添加评论
142
-   * @param {string} content comment content
143
+   * @param {object} {content} comment content
143 144
    */
144
-  sCreateComment(content) {
145
+  sCreateComment({ content } = {}) {
145 146
     if (!content) return message.error("评论内容不能为空 ");
146 147
     this.handleChangeLoading("sCreateComment", true);
147 148
     const { API, type, businessId } = this.props;
@@ -168,10 +169,10 @@ class App extends Component {
168 169
       })
169 170
       .catch(error => {
170 171
         if (error.response && error.response.data && error.response.data.msg) {
171
-          message.error(error.response.data.msg || ERROR_DEFAULT);
172
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
172 173
           return;
173 174
         }
174
-        message.error(error.message || ERROR_DEFAULT);
175
+        message.error(lang[error.message] || ERROR_DEFAULT);
175 176
       })
176 177
       .finally(() => {
177 178
         this.handleChangeLoading("sCreateComment", false);
@@ -209,10 +210,10 @@ class App extends Component {
209 210
       })
210 211
       .catch(error => {
211 212
         if (error.response && error.response.data && error.response.data.msg) {
212
-          message.error(error.response.data.msg || ERROR_DEFAULT);
213
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
213 214
           return;
214 215
         }
215
-        message.error(error.message || ERROR_DEFAULT);
216
+        message.error(lang[error.message] || ERROR_DEFAULT);
216 217
       })
217 218
       .finally(() => {
218 219
         this.handleChangeLoading("sCreateReply", false);
@@ -245,10 +246,10 @@ class App extends Component {
245 246
       })
246 247
       .catch(error => {
247 248
         if (error.response && error.response.data && error.response.data.msg) {
248
-          message.error(error.response.data.msg || ERROR_DEFAULT);
249
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
249 250
           return;
250 251
         }
251
-        message.error(error.message || ERROR_DEFAULT);
252
+        message.error(lang[error.message] || ERROR_DEFAULT);
252 253
       })
253 254
       .finally(() => {
254 255
         this.handleChangeLoading("sCommentFavor", false);
@@ -268,10 +269,10 @@ class App extends Component {
268 269
       })
269 270
       .catch(error => {
270 271
         if (error.response && error.response.data && error.response.data.msg) {
271
-          message.error(error.response.data.msg || ERROR_DEFAULT);
272
+          message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
272 273
           return;
273 274
         }
274
-        message.error(error.message || ERROR_DEFAULT);
275
+        message.error(lang[error.message] || ERROR_DEFAULT);
275 276
       })
276 277
       .finally(() => {
277 278
         this.handleChangeLoading("sOssSts", false);

+ 19
- 13
src/components/CommentBox/index.js Vedi File

@@ -39,9 +39,10 @@ class CommentBox extends Component {
39 39
   /**
40 40
    * 渲染回复 DOM
41 41
    * @param {array} replies 回复列表
42
+   * @param {number} replies 回复的数量
42 43
    * @param {boolean} isNoMoreReply 是否没有更多回复
43 44
    */
44
-  renderReplies(replies, isNoMoreReply) {
45
+  renderReplies(replies, replyCount, isNoMoreReply) {
45 46
     const { commentId } = this.props;
46 47
     const { showReply } = this.state;
47 48
     if (showReply && replies && replies.length) {
@@ -56,17 +57,18 @@ class CommentBox extends Component {
56 57
                   replyId={item.id}
57 58
                   key={item.id}
58 59
                   content={item}
59
-                  type="reply"
60
+                  type="replyToReply" // 回复的回复
60 61
                 />,
61 62
                 <div className="moreBox" key="show_more_button">
62
-                  {!isNoMoreReply && (
63
-                    <span
64
-                      className="showMore"
65
-                      onClick={() => this.handleGetMoreReply(commentId)}
66
-                    >
67
-                      查看更多回复
68
-                    </span>
69
-                  )}
63
+                  {!isNoMoreReply &&
64
+                    replyCount !== len && (
65
+                      <span
66
+                        className="showMore"
67
+                        onClick={() => this.handleGetMoreReply(commentId)}
68
+                      >
69
+                        查看更多回复
70
+                      </span>
71
+                    )}
70 72
 
71 73
                   <a
72 74
                     style={{ float: "right" }}
@@ -83,7 +85,7 @@ class CommentBox extends Component {
83 85
                 replyId={item.id}
84 86
                 key={item.id}
85 87
                 content={item}
86
-                type="reply"
88
+                type="reply" // 评论的回复
87 89
               />
88 90
             );
89 91
           })}
@@ -103,9 +105,13 @@ class CommentBox extends Component {
103 105
           onShowReply={this.handleToggleReply}
104 106
           showReply={showReply}
105 107
           commentId={content.id}
106
-          type="comment"
108
+          action="comment" // 回复
107 109
         />
108
-        {this.renderReplies(content.replies, content.isNoMoreReply)}
110
+        {this.renderReplies(
111
+          content.replies,
112
+          content.reply_count,
113
+          content.isNoMoreReply
114
+        )}
109 115
       </div>
110 116
     );
111 117
   }

+ 143
- 0
src/components/CommentInput.1/index.js Vedi File

@@ -0,0 +1,143 @@
1
+import React, { Component } from "react";
2
+import PropTypes from "prop-types";
3
+import { OSS_LINK } from "../../constant";
4
+import Comment from "../../Comment";
5
+
6
+class CommentInput extends Component {
7
+  constructor(props) {
8
+    super(props);
9
+    this.state = {
10
+      value: "",
11
+
12
+      fileList: [], // 图片列表
13
+      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
14
+    };
15
+    this.handleChange = this.handleChange.bind(this);
16
+    this.handleSubmit = this.handleSubmit.bind(this);
17
+    this.handleChangeFileList = this.handleChangeFileList.bind(this);
18
+    this.handleChangeEmoji = this.handleChangeEmoji.bind(this);
19
+    this.handleUpload = this.handleUpload.bind(this);
20
+  }
21
+
22
+  handleChange(value) {
23
+    this.setState({ value });
24
+  }
25
+
26
+  handleChangeFileList(fileList) {
27
+    this.setState({ fileList });
28
+  }
29
+
30
+  handleChangeEmoji(emojiId) {
31
+    let { value } = this.state;
32
+    value += `[${emojiId}]`;
33
+    this.setState({ value });
34
+    React.Children.forEach(this.props.content, child => {
35
+      // 如果 Editor 的父组件传入了 onChange 事件,则需要将改变之后的值传递给父组件
36
+      if (child.props.onChange) {
37
+        child.props.onChange(value);
38
+      }
39
+    });
40
+  }
41
+
42
+  handleUpload({ uid, path }) {
43
+    const { fileMap } = this.state;
44
+    fileMap[uid] = path;
45
+    this.setState({ fileMap });
46
+  }
47
+
48
+  handleSubmit() {
49
+    let { value, fileMap, fileList } = this.state;
50
+    if (fileList.length) {
51
+      value += "<br/>";
52
+      fileList.forEach(item => {
53
+        value += `[${OSS_LINK}${fileMap[item.uid]}]`;
54
+      });
55
+    }
56
+
57
+    const { type, commentId, replyId, handleToggleInput } = this.props;
58
+    if (type === "normal") {
59
+      this.props.app.sCreateComment({
60
+        content: value
61
+      });
62
+    } else if (type === "comment") {
63
+      this.props.app.sCreateReply(
64
+        {
65
+          comment_id: commentId,
66
+          content: value
67
+        },
68
+        () => handleToggleInput()
69
+      );
70
+    } else if (type === "reply") {
71
+      this.props.app.sCreateReply(
72
+        {
73
+          comment_id: commentId,
74
+          content: value,
75
+          reply_id: replyId
76
+        },
77
+        () => handleToggleInput()
78
+      );
79
+    }
80
+
81
+    React.Children.forEach(this.props.content, child => {
82
+      // 如果 Editor 的父组件传入了 onSubmit 事件,则需要将改变之后的值传递给父组件
83
+      if (child.props.onSubmit) {
84
+        child.props.onSubmit(value);
85
+      }
86
+    });
87
+  }
88
+
89
+  render() {
90
+    const { type } = this.props;
91
+    const { value, fileList } = this.state;
92
+
93
+    const childrenWithProps = React.Children.map(this.props.content, child => {
94
+      return React.cloneElement(child, {
95
+        value: value,
96
+        fileList: fileList,
97
+        onChange: this.handleChange,
98
+        onSubmit: this.handleSubmit,
99
+        onChangeFileList: this.handleChangeFileList,
100
+        onChangeEmoji: this.handleChangeEmoji,
101
+        onUpload: this.handleUpload,
102
+        loading: this.props.app.loading.sCreateComment,
103
+        ...child.props
104
+      });
105
+    });
106
+
107
+    return (
108
+      <div>
109
+        {type === "normal" ? (
110
+          <div>
111
+            <span
112
+              style={{
113
+                border: "1px solid #CECECE",
114
+                color: "#666",
115
+                padding: "2px 3px"
116
+              }}
117
+            >
118
+              回复
119
+            </span>
120
+            <span style={{ marginLeft: "20px", color: "#5198EB" }}>
121
+              口碑
122
+              <span style={{ marginLeft: "20px", color: "#666666" }}>
123
+                (全站挑出毛病或提出合理建议,奖励10到100元红包)
124
+              </span>
125
+            </span>
126
+          </div>
127
+        ) : null}
128
+        <div style={{ marginTop: 40 }}>{childrenWithProps}</div>
129
+      </div>
130
+    );
131
+  }
132
+}
133
+
134
+CommentInput.propTypes = {
135
+  // normal 有切换回复/口碑的 header ; comment 评论输入框 / reply 回复输入框
136
+  type: PropTypes.oneOf(["normal", "comment", "reply"])
137
+};
138
+
139
+CommentInput.defaultProps = {
140
+  type: "normal"
141
+};
142
+
143
+export default Comment(CommentInput);

+ 19
- 75
src/components/CommentInput/index.js Vedi File

@@ -1,118 +1,60 @@
1 1
 import React, { Component } from "react";
2 2
 import PropTypes from "prop-types";
3
-import { OSS_LINK } from "../../constant";
4 3
 import Comment from "../../Comment";
5 4
 
6 5
 class CommentInput extends Component {
7 6
   constructor(props) {
8 7
     super(props);
9
-    this.state = {
10
-      value: "",
11
-
12
-      fileList: [], // 图片列表
13
-      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
14
-    };
15
-    this.handleChange = this.handleChange.bind(this);
8
+    this.state = {};
16 9
     this.handleSubmit = this.handleSubmit.bind(this);
17
-    this.handleChangeFileList = this.handleChangeFileList.bind(this);
18
-    this.handleChangeEmoji = this.handleChangeEmoji.bind(this);
19
-    this.handleUpload = this.handleUpload.bind(this);
20
-  }
21
-
22
-  handleChange(value) {
23
-    this.setState({ value });
24
-  }
25
-
26
-  handleChangeFileList(fileList) {
27
-    this.setState({ fileList });
28
-  }
29
-
30
-  handleChangeEmoji(emojiId) {
31
-    let { value } = this.state;
32
-    value += `[${emojiId}]`;
33
-    this.setState({ value });
34
-    React.Children.forEach(this.props.content, child => {
35
-      // 如果 Editor 的父组件传入了 onChange 事件,则需要将改变之后的值传递给父组件
36
-      if (child.props.onChange) {
37
-        child.props.onChange(value);
38
-      }
39
-    });
40 10
   }
41 11
 
42
-  handleUpload({ uid, path }) {
43
-    const { fileMap } = this.state;
44
-    fileMap[uid] = path;
45
-    this.setState({ fileMap });
46
-  }
47
-
48
-  handleSubmit() {
49
-    let { value, fileMap, fileList } = this.state;
50
-    if (fileList.length) {
51
-      value += "<br/>";
52
-      fileList.forEach(item => {
53
-        value += `[${OSS_LINK}${fileMap[item.uid]}]`;
54
-      });
55
-    }
56
-
57
-    const { type, commentId, replyId, handleToggleInput } = this.props;
58
-    if (type === "normal") {
12
+  handleSubmit(value) {
13
+    console.log("handleSubmit comment input value: ", value);
14
+    const { action, commentId, replyId, callback } = this.props;
15
+    if (action === "comment") {
59 16
       this.props.app.sCreateComment({
60 17
         content: value
61 18
       });
62
-    } else if (type === "comment") {
19
+    } else if (action === "reply") {
63 20
       this.props.app.sCreateReply(
64 21
         {
65 22
           comment_id: commentId,
66 23
           content: value
67 24
         },
68
-        () => handleToggleInput()
25
+        () => callback && callback()
69 26
       );
70
-    } else if (type === "reply") {
27
+    } else if (action === "replyToReply") {
71 28
       this.props.app.sCreateReply(
72 29
         {
73 30
           comment_id: commentId,
74 31
           content: value,
75 32
           reply_id: replyId
76 33
         },
77
-        () => handleToggleInput()
34
+        () => callback && callback()
78 35
       );
79 36
     }
80
-
81
-    React.Children.forEach(this.props.content, child => {
82
-      // 如果 Editor 的父组件传入了 onSubmit 事件,则需要将改变之后的值传递给父组件
83
-      if (child.props.onSubmit) {
84
-        child.props.onSubmit(value);
85
-      }
86
-    });
87 37
   }
88 38
 
89 39
   render() {
90
-    const { type } = this.props;
91
-    const { value, fileList } = this.state;
92
-
93 40
     const childrenWithProps = React.Children.map(this.props.content, child => {
94 41
       return React.cloneElement(child, {
95
-        value: value,
96
-        fileList: fileList,
97
-        onChange: this.handleChange,
42
+        // 默认使用 CommentInput 的 onSubmit 来提交评论
43
+        // 但也可以使用 Editor 的 props 来覆盖 onSubmit
98 44
         onSubmit: this.handleSubmit,
99
-        onChangeFileList: this.handleChangeFileList,
100
-        onChangeEmoji: this.handleChangeEmoji,
101
-        onUpload: this.handleUpload,
102
-        loading: this.props.app.loading.sCreateComment,
103 45
         ...child.props
104 46
       });
105 47
     });
106 48
 
107 49
     return (
108 50
       <div>
109
-        {type === "normal" ? (
51
+        {/* {type === "normal" ? (
110 52
           <div>
111 53
             <span
112 54
               style={{
113 55
                 border: "1px solid #CECECE",
114 56
                 color: "#666",
115
-                padding: "2px 3px"
57
+                padding: "2px 3px",
116 58
               }}
117 59
             >
118 60
               回复
@@ -124,7 +66,7 @@ class CommentInput extends Component {
124 66
               </span>
125 67
             </span>
126 68
           </div>
127
-        ) : null}
69
+        ) : null} */}
128 70
         <div style={{ marginTop: 40 }}>{childrenWithProps}</div>
129 71
       </div>
130 72
     );
@@ -132,12 +74,14 @@ class CommentInput extends Component {
132 74
 }
133 75
 
134 76
 CommentInput.propTypes = {
135
-  // normal 有切换回复/口碑的 header ; comment 评论输入框 / reply 回复输入框
136
-  type: PropTypes.oneOf(["normal", "comment", "reply"])
77
+  // comment 评论
78
+  // reply 评论的回复
79
+  // replyToReply 回复的回复
80
+  action: PropTypes.oneOf(["comment", "reply", "replyToReply"])
137 81
 };
138 82
 
139 83
 CommentInput.defaultProps = {
140
-  type: "normal"
84
+  action: "comment"
141 85
 };
142 86
 
143 87
 export default Comment(CommentInput);

+ 10
- 8
src/components/CommentList/index.js Vedi File

@@ -17,6 +17,7 @@ class CommentList extends Component {
17 17
   render() {
18 18
     const {
19 19
       list,
20
+      total,
20 21
       page,
21 22
       loading,
22 23
       isNoMoreComment,
@@ -31,14 +32,15 @@ class CommentList extends Component {
31 32
             <CommentBox content={item} key={item.id} commentId={item.id} />
32 33
           ))}
33 34
 
34
-          {!isNoMoreComment && (
35
-            <div
36
-              className="showMore"
37
-              onClick={() => sGetComment({ page: page + 1 })}
38
-            >
39
-              <span>查看更多评论</span>
40
-            </div>
41
-          )}
35
+          {!isNoMoreComment &&
36
+            list.length !== total && (
37
+              <div
38
+                className="showMore"
39
+                onClick={() => sGetComment({ page: page + 1 })}
40
+              >
41
+                <span>查看更多评论</span>
42
+              </div>
43
+            )}
42 44
         </Spin>
43 45
       </div>
44 46
     );

+ 28
- 21
src/components/ContentItem/index.js Vedi File

@@ -4,6 +4,7 @@ import { Avatar, Icon } from "antd";
4 4
 import dayjs from "dayjs";
5 5
 import Comment from "../../Comment";
6 6
 import CommentInput from "../CommentInput";
7
+// import Editor from "../Editor";
7 8
 import { renderContent } from "../../helper";
8 9
 import "./index.css";
9 10
 
@@ -40,13 +41,16 @@ class CommentItem extends Component {
40 41
       commentId,
41 42
       replyId,
42 43
       content,
43
-      type,
44
+      action,
44 45
       showReply,
45 46
       onShowReply,
46 47
       app
47 48
     } = this.props;
49
+
48 50
     const { isShowInput } = this.state;
49
-    const isComment = type === "comment";
51
+    console.log("isShowInput ", isShowInput);
52
+
53
+    const isComment = action === "comment";
50 54
     return (
51 55
       <div className="box">
52 56
         <div className="left">
@@ -71,18 +75,19 @@ class CommentItem extends Component {
71 75
             }}
72 76
           />
73 77
           <div className="bottom">
74
-            {isComment && content.reply_count ? (
75
-              <div>
76
-                <a
77
-                  className="itemLeft"
78
-                  onClick={onShowReply}
79
-                  style={{ userSelect: "none" }}
80
-                >
81
-                  {content.reply_count} 条回复
82
-                  {showReply ? <Icon type="up" /> : <Icon type="down" />}
83
-                </a>
84
-              </div>
85
-            ) : null}
78
+            {isComment &&
79
+              content.reply_count && (
80
+                <div>
81
+                  <a
82
+                    className="itemLeft"
83
+                    onClick={onShowReply}
84
+                    style={{ userSelect: "none" }}
85
+                  >
86
+                    {content.reply_count} 条回复
87
+                    {showReply ? <Icon type="up" /> : <Icon type="down" />}
88
+                  </a>
89
+                </div>
90
+              )}
86 91
 
87 92
             <a onClick={this.handleToggleInput} className="itemRight">
88 93
               &nbsp; 回复
@@ -100,14 +105,15 @@ class CommentItem extends Component {
100 105
             </div>
101 106
           </div>
102 107
 
103
-          {isShowInput ? (
108
+          {/* {isShowInput && (
104 109
             <CommentInput
105
-              type={type}
110
+              action={action}
106 111
               replyId={replyId}
107 112
               commentId={commentId}
108
-              handleToggleInput={this.handleToggleInput}
113
+              callback={this.handleToggleInput}
109 114
             />
110
-          ) : null}
115
+          )} */}
116
+          {isShowInput && <CommentInput content={app.children} />}
111 117
         </div>
112 118
       </div>
113 119
     );
@@ -117,13 +123,14 @@ class CommentItem extends Component {
117 123
 CommentItem.propTypes = {
118 124
   content: PropTypes.object.isRequired,
119 125
   // comment 评论
120
-  // reply 回复
121
-  type: PropTypes.oneOf(["comment", "reply"]),
126
+  // reply 评论的回复
127
+  // replyToReply 回复的回复
128
+  action: PropTypes.oneOf(["comment", "reply", "replyToReply"]),
122 129
   onShowReply: PropTypes.func
123 130
 };
124 131
 
125 132
 CommentItem.defaultProps = {
126
-  type: "comment",
133
+  action: "comment",
127 134
   onShowReply: () => {}
128 135
 };
129 136
 

+ 23
- 0
src/components/Editor.1/Emoji.css Vedi File

@@ -0,0 +1,23 @@
1
+.item {
2
+  float: left;
3
+  width: 40px;
4
+  height: 40px;
5
+  cursor: pointer;
6
+  white-space: nowrap;
7
+  /* this is required unless you put the helper span closely near the img */
8
+  text-align: center;
9
+  margin: 0;
10
+}
11
+.item .helper {
12
+  display: inline-block;
13
+  height: 100%;
14
+  vertical-align: middle;
15
+}
16
+.item img {
17
+  margin: 0 auto;
18
+  vertical-align: middle;
19
+  padding: 3px;
20
+}
21
+.item img:hover {
22
+  border: 1px solid #40a9ff;
23
+}

+ 67
- 0
src/components/Editor.1/Emoji.js Vedi File

@@ -0,0 +1,67 @@
1
+import React from "react";
2
+import { Carousel } from "antd";
3
+import emoji, { prefixUrl, ext } from "../../emoji";
4
+import "./Emoji.css";
5
+// 每页 20  5*4
6
+// 共 20 * 3 = 60 (实际是 54)
7
+
8
+// class Emoji
9
+
10
+const Emoji = ({ onClick }) => {
11
+  const content = [[], [], []];
12
+
13
+  for (let i = 0; i < emoji.length; i++) {
14
+    if (i < 20) {
15
+      content[0].push(emoji[i]);
16
+    } else if (i < 40) {
17
+      content[1].push(emoji[i]);
18
+    } else if (i < emoji.length) {
19
+      content[2].push(emoji[i]);
20
+    }
21
+  }
22
+  return (
23
+    <Carousel>
24
+      <div>
25
+        {content[0].map((item, index) => (
26
+          <div className="item" key={item.value}>
27
+            <span className="helper" />
28
+            <img
29
+              src={`${prefixUrl}${item.value}.${ext}`}
30
+              alt={item.title}
31
+              style={{ display: "inline-block" }}
32
+              onClick={() => onClick(item.title)}
33
+            />
34
+          </div>
35
+        ))}
36
+      </div>
37
+      <div>
38
+        {content[1].map((item, index) => (
39
+          <div className="item" key={item.value}>
40
+            <span className="helper" />
41
+            <img
42
+              src={`${prefixUrl}${item.value}.${ext}`}
43
+              alt={item.title}
44
+              style={{ display: "inline-block" }}
45
+              onClick={() => onClick(item.title)}
46
+            />
47
+          </div>
48
+        ))}
49
+      </div>
50
+      <div>
51
+        {content[2].map(item => (
52
+          <div className="item" key={item.value}>
53
+            <span className="helper" />
54
+            <img
55
+              src={`${prefixUrl}${item.value}.${ext}`}
56
+              alt={item.title}
57
+              style={{ display: "inline-block" }}
58
+              onClick={() => onClick(item.title)}
59
+            />
60
+          </div>
61
+        ))}
62
+      </div>
63
+    </Carousel>
64
+  );
65
+};
66
+
67
+export default Emoji;

+ 8
- 0
src/components/Editor.1/Upload.css Vedi File

@@ -0,0 +1,8 @@
1
+.ant-upload-select-picture-card i {
2
+  font-size: 32px;
3
+  color: #999;
4
+}
5
+.ant-upload-select-picture-card .ant-upload-text {
6
+  margin-top: 8px;
7
+  color: #666;
8
+}

+ 132
- 0
src/components/Editor.1/Upload.js Vedi File

@@ -0,0 +1,132 @@
1
+import React from "react";
2
+import { Upload, Icon, Modal, message } from "antd";
3
+import dayjs from "dayjs";
4
+import shortid from "shortid";
5
+import {
6
+  OSS_ENDPOINT,
7
+  OSS_BUCKET,
8
+  DRIVER_LICENSE_PATH,
9
+  ERROR_DEFAULT,
10
+  MAX_UPLOAD_NUMBER
11
+} from "../../constant";
12
+import Comment from "../../Comment";
13
+// import styles from "./Upload.less";
14
+import "./Upload.css";
15
+
16
+const client = oss => {
17
+  return new window.OSS.Wrapper({
18
+    accessKeyId: oss.access_key_id,
19
+    accessKeySecret: oss.access_key_secret,
20
+    stsToken: oss.security_token,
21
+    endpoint: OSS_ENDPOINT, //常量,你可以自己定义
22
+    bucket: OSS_BUCKET
23
+  });
24
+};
25
+
26
+const uploadPath = (path, file) => {
27
+  return `${path}/${dayjs().format("YYYYMMDD")}/${shortid.generate()}.${
28
+    file.type.split("/")[1]
29
+  }`;
30
+};
31
+
32
+const UploadToOss = (oss, path, file) => {
33
+  const url = uploadPath(path, file);
34
+  return new Promise((resolve, reject) => {
35
+    client(oss)
36
+      .multipartUpload(url, file)
37
+      .then(data => {
38
+        resolve(data);
39
+      })
40
+      .catch(error => {
41
+        reject(error);
42
+      });
43
+  });
44
+};
45
+
46
+class App extends React.Component {
47
+  constructor(props) {
48
+    super(props);
49
+    this.state = {
50
+      previewVisible: false,
51
+      previewImage: ""
52
+    };
53
+    this.handleCancel = this.handleCancel.bind(this);
54
+    this.handlePreview = this.handlePreview.bind(this);
55
+    this.handleChange = this.handleChange.bind(this);
56
+    this.customRequest = this.customRequest.bind(this);
57
+  }
58
+
59
+  componentDidMount() {
60
+    this.props.app.sOssSts();
61
+  }
62
+
63
+  handleCancel() {
64
+    this.setState({ previewVisible: false });
65
+  }
66
+
67
+  handlePreview(file) {
68
+    this.setState({
69
+      previewImage: file.url || file.thumbUrl,
70
+      previewVisible: true
71
+    });
72
+  }
73
+
74
+  handleChange({ fileList }) {
75
+    this.props.onChangeFileList(fileList);
76
+  }
77
+
78
+  customRequest(info) {
79
+    const { file } = info;
80
+    info.onProgress({ percent: 10 });
81
+    let reader = new FileReader();
82
+    reader.readAsDataURL(info.file);
83
+    reader.onloadend = () => {
84
+      info.onProgress({ percent: 20 });
85
+      // DRIVER_LICENSE_PATH oss 的存储路径位置
86
+      UploadToOss(this.props.app.oss, DRIVER_LICENSE_PATH, file)
87
+        .then(data => {
88
+          info.onProgress({ percent: 100 });
89
+          info.onSuccess();
90
+          this.props.onUpload({ path: data.name, uid: file.uid });
91
+        })
92
+        .catch(e => {
93
+          message.error(e.message || ERROR_DEFAULT);
94
+          info.onError(e);
95
+        });
96
+    };
97
+  }
98
+
99
+  render() {
100
+    const { previewVisible, previewImage } = this.state;
101
+    const { fileList } = this.props;
102
+    const uploadButton = (
103
+      <div>
104
+        <Icon type="plus" />
105
+        <div className="ant-upload-text">上传</div>
106
+      </div>
107
+    );
108
+    return (
109
+      <div>
110
+        <Upload
111
+          accept="image/jpg,image/jpeg,image/png,image/bmp"
112
+          listType="picture-card"
113
+          fileList={fileList}
114
+          customRequest={this.customRequest}
115
+          onPreview={this.handlePreview}
116
+          onChange={this.handleChange}
117
+        >
118
+          {fileList.length >= MAX_UPLOAD_NUMBER ? null : uploadButton}
119
+        </Upload>
120
+        <Modal
121
+          visible={previewVisible}
122
+          footer={null}
123
+          onCancel={this.handleCancel}
124
+        >
125
+          <img alt="upload" style={{ width: "100%" }} src={previewImage} />
126
+        </Modal>
127
+      </div>
128
+    );
129
+  }
130
+}
131
+
132
+export default Comment(App);

+ 82
- 0
src/components/Editor.1/index.css Vedi File

@@ -0,0 +1,82 @@
1
+.editor {
2
+  box-sizing: border-box;
3
+  margin: 0;
4
+  padding: 0;
5
+  width: 100%;
6
+  max-width: 100%;
7
+  list-style: none;
8
+  position: relative;
9
+  display: block;
10
+  font-size: 14px;
11
+  line-height: 1.5;
12
+  color: rgba(0, 0, 0, 0.65);
13
+  background-color: #fff;
14
+  background-image: none;
15
+  border: 1px solid #d9d9d9;
16
+  border-radius: 4px;
17
+  transition: all 0.3s, height 0s;
18
+}
19
+.editor textarea.ant-input {
20
+  border: none;
21
+  border-bottom: 1px solid #eee;
22
+  border-bottom-right-radius: 0;
23
+  border-bottom-left-radius: 0;
24
+}
25
+.editor textarea.ant-input:hover {
26
+  border: none;
27
+  border-bottom: 1px solid #eee;
28
+}
29
+.editor textarea.ant-input:focus {
30
+  box-shadow: none;
31
+  border-bottom: 1px solid #eee;
32
+}
33
+.editor [contentEditable="true"]:empty:not(:focus):before {
34
+  content: attr(data-text);
35
+  color: #bfbfbf;
36
+}
37
+.editor:focus,
38
+.editor:hover {
39
+  border-color: #40a9ff;
40
+  outline: 0;
41
+  box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
42
+}
43
+.content {
44
+  display: block;
45
+  box-sizing: border-box;
46
+  padding: 4px 10px;
47
+  width: 100%;
48
+  max-width: 100%;
49
+  height: 100%;
50
+  max-height: 110px;
51
+  overflow-y: auto;
52
+  overflow-x: auto;
53
+}
54
+.content:focus,
55
+.content:hover {
56
+  outline: 0;
57
+}
58
+.toolbar {
59
+  display: inline-block;
60
+  width: 100%;
61
+  margin: 5px 0 0 0;
62
+}
63
+.toolbar .icon {
64
+  font-size: 23px;
65
+  cursor: pointer;
66
+  transition: color 0.3s;
67
+}
68
+.toolbar .icon:hover {
69
+  color: #40a9ff;
70
+}
71
+.feed .ant-popover-inner-content {
72
+  padding: 12px 16px 20px 16px;
73
+}
74
+.feed .ant-carousel .slick-dots {
75
+  bottom: -10px;
76
+}
77
+.feed .ant-carousel .slick-dots li.slick-active button {
78
+  background-color: #7b868a;
79
+}
80
+.feed .ant-carousel .slick-dots li button {
81
+  background-color: #a2aeb5;
82
+}

+ 157
- 0
src/components/Editor.1/index.js Vedi File

@@ -0,0 +1,157 @@
1
+import React from "react";
2
+import PropTypes from "prop-types";
3
+import { Icon, Button, Popover, Input } from "antd";
4
+import { MAX_UPLOAD_NUMBER } from "../../constant";
5
+import Upload from "./Upload";
6
+import Emoji from "./Emoji";
7
+import "./index.css";
8
+
9
+const { TextArea } = Input;
10
+// 设置 Editor 组件的默认值
11
+// 不能在 Editor.defaultProps 中设置
12
+// 因为 Editor 在 ComponentInput 中调用
13
+// 在 ComponentInput 中,需要使用 Editor 的 props 覆盖 ComponentInput 传入的 props
14
+const EditorDefaultProps = {
15
+  rows: 5,
16
+  placeholder: "说点什么吧...",
17
+  showEmoji: true,
18
+  showUpload: true,
19
+  submitText: "发表",
20
+  onChange: () => {}
21
+};
22
+
23
+class Editor extends React.Component {
24
+  constructor(props) {
25
+    super(props);
26
+    this.state = {
27
+      showUpload: false
28
+    };
29
+    this.handleClickEmoji = this.handleClickEmoji.bind(this);
30
+    this.handleShowUpload = this.handleShowUpload.bind(this);
31
+  }
32
+
33
+  componentDidMount() {}
34
+
35
+  handleClickEmoji(emojiId) {
36
+    this.props.onChangeEmoji(emojiId);
37
+  }
38
+
39
+  handleShowUpload(showUpload) {
40
+    if (typeof showUpload === "boolean") {
41
+      this.setState({ showUpload: showUpload });
42
+    } else {
43
+      this.setState({ showUpload: !this.state.showUpload });
44
+    }
45
+  }
46
+
47
+  render() {
48
+    const props = { ...EditorDefaultProps, ...this.props };
49
+    const {
50
+      value,
51
+      onChange,
52
+      onSubmit,
53
+      loading,
54
+      placeholder,
55
+      fileList,
56
+      onChangeFileList,
57
+      rows,
58
+      onUpload,
59
+      showEmoji,
60
+      showUpload,
61
+      submitText
62
+    } = props;
63
+
64
+    return (
65
+      <div className="editor">
66
+        <TextArea
67
+          value={value}
68
+          onChange={e => onChange(e.target.value)}
69
+          rows={rows}
70
+          placeholder={placeholder}
71
+        />
72
+
73
+        <div className="toolbar">
74
+          <div style={{ float: "left", margin: "8px 11px" }}>
75
+            {showEmoji && (
76
+              <Popover
77
+                trigger="click"
78
+                placement="bottomLeft"
79
+                autoAdjustOverflow={false}
80
+                content={
81
+                  <div style={{ width: 200 }}>
82
+                    <Emoji onClick={this.handleClickEmoji} />
83
+                  </div>
84
+                }
85
+                overlayClassName="feed"
86
+              >
87
+                <Icon type="smile-o" className="icon" />
88
+              </Popover>
89
+            )}
90
+
91
+            {showUpload && (
92
+              <Popover
93
+                visible={this.state.showUpload}
94
+                overlayStyle={{ zIndex: 999 }}
95
+                content={
96
+                  <div
97
+                    style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
98
+                  >
99
+                    <Upload
100
+                      onChangeFileList={onChangeFileList}
101
+                      onUpload={onUpload}
102
+                      fileList={fileList}
103
+                    />
104
+                  </div>
105
+                }
106
+                placement="bottomLeft"
107
+                title={
108
+                  <div style={{ margin: "5px auto" }}>
109
+                    <span>
110
+                      上传图片
111
+                      <span style={{ color: "#666", fontWeight: 400 }}>
112
+                        (您还能上传{MAX_UPLOAD_NUMBER - fileList.length}张图片)
113
+                      </span>
114
+                    </span>
115
+                    <Icon
116
+                      type="close"
117
+                      onClick={() => this.handleShowUpload(false)}
118
+                      style={{
119
+                        float: "right",
120
+                        cursor: "pointer",
121
+                        marginTop: 4
122
+                      }}
123
+                    />
124
+                  </div>
125
+                }
126
+              >
127
+                <Icon
128
+                  type="picture"
129
+                  className="icon"
130
+                  style={{ marginLeft: 10 }}
131
+                  onClick={() => this.handleShowUpload(true)}
132
+                />
133
+              </Popover>
134
+            )}
135
+          </div>
136
+
137
+          <div style={{ float: "right", margin: "5px 11px" }}>
138
+            <Button onClick={onSubmit} type="primary" loading={loading}>
139
+              {submitText}
140
+            </Button>
141
+          </div>
142
+        </div>
143
+      </div>
144
+    );
145
+  }
146
+}
147
+
148
+Editor.propTypes = {
149
+  rows: PropTypes.number,
150
+  placeholder: PropTypes.string,
151
+  showEmoji: PropTypes.bool,
152
+  showUpload: PropTypes.bool,
153
+  submitText: PropTypes.string,
154
+  onChange: PropTypes.func
155
+};
156
+
157
+export default Editor;

+ 104
- 10
src/components/Editor/index.js Vedi File

@@ -1,6 +1,7 @@
1 1
 import React from "react";
2 2
 import PropTypes from "prop-types";
3 3
 import { Icon, Button, Popover, Input } from "antd";
4
+import { OSS_LINK } from "../../constant";
4 5
 import { MAX_UPLOAD_NUMBER } from "../../constant";
5 6
 import Upload from "./Upload";
6 7
 import Emoji from "./Emoji";
@@ -24,18 +25,56 @@ class Editor extends React.Component {
24 25
   constructor(props) {
25 26
     super(props);
26 27
     this.state = {
27
-      showUpload: false
28
+      showUpload: false,
29
+      value: "", // 编辑器里面的值
30
+
31
+      fileList: [], // 图片列表
32
+      fileMap: {} // 已经上传的图片路径和 uid 的映射 { uid: path }
28 33
     };
34
+    this.handleChange = this.handleChange.bind(this);
29 35
     this.handleClickEmoji = this.handleClickEmoji.bind(this);
36
+    this.handleChangeFileList = this.handleChangeFileList.bind(this);
30 37
     this.handleShowUpload = this.handleShowUpload.bind(this);
38
+    this.handleUpload = this.handleUpload.bind(this);
39
+    this.handleSubmit = this.handleSubmit.bind(this);
31 40
   }
32 41
 
33 42
   componentDidMount() {}
34 43
 
35
-  handleClickEmoji(emojiId) {
36
-    this.props.onChangeEmoji(emojiId);
44
+  /**
45
+   * 编辑器的值改变事件
46
+   * 将最新的值存储到 state 中
47
+   * @param {string} value 输入的值
48
+   */
49
+  handleChange(value) {
50
+    this.setState({ value });
51
+  }
52
+
53
+  /**
54
+   * 点击 emoji 的事件
55
+   * 点击后,需要将改 emoji 插入到编辑器中
56
+   * 插入的值为 [emoji chinese name]
57
+   * 参数 emoji 即为 emoji chinese name
58
+   * @param {string}} emoji emoji 的中文,如 微笑
59
+   */
60
+  handleClickEmoji(emoji) {
61
+    let { value } = this.state;
62
+    value += `[${emoji}]`;
63
+    this.handleChange(value);
37 64
   }
38 65
 
66
+  /**
67
+   * 监听文件列表改变事件
68
+   * @param {Array} fileList 文件列表
69
+   */
70
+  handleChangeFileList(fileList) {
71
+    this.setState({ fileList });
72
+  }
73
+
74
+  /**
75
+   * 控制上传 Popover 的显示和隐藏
76
+   * @param {boolean} showUpload 是否显示上传的 Popover
77
+   */
39 78
   handleShowUpload(showUpload) {
40 79
     if (typeof showUpload === "boolean") {
41 80
       this.setState({ showUpload: showUpload });
@@ -44,6 +83,56 @@ class Editor extends React.Component {
44 83
     }
45 84
   }
46 85
 
86
+  /**
87
+   * 上传文件 TODO:
88
+   * @param {object} param 文件对象
89
+   */
90
+  handleUpload({ uid, path }) {
91
+    const { fileMap } = this.state;
92
+    fileMap[uid] = path;
93
+    this.setState({ fileMap });
94
+  }
95
+
96
+  /**
97
+   * 提交编辑器内容
98
+   * 提交功能,交给父组件来实现
99
+   * 需要父组件传入 onSubmit
100
+   */
101
+  handleSubmit() {
102
+    let { value, fileMap, fileList } = this.state;
103
+    if (fileList.length) {
104
+      value += "<br/>";
105
+      fileList.forEach(item => {
106
+        value += `[${OSS_LINK}${fileMap[item.uid]}]`;
107
+      });
108
+    }
109
+    this.props.onSubmit(value);
110
+
111
+    // const { type, commentId, replyId, handleToggleInput } = this.props;
112
+    // if (type === "normal") {
113
+    //   this.props.app.sCreateComment({
114
+    //     content: value,
115
+    //   });
116
+    // } else if (type === "comment") {
117
+    //   this.props.app.sCreateReply(
118
+    //     {
119
+    //       comment_id: commentId,
120
+    //       content: value,
121
+    //     },
122
+    //     () => handleToggleInput()
123
+    //   );
124
+    // } else if (type === "reply") {
125
+    //   this.props.app.sCreateReply(
126
+    //     {
127
+    //       comment_id: commentId,
128
+    //       content: value,
129
+    //       reply_id: replyId,
130
+    //     },
131
+    //     () => handleToggleInput()
132
+    //   );
133
+    // }
134
+  }
135
+
47 136
   render() {
48 137
     const props = { ...EditorDefaultProps, ...this.props };
49 138
     const {
@@ -64,8 +153,8 @@ class Editor extends React.Component {
64 153
     return (
65 154
       <div className="editor">
66 155
         <TextArea
67
-          value={value}
68
-          onChange={e => onChange(e.target.value)}
156
+          value={this.state.value}
157
+          onChange={e => this.handleChange(e.target.value)}
69 158
           rows={rows}
70 159
           placeholder={placeholder}
71 160
         />
@@ -97,9 +186,9 @@ class Editor extends React.Component {
97 186
                     style={{ width: 112 * MAX_UPLOAD_NUMBER, minHeight: 100 }}
98 187
                   >
99 188
                     <Upload
100
-                      onChangeFileList={onChangeFileList}
101
-                      onUpload={onUpload}
102
-                      fileList={fileList}
189
+                      onChangeFileList={this.handleChangeFileList}
190
+                      onUpload={this.handleUpload}
191
+                      fileList={this.state.fileList}
103 192
                     />
104 193
                   </div>
105 194
                 }
@@ -109,7 +198,8 @@ class Editor extends React.Component {
109 198
                     <span>
110 199
                       上传图片
111 200
                       <span style={{ color: "#666", fontWeight: 400 }}>
112
-                        (您还能上传{MAX_UPLOAD_NUMBER - fileList.length}张图片)
201
+                        (您还能上传{MAX_UPLOAD_NUMBER -
202
+                          this.state.fileList.length}张图片)
113 203
                       </span>
114 204
                     </span>
115 205
                     <Icon
@@ -135,7 +225,11 @@ class Editor extends React.Component {
135 225
           </div>
136 226
 
137 227
           <div style={{ float: "right", margin: "5px 11px" }}>
138
-            <Button onClick={onSubmit} type="primary" loading={loading}>
228
+            <Button
229
+              onClick={() => this.handleSubmit()}
230
+              type="primary"
231
+              loading={loading}
232
+            >
139 233
               {submitText}
140 234
             </Button>
141 235
           </div>

+ 1
- 1
src/index.js Vedi File

@@ -7,7 +7,7 @@ const props = {
7 7
   type: 1,
8 8
   businessId: "1",
9 9
   API: "http://api.links123.net/comment/v1",
10
-  showList: false
10
+  showList: true
11 11
 };
12 12
 
13 13
 const editorProps = {