通用评论 vedio

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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 RenderText from "./components/RenderText";
  12. import lang from "./lang";
  13. import "./App.css";
  14. class App extends Component {
  15. constructor(props) {
  16. super(props);
  17. this.state = {
  18. loading: {},
  19. // oss 配置
  20. oss: {},
  21. // 评论数据
  22. list: [],
  23. page: 1,
  24. total: 0,
  25. // 是否没有更多评论了
  26. isNoMoreComment: false
  27. };
  28. this.handleChangeLoading = this.handleChangeLoading.bind(this);
  29. this.sGetComment = this.sGetComment.bind(this);
  30. this.sGetReply = this.sGetReply.bind(this);
  31. this.sCreateComment = this.sCreateComment.bind(this);
  32. this.sCreateReply = this.sCreateReply.bind(this);
  33. this.sCommentFavor = this.sCommentFavor.bind(this);
  34. this.sReplyFavor = this.sReplyFavor.bind(this);
  35. this.sOssSts = this.sOssSts.bind(this);
  36. }
  37. componentWillMount() {
  38. this.axios = axios;
  39. this.axios.defaults.withCredentials = true;
  40. if (this.props.token) {
  41. this.axios.defaults.headers.common["Authorization"] = `Bearer ${
  42. this.props.token
  43. }`;
  44. }
  45. }
  46. componentDidMount() {}
  47. error(msg) {
  48. if (this.props.showError) {
  49. message.error(msg);
  50. }
  51. if (this.props.onError) {
  52. this.props.onError(msg);
  53. }
  54. }
  55. /**
  56. * 改变 loading 状态
  57. * @param {string} key key
  58. * @param {string} value value
  59. */
  60. handleChangeLoading(key, value) {
  61. const { loading } = this.state;
  62. loading[key] = value;
  63. this.setState({ loading });
  64. }
  65. /**
  66. * 获取评论列表
  67. */
  68. sGetComment({ page = 1 } = {}) {
  69. this.handleChangeLoading("sGetComment", true);
  70. const { API, type, businessId } = this.props;
  71. this.axios
  72. .get(
  73. `${API}/comments?type=${type}&business_id=${businessId}&page=${page}&limit=${LIMIT}`
  74. )
  75. .then(response => {
  76. const { list, page, total } = response.data;
  77. if (list) {
  78. let newList = list;
  79. if (page > 1) {
  80. let { list: oldList } = this.state;
  81. // 删除临时数据
  82. oldList = oldList.filter(o => !o.isTemporary);
  83. newList = oldList.concat(newList);
  84. }
  85. this.setState({
  86. list: newList,
  87. page,
  88. total
  89. });
  90. } else {
  91. message.info("没有更多评论了");
  92. this.setState({
  93. isNoMoreComment: true
  94. });
  95. }
  96. })
  97. .catch(error => {
  98. if (error.response && error.response.data && error.response.data.msg) {
  99. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  100. return;
  101. }
  102. this.error(lang[error.message] || ERROR_DEFAULT);
  103. })
  104. .finally(() => {
  105. this.handleChangeLoading("sGetComment", false);
  106. });
  107. }
  108. /**
  109. * 获取更多回复
  110. */
  111. sGetReply({ commentId, page = 1 } = {}) {
  112. this.handleChangeLoading("sGetReply", true);
  113. const { API } = this.props;
  114. this.axios
  115. .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`)
  116. .then(response => {
  117. if (!response.data.list) {
  118. message.info("没有更多数据了!");
  119. }
  120. const list = this.state.list.map(item => {
  121. if (item.id === commentId) {
  122. if (!item.replies) item.replies = [];
  123. if (response.data.list) {
  124. if (page === 1) {
  125. // 如果当前页数为第一页,则清空当前所有的 replies
  126. // 并将获取到的 replies 存放在 state
  127. item.replies = response.data.list;
  128. } else {
  129. item.replies = item.replies
  130. .filter(o => !o.isTemporary)
  131. .concat(response.data.list);
  132. // 如果当前页数非第一页,则合并 replies
  133. }
  134. item.reply_count = response.data.total;
  135. item.reply_page = response.data.page;
  136. } else {
  137. item.isNoMoreReply = true;
  138. }
  139. }
  140. return item;
  141. });
  142. this.setState({ list });
  143. })
  144. .catch(error => {
  145. if (error.response && error.response.data && error.response.data.msg) {
  146. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  147. return;
  148. }
  149. this.error(lang[error.message] || ERROR_DEFAULT);
  150. })
  151. .finally(() => {
  152. this.handleChangeLoading("sGetReply", false);
  153. });
  154. }
  155. /**
  156. * 添加评论
  157. * @param {object} {content} comment content
  158. */
  159. sCreateComment({ content } = {}, cb) {
  160. if (!content) return this.error("评论内容不能为空 ");
  161. this.handleChangeLoading("sCreateComment", true);
  162. const { API, type, businessId } = this.props;
  163. this.axios(`${API}/comments`, {
  164. method: "post",
  165. data: {
  166. type,
  167. business_id: businessId,
  168. content
  169. },
  170. withCredentials: true
  171. })
  172. .then(response => {
  173. if (this.props.showAlertComment) {
  174. message.success("评论成功!");
  175. }
  176. if (isFunction(cb)) cb();
  177. // 将数据写入到 list 中
  178. // 临时插入
  179. // 等到获取数据之后,删除临时数据
  180. const { list, total } = this.state;
  181. list.unshift({
  182. ...response.data,
  183. isTemporary: true // 临时的数据
  184. });
  185. this.setState({ list, total: total + 1 });
  186. })
  187. .catch(error => {
  188. if (error.response && error.response.data && error.response.data.msg) {
  189. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  190. return;
  191. }
  192. this.error(lang[error.message] || ERROR_DEFAULT);
  193. })
  194. .finally(() => {
  195. this.handleChangeLoading("sCreateComment", false);
  196. });
  197. }
  198. /**
  199. * 添加回复
  200. * 回复评论/回复回复
  201. * @param {object} data { comment_id, content, [reply_id] }
  202. */
  203. sCreateReply(data, cb) {
  204. if (!data.content) return this.error("回复内容不能为空 ");
  205. this.handleChangeLoading("sCreateReply", true);
  206. const { API } = this.props;
  207. this.axios(`${API}/replies`, {
  208. method: "post",
  209. data,
  210. withCredentials: true
  211. })
  212. .then(response => {
  213. if (this.props.showAlertReply) {
  214. message.success("回复成功!");
  215. }
  216. if (isFunction(cb)) cb();
  217. // 将数据写入到 list 中
  218. // 临时插入
  219. // 等到获取数据之后,删除临时数据
  220. const list = this.state.list.map(item => {
  221. if (item.id === data.comment_id) {
  222. if (!item.replies) item.replies = [];
  223. item.replies.push({
  224. ...response.data,
  225. isTemporary: true // 临时的数据
  226. });
  227. item.reply_count += 1;
  228. }
  229. return item;
  230. });
  231. this.setState({ list });
  232. })
  233. .catch(error => {
  234. if (error.response && error.response.data && error.response.data.msg) {
  235. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  236. return;
  237. }
  238. this.error(lang[error.message] || ERROR_DEFAULT);
  239. })
  240. .finally(() => {
  241. this.handleChangeLoading("sCreateReply", false);
  242. });
  243. }
  244. /**
  245. * 评论 点赞/取消点赞
  246. * @param {string} commentId { commentId }
  247. * @param {boolean} favored 是否已经点过赞
  248. */
  249. sCommentFavor(commentId, favored) {
  250. this.handleChangeLoading("sCommentFavor", true);
  251. const { API } = this.props;
  252. this.axios(`${API}/comments/${commentId}/favor`, {
  253. method: favored ? "delete" : "put",
  254. withCredentials: true
  255. })
  256. .then(response => {
  257. if (this.props.showAlertFavor) {
  258. message.success(favored ? "取消点赞成功!" : "点赞成功!");
  259. }
  260. // 更新 list 中的该项数据的 favored
  261. const list = this.state.list.map(item => {
  262. if (item.id === commentId) {
  263. item.favored = !favored;
  264. item.favor_count += favored ? -1 : 1;
  265. }
  266. return item;
  267. });
  268. this.setState({ list });
  269. })
  270. .catch(error => {
  271. if (error.response && error.response.data && error.response.data.msg) {
  272. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  273. return;
  274. }
  275. this.error(lang[error.message] || ERROR_DEFAULT);
  276. })
  277. .finally(() => {
  278. this.handleChangeLoading("sCommentFavor", false);
  279. });
  280. }
  281. /**
  282. * 回复 点赞/取消点赞
  283. * @param {string} replyId replyId
  284. * @param {string} commentId commentId
  285. * @param {boolean} favored 是否已经点过赞
  286. */
  287. sReplyFavor(replyId, commentId, favored) {
  288. this.handleChangeLoading("sReplyFavor", true);
  289. const { API } = this.props;
  290. this.axios(`${API}/replies/${replyId}/favor`, {
  291. method: favored ? "delete" : "put",
  292. data: {
  293. comment_id: commentId
  294. },
  295. withCredentials: true
  296. })
  297. .then(response => {
  298. message.success(favored ? "取消点赞成功!" : "点赞成功!");
  299. // 更新 list 中的该项数据的 favored
  300. const list = this.state.list.map(item => {
  301. if (item.id === commentId) {
  302. item.replies = item.replies.map(r => {
  303. if (r.id === replyId) {
  304. r.favored = !favored;
  305. // r.favor_count = response.data.favor_count;
  306. // 点赞数 +1,而不是使用后端返回的点赞数
  307. // 不然如果返回的不是增加 1,用户可能以为程序错误
  308. r.favor_count += favored ? -1 : 1;
  309. }
  310. return r;
  311. });
  312. }
  313. return item;
  314. });
  315. this.setState({ list });
  316. })
  317. .catch(error => {
  318. if (error.response && error.response.data && error.response.data.msg) {
  319. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  320. return;
  321. }
  322. this.error(lang[error.message] || ERROR_DEFAULT);
  323. })
  324. .finally(() => {
  325. this.handleChangeLoading("sReplyFavor", false);
  326. });
  327. }
  328. /**
  329. * 获取 OSS 上传的参数
  330. */
  331. sOssSts() {
  332. this.handleChangeLoading("sOssSts", true);
  333. const { API } = this.props;
  334. this.axios
  335. .get(`${API}/oss/sts`)
  336. .then(response => {
  337. this.setState({ oss: { ...response.data } });
  338. })
  339. .catch(error => {
  340. if (error.response && error.response.data && error.response.data.msg) {
  341. this.error(lang[error.response.data.msg] || ERROR_DEFAULT);
  342. return;
  343. }
  344. this.error(lang[error.message] || ERROR_DEFAULT);
  345. })
  346. .finally(() => {
  347. this.handleChangeLoading("sOssSts", false);
  348. });
  349. }
  350. render() {
  351. // 添加到 Context 的数据
  352. const value = {
  353. ...this.state,
  354. ...this.props,
  355. sCreateComment: this.sCreateComment,
  356. sGetComment: this.sGetComment,
  357. sCommentFavor: this.sCommentFavor,
  358. sReplyFavor: this.sReplyFavor,
  359. sCreateReply: this.sCreateReply,
  360. sGetReply: this.sGetReply,
  361. sOssSts: this.sOssSts
  362. };
  363. return (
  364. <CommentContext.Provider value={value}>
  365. <div className="comment">
  366. {this.props.showEditor && (
  367. <CommentInput content={this.props.children} />
  368. )}
  369. {this.props.showList && (
  370. <div style={{ marginTop: 20 }}>
  371. <CommentList />
  372. </div>
  373. )}
  374. </div>
  375. </CommentContext.Provider>
  376. );
  377. }
  378. }
  379. App.propTypes = {
  380. type: PropTypes.number.isRequired, // 评论的 type
  381. businessId: PropTypes.string.isRequired, // 评论的 business_id
  382. API: PropTypes.string, // 评论的 API 前缀
  383. showList: PropTypes.bool, // 是否显示评论列表
  384. showEditor: PropTypes.bool, // 是否显示评论输入框
  385. showAlertComment: PropTypes.bool, // 评论成功之后,是否通过 Antd 的 Message 组件进行提示
  386. showAlertReply: PropTypes.bool, // 回复成功之后,是否通过 Antd 的 Message 组件进行提示
  387. showAlertFavor: PropTypes.bool, // 点赞/取消点赞成功之后,是否通过 Antd 的 Message 组件进行提示
  388. showError: PropTypes.bool, // 是否使用Antd的Message组件提示错误信息
  389. onError: PropTypes.func // 错误回调, 出错了会被调用
  390. };
  391. App.defaultProps = {
  392. API: "http://api.links123.net/comment/v1",
  393. showList: true,
  394. showEditor: true,
  395. showAlertComment: false,
  396. showAlertReply: false,
  397. showAlertFavor: false,
  398. showError: true
  399. };
  400. export { Editor, RenderText };
  401. export default App;