通用评论

App.js 9.5KB

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