通用评论

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import React, { Component } from "react";
  2. import PropTypes from "prop-types";
  3. import { message, Tag } 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.sReplyFavor = this.sReplyFavor.bind(this);
  34. this.sOssSts = this.sOssSts.bind(this);
  35. }
  36. componentDidMount() {}
  37. /**
  38. * 改变 loading 状态
  39. * @param {string} key key
  40. * @param {string} value value
  41. */
  42. handleChangeLoading(key, value) {
  43. const { loading } = this.state;
  44. loading[key] = value;
  45. this.setState({ loading });
  46. }
  47. /**
  48. * 获取评论列表
  49. */
  50. sGetComment({ page = 1 } = {}) {
  51. this.handleChangeLoading("sGetComment", true);
  52. const { API, type, businessId } = this.props;
  53. axios
  54. .get(
  55. `${API}/comments?type=${type}&business_id=${businessId}&page=${page}&limit=${LIMIT}`
  56. )
  57. .then(response => {
  58. const { list, page, total } = response.data;
  59. if (list) {
  60. let newList = list;
  61. if (page > 1) {
  62. let { list: oldList } = this.state;
  63. // 删除临时数据
  64. oldList = oldList.filter(o => !o.isTemporary);
  65. newList = oldList.concat(newList);
  66. }
  67. this.setState({
  68. list: newList,
  69. page,
  70. total
  71. });
  72. } else {
  73. message.info("没有更多评论了");
  74. this.setState({
  75. isNoMoreComment: true
  76. });
  77. }
  78. })
  79. .catch(error => {
  80. if (error.response && error.response.data && error.response.data.msg) {
  81. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  82. return;
  83. }
  84. message.error(lang[error.message] || ERROR_DEFAULT);
  85. })
  86. .finally(() => {
  87. this.handleChangeLoading("sGetComment", false);
  88. });
  89. }
  90. /**
  91. * 获取更多回复
  92. */
  93. sGetReply({ commentId, page = 1 } = {}) {
  94. this.handleChangeLoading("sGetReply", true);
  95. const { API } = this.props;
  96. axios
  97. .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
  98. .then(response => {
  99. if (!response.data.list) {
  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 (response.data.list) {
  106. if (page === 1) {
  107. // 如果当前页数为第一页,则清空当前所有的 replies
  108. // 并将获取到的 replies 存放在 state
  109. item.replies = response.data.list;
  110. } else {
  111. item.replies = item.replies
  112. .filter(o => !o.isTemporary)
  113. .concat(response.data.list);
  114. // 如果当前页数非第一页,则合并 replies
  115. }
  116. item.reply_count = response.data.total;
  117. item.reply_page = response.data.page;
  118. } else {
  119. item.isNoMoreReply = true;
  120. }
  121. }
  122. return item;
  123. });
  124. this.setState({ list });
  125. })
  126. .catch(error => {
  127. if (error.response && error.response.data && error.response.data.msg) {
  128. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  129. return;
  130. }
  131. message.error(lang[error.message] || ERROR_DEFAULT);
  132. })
  133. .finally(() => {
  134. this.handleChangeLoading("sGetReply", false);
  135. });
  136. }
  137. /**
  138. * 添加评论
  139. * @param {object} {content} comment content
  140. */
  141. sCreateComment({ content } = {}) {
  142. if (!content) return message.error("评论内容不能为空 ");
  143. this.handleChangeLoading("sCreateComment", true);
  144. const { API, type, businessId } = this.props;
  145. axios(`${API}/comments`, {
  146. method: "post",
  147. data: {
  148. type,
  149. business_id: businessId,
  150. content
  151. },
  152. withCredentials: true
  153. })
  154. .then(response => {
  155. message.success("评论成功!");
  156. // 将数据写入到 list 中
  157. // 临时插入
  158. // 等到获取数据之后,删除临时数据
  159. const { list, total } = this.state;
  160. list.unshift({
  161. ...response.data,
  162. isTemporary: true // 临时的数据
  163. });
  164. this.setState({ list, total: total + 1 });
  165. })
  166. .catch(error => {
  167. if (error.response && error.response.data && error.response.data.msg) {
  168. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  169. return;
  170. }
  171. message.error(lang[error.message] || ERROR_DEFAULT);
  172. })
  173. .finally(() => {
  174. this.handleChangeLoading("sCreateComment", false);
  175. });
  176. }
  177. /**
  178. * 添加回复
  179. * 回复评论/回复回复
  180. * @param {object} data { comment_id, content, [reply_id] }
  181. */
  182. sCreateReply(data, cb) {
  183. console.log("list: ", this.state.list);
  184. if (!data.content) return message.error("回复内容不能为空 ");
  185. this.handleChangeLoading("sCreateReply", true);
  186. const { API } = this.props;
  187. axios(`${API}/replies`, {
  188. method: "post",
  189. data,
  190. withCredentials: true
  191. })
  192. .then(response => {
  193. message.success("回复成功!");
  194. if (isFunction(cb)) cb();
  195. // 将数据写入到 list 中
  196. // 临时插入
  197. // 等到获取数据之后,删除临时数据
  198. const list = this.state.list.map(item => {
  199. if (item.id === data.comment_id) {
  200. if (!item.replies) item.replies = [];
  201. item.replies.push({
  202. ...response.data,
  203. isTemporary: true // 临时的数据
  204. });
  205. item.reply_count += 1;
  206. }
  207. return item;
  208. });
  209. this.setState({ list });
  210. })
  211. .catch(error => {
  212. if (error.response && error.response.data && error.response.data.msg) {
  213. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  214. return;
  215. }
  216. message.error(lang[error.message] || ERROR_DEFAULT);
  217. })
  218. .finally(() => {
  219. this.handleChangeLoading("sCreateReply", false);
  220. });
  221. }
  222. /**
  223. * 评论 点赞/取消点赞
  224. * @param {string} commentId { commentId }
  225. * @param {boolean} favored 是否已经点过赞
  226. */
  227. sCommentFavor(commentId, favored) {
  228. this.handleChangeLoading("sCommentFavor", true);
  229. const { API } = this.props;
  230. axios(`${API}/comments/${commentId}/favor`, {
  231. method: favored ? "delete" : "put",
  232. withCredentials: true
  233. })
  234. .then(response => {
  235. message.success(favored ? "取消点赞成功!" : "点赞成功!");
  236. // 更新 list 中的该项数据的 favored
  237. const list = this.state.list.map(item => {
  238. if (item.id === commentId) {
  239. item.favored = !favored;
  240. item.favor_count += favored ? -1 : 1;
  241. }
  242. return item;
  243. });
  244. this.setState({ list });
  245. })
  246. .catch(error => {
  247. if (error.response && error.response.data && error.response.data.msg) {
  248. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  249. return;
  250. }
  251. message.error(lang[error.message] || ERROR_DEFAULT);
  252. })
  253. .finally(() => {
  254. this.handleChangeLoading("sCommentFavor", false);
  255. });
  256. }
  257. /**
  258. * 回复 点赞/取消点赞
  259. * @param {string} replyId replyId
  260. * @param {string} commentId commentId
  261. * @param {boolean} favored 是否已经点过赞
  262. */
  263. sReplyFavor(replyId, commentId, favored) {
  264. this.handleChangeLoading("sReplyFavor", true);
  265. console.log("replyId, commentId ", replyId, commentId);
  266. const { API } = this.props;
  267. axios(`${API}/replies/${replyId}/favor`, {
  268. method: favored ? "delete" : "put",
  269. withCredentials: true
  270. })
  271. .then(response => {
  272. console.log("response: ", response);
  273. message.success(favored ? "取消点赞成功!" : "点赞成功!");
  274. // TODO: (2018.07.20 node) 对评论的回复点赞,报错
  275. // // 更新 list 中的该项数据的 favored
  276. // const list = this.state.list.map(item => {
  277. // if (item.id === replyId) {
  278. // item.favored = !favored;
  279. // item.favor_count += favored ? -1 : 1;
  280. // }
  281. // return item;
  282. // });
  283. // this.setState({ list });
  284. })
  285. .catch(error => {
  286. if (error.response && error.response.data && error.response.data.msg) {
  287. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  288. return;
  289. }
  290. message.error(lang[error.message] || ERROR_DEFAULT);
  291. })
  292. .finally(() => {
  293. this.handleChangeLoading("sReplyFavor", false);
  294. });
  295. }
  296. /**
  297. * 获取 OSS 上传的参数
  298. */
  299. sOssSts() {
  300. this.handleChangeLoading("sOssSts", true);
  301. const { API } = this.props;
  302. axios
  303. .get(`${API}/oss/sts`)
  304. .then(response => {
  305. this.setState({ oss: { ...response.data } });
  306. })
  307. .catch(error => {
  308. if (error.response && error.response.data && error.response.data.msg) {
  309. message.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  310. return;
  311. }
  312. message.error(lang[error.message] || ERROR_DEFAULT);
  313. })
  314. .finally(() => {
  315. this.handleChangeLoading("sOssSts", false);
  316. });
  317. }
  318. render() {
  319. // 添加到 Context 的数据
  320. const value = {
  321. ...this.state,
  322. ...this.props,
  323. sCreateComment: this.sCreateComment,
  324. sGetComment: this.sGetComment,
  325. sCommentFavor: this.sCommentFavor,
  326. sReplyFavor: this.sReplyFavor,
  327. sCreateReply: this.sCreateReply,
  328. sGetReply: this.sGetReply,
  329. sOssSts: this.sOssSts
  330. };
  331. return (
  332. <CommentContext.Provider value={value}>
  333. <div className="comment">
  334. {this.props.showHeader && (
  335. <div style={{ marginBottom: 15 }}>
  336. <Tag className="comment-header-tag">留言</Tag>
  337. <span className="comment-header-tip">口碑</span>
  338. <span className="comment-header-text">
  339. (全站挑出毛病或提出合理建议,奖励10到100元红包)
  340. </span>
  341. </div>
  342. )}
  343. {this.props.showEditor && (
  344. <CommentInput content={this.props.children} />
  345. )}
  346. {this.props.showList && (
  347. <div style={{ marginTop: 20 }}>
  348. <CommentList />
  349. </div>
  350. )}
  351. </div>
  352. </CommentContext.Provider>
  353. );
  354. }
  355. }
  356. App.propTypes = {
  357. type: PropTypes.number.isRequired, // 评论的 type
  358. businessId: PropTypes.string.isRequired, // 评论的 business_id
  359. API: PropTypes.string, // 评论的 API 前缀
  360. showList: PropTypes.bool, // 是否显示评论列表
  361. showEditor: PropTypes.bool, // 是否显示评论输入框
  362. showHeader: PropTypes.bool // 是否显示评论顶部的提示
  363. };
  364. App.defaultProps = {
  365. API: "http://api.links123.net/comment/v1",
  366. showList: true,
  367. showEditor: true,
  368. showHeader: true
  369. };
  370. export { Editor };
  371. export default App;