通用评论

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. import { message } from "antd";
  4. import axios from "./axios";
  5. import { ERROR_DEFAULT, LIMIT } from "./constant";
  6. import { CommentContext } from "./Comment";
  7. import { isFunction } from "./helper";
  8. import CommentInput from "./components/CommentInput";
  9. import CommentList from "./components/CommentList";
  10. import Editor from "./components/Editor";
  11. import "./App.css";
  12. class App extends Component {
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. loading: {},
  17. // oss 配置
  18. oss: {},
  19. // 评论数据
  20. list: [],
  21. page: 1,
  22. total: 0,
  23. // 是否没有更多评论了
  24. isNoMoreComment: false
  25. };
  26. this.handleChangeLoading = this.handleChangeLoading.bind(this);
  27. this.sGetComment = this.sGetComment.bind(this);
  28. this.sGetReply = this.sGetReply.bind(this);
  29. this.sCreateComment = this.sCreateComment.bind(this);
  30. this.sCreateReply = this.sCreateReply.bind(this);
  31. this.sCommentFavor = this.sCommentFavor.bind(this);
  32. this.sOssSts = this.sOssSts.bind(this);
  33. }
  34. componentDidMount() {}
  35. /**
  36. * 改变 loading 状态
  37. * @param {string} key key
  38. * @param {string} value value
  39. */
  40. handleChangeLoading(key, value) {
  41. const { loading } = this.state;
  42. loading[key] = value;
  43. this.setState({ loading });
  44. }
  45. /**
  46. * 获取评论列表
  47. */
  48. sGetComment({ page = 1 } = {}) {
  49. this.handleChangeLoading("sGetComment", true);
  50. const { API, type, businessId } = this.props;
  51. axios
  52. .get(
  53. `${API}/comments?type=${type}&business_id=${businessId}&page=${page}&limit=${LIMIT}`
  54. )
  55. .then(response => {
  56. const { list, page, total } = response.data;
  57. if (list) {
  58. let newList = list;
  59. if (page > 1) {
  60. let { list: oldList } = this.state;
  61. // 删除临时数据
  62. oldList = oldList.filter(o => !o.isTemporary);
  63. newList = oldList.concat(newList);
  64. }
  65. this.setState({
  66. list: newList,
  67. page,
  68. total
  69. });
  70. } else {
  71. message.info("没有更多评论了");
  72. this.setState({
  73. isNoMoreComment: true
  74. });
  75. }
  76. })
  77. .catch(error => {
  78. if (error.response && error.response.data && error.response.data.msg) {
  79. message.error(error.response.data.msg || ERROR_DEFAULT);
  80. return;
  81. }
  82. message.error(error.message || ERROR_DEFAULT);
  83. })
  84. .finally(() => {
  85. this.handleChangeLoading("sGetComment", false);
  86. });
  87. }
  88. /**
  89. * 获取更多回复
  90. */
  91. sGetReply({ commentId, page = 1 } = {}) {
  92. this.handleChangeLoading("sGetReply", true);
  93. const { API } = this.props;
  94. axios
  95. .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
  96. .then(response => {
  97. const { list: replies } = response.data;
  98. if (!replies) {
  99. message.info("没有更多数据了!");
  100. }
  101. const list = this.state.list.map(item => {
  102. if (item.id === commentId) {
  103. if (!item.replies) item.replies = [];
  104. if (replies) {
  105. if (page === 1) {
  106. // 如果当前页数为第一页,则清空当前所有的 replies
  107. // 并将获取到的 replies 存放在 state
  108. item.replies = replies;
  109. } else {
  110. item.replies = item.replies.concat(replies);
  111. // 如果当前页数非第一页,则合并 replies
  112. }
  113. } else {
  114. item.isNoMoreReply = true;
  115. }
  116. }
  117. return item;
  118. });
  119. this.setState({ list });
  120. })
  121. .catch(error => {
  122. if (error.response && error.response.data && error.response.data.msg) {
  123. message.error(error.response.data.msg || ERROR_DEFAULT);
  124. return;
  125. }
  126. message.error(error.message || ERROR_DEFAULT);
  127. })
  128. .finally(() => {
  129. this.handleChangeLoading("sGetReply", false);
  130. });
  131. }
  132. /**
  133. * 添加评论
  134. * @param {string} content comment content
  135. */
  136. sCreateComment(content) {
  137. if (!content) return message.error("评论内容不能为空 ");
  138. this.handleChangeLoading("sCreateComment", true);
  139. const { API, type, businessId } = this.props;
  140. axios(`${API}/comments`, {
  141. method: "post",
  142. data: {
  143. type,
  144. business_id: businessId,
  145. content
  146. },
  147. withCredentials: true
  148. })
  149. .then(response => {
  150. message.success("评论成功!");
  151. // 将数据写入到 list 中
  152. // 临时插入
  153. // 等到获取数据之后,删除临时数据
  154. const { list } = this.state;
  155. list.unshift({
  156. ...response.data,
  157. isTemporary: true // 临时的数据
  158. });
  159. this.setState({ list });
  160. })
  161. .catch(error => {
  162. if (error.response && error.response.data && error.response.data.msg) {
  163. message.error(error.response.data.msg || ERROR_DEFAULT);
  164. return;
  165. }
  166. message.error(error.message || ERROR_DEFAULT);
  167. })
  168. .finally(() => {
  169. this.handleChangeLoading("sCreateComment", false);
  170. });
  171. }
  172. /**
  173. * 添加回复
  174. * 回复评论/回复回复
  175. * @param {object} data { comment_id, content, [reply_id] }
  176. */
  177. sCreateReply(data, cb) {
  178. if (!data.content) return message.error("回复内容不能为空 ");
  179. this.handleChangeLoading("sCreateReply", true);
  180. const { API } = this.props;
  181. axios(`${API}/replies`, {
  182. method: "post",
  183. data,
  184. withCredentials: true
  185. })
  186. .then(response => {
  187. // // 将该条数据插入到 list 中
  188. // const list = this.state.list.map(item => {
  189. // if (item.id === data.comment_id) {
  190. // if (!item.replies) item.replies = [];
  191. // item.reply_count += 1
  192. // item.replies.unshift(response.data);
  193. // }
  194. // return item;
  195. // });
  196. // this.setState({ list });
  197. this.sGetReply({ commentId: data.comment_id });
  198. message.success("回复成功!");
  199. if (isFunction(cb)) cb();
  200. })
  201. .catch(error => {
  202. if (error.response && error.response.data && error.response.data.msg) {
  203. message.error(error.response.data.msg || ERROR_DEFAULT);
  204. return;
  205. }
  206. message.error(error.message || ERROR_DEFAULT);
  207. })
  208. .finally(() => {
  209. this.handleChangeLoading("sCreateReply", false);
  210. });
  211. }
  212. /**
  213. * 点赞/取消点赞
  214. * @param {string} commentId { commentId }
  215. * @param {boolean} favored 是否已经点过赞
  216. */
  217. sCommentFavor(commentId, favored) {
  218. this.handleChangeLoading("sCommentFavor", true);
  219. const { API } = this.props;
  220. axios(`${API}/comments/${commentId}/favor`, {
  221. method: favored ? "delete" : "put",
  222. withCredentials: true
  223. })
  224. .then(response => {
  225. message.success(favored ? "取消点赞成功!" : "点赞成功!");
  226. // 更新 list 中的该项数据的 favored
  227. const list = this.state.list.map(item => {
  228. if (item.id === commentId) {
  229. item.favored = !favored;
  230. item.favor_count += favored ? -1 : 1;
  231. }
  232. return item;
  233. });
  234. this.setState({ list });
  235. })
  236. .catch(error => {
  237. if (error.response && error.response.data && error.response.data.msg) {
  238. message.error(error.response.data.msg || ERROR_DEFAULT);
  239. return;
  240. }
  241. message.error(error.message || ERROR_DEFAULT);
  242. })
  243. .finally(() => {
  244. this.handleChangeLoading("sCommentFavor", false);
  245. });
  246. }
  247. /**
  248. * 获取 OSS 上传的参数
  249. */
  250. sOssSts() {
  251. this.handleChangeLoading("sOssSts", true);
  252. const { API } = this.props;
  253. axios
  254. .get(`${API}/oss/sts`)
  255. .then(response => {
  256. this.setState({ oss: { ...response.data } });
  257. })
  258. .catch(error => {
  259. if (error.response && error.response.data && error.response.data.msg) {
  260. message.error(error.response.data.msg || ERROR_DEFAULT);
  261. return;
  262. }
  263. message.error(error.message || ERROR_DEFAULT);
  264. })
  265. .finally(() => {
  266. this.handleChangeLoading("sOssSts", false);
  267. });
  268. }
  269. render() {
  270. // 添加到 Context 的数据
  271. const value = {
  272. ...this.state,
  273. ...this.props,
  274. sCreateComment: this.sCreateComment,
  275. sGetComment: this.sGetComment,
  276. sCommentFavor: this.sCommentFavor,
  277. sCreateReply: this.sCreateReply,
  278. sGetReply: this.sGetReply,
  279. sOssSts: this.sOssSts
  280. };
  281. return (
  282. <CommentContext.Provider value={value}>
  283. <div className="comment">
  284. {this.props.showEditor && (
  285. <CommentInput content={this.props.children} />
  286. )}
  287. {this.props.showList && (
  288. <div style={{ marginTop: 20 }}>
  289. <CommentList />
  290. </div>
  291. )}
  292. </div>
  293. </CommentContext.Provider>
  294. );
  295. }
  296. }
  297. App.propTypes = {
  298. type: PropTypes.number.isRequired, // 评论的 type
  299. businessId: PropTypes.string.isRequired, // 评论的 business_id
  300. API: PropTypes.string, // 评论的 API 前缀
  301. showList: PropTypes.bool, // 是否显示评论列表
  302. showEditor: PropTypes.bool // 是否显示评论输入框
  303. };
  304. App.defaultProps = {
  305. API: "http://api.links123.net/comment/v1",
  306. showList: true,
  307. showEditor: true
  308. };
  309. export { Editor };
  310. export default App;