import React, { Component } from "react"; import PropTypes from "prop-types"; import { message } from "antd"; import axios from "axios"; import { ERROR_DEFAULT, LIMIT } from "./constant"; import { CommentContext } from "./Comment"; import { isFunction } from "./helper"; import CommentInput from "./components/CommentInput"; import CommentList from "./components/CommentList"; import Editor from "./components/Editor"; import RenderText from "./components/RenderText"; import lang from "./lang"; import "./App.css"; // import styles from "./App.module.css"; class App extends Component { constructor(props) { super(props); this.state = { loading: {}, // oss 配置 oss: {}, // 评论数据 list: [], page: 1, total: 0, // 是否没有更多评论了 isNoMoreComment: false }; this.handleChangeLoading = this.handleChangeLoading.bind(this); this.sCreateComment = this.sCreateComment.bind(this); this.sDeleteComment = this.sDeleteComment.bind(this); this.sCommentFavor = this.sCommentFavor.bind(this); this.sCreateReply = this.sCreateReply.bind(this); this.sDeleteReply = this.sDeleteReply.bind(this); this.errorHandler = this.errorHandler.bind(this); this.sGetComment = this.sGetComment.bind(this); this.sReplyFavor = this.sReplyFavor.bind(this); this.sGetReply = this.sGetReply.bind(this); this.sOssSts = this.sOssSts.bind(this); } componentWillMount() { this.axios = axios; this.axios.defaults.withCredentials = true; if (this.props.token) { this.axios.defaults.headers.common["Authorization"] = `Bearer ${ this.props.token }`; } } componentDidMount() {} error(msg, info = {}) { if (this.props.showError) { message.error(msg); } if (this.props.onError) { this.props.onError(msg, info); } } errorHandler(error) { if (error.response && error.response.data && error.response.data.msg) { this.error(lang[error.response.data.msg] || ERROR_DEFAULT, { response: error.response }); return; } this.error(lang[error.message] || ERROR_DEFAULT, { response: error.response }); } /** * 改变 loading 状态 * @param {string} key key * @param {string} value value */ handleChangeLoading(key, value) { const { loading } = this.state; loading[key] = value; this.setState({ loading }); } /** * 获取评论列表 */ sGetComment({ page = 1 } = {}) { const { pageType } = this.props; this.handleChangeLoading("sGetComment", true); const { API, type, businessId } = this.props; this.axios .get( `${API}/comments?type=${type}&business_id=${businessId}&page=${page}&limit=${LIMIT}` ) .then(response => { const { list, page, total } = response.data; if (list) { let newList = list; let { list: oldList } = this.state; if (pageType === "more") { if (page > 1) { // 删除临时数据 oldList = oldList.filter(o => !o.isTemporary); newList = oldList.concat(newList); } } else if (pageType === "pagination") { // TODO 滚动到顶部 window.scrollTo(0, 0); } this.setState({ list: newList, page, total }); } else { message.info("没有更多评论了"); this.setState({ isNoMoreComment: true }); } }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sGetComment", false); }); } /** * 获取更多回复 */ sGetReply({ commentId, page = 1 } = {}) { this.handleChangeLoading("sGetReply", true); const { API } = this.props; this.axios .get(`${API}/replies?comment_id=${commentId}&page=${page}&limit=${LIMIT}`) .then(response => { if (!response.data.list) { message.info("没有更多数据了!"); } const list = this.state.list.map(item => { if (item.id === commentId) { if (!item.replies) item.replies = []; if (response.data.list) { if (page === 1) { // 如果当前页数为第一页,则清空当前所有的 replies // 并将获取到的 replies 存放在 state item.replies = response.data.list; } else { item.replies = item.replies .filter(o => !o.isTemporary) .concat(response.data.list); // 如果当前页数非第一页,则合并 replies } item.reply_count = response.data.total; item.reply_page = response.data.page; } else { item.isNoMoreReply = true; } } return item; }); this.setState({ list }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sGetReply", false); }); } /** * 添加评论 * @param {object} {content} comment content */ sCreateComment({ content } = {}, cb) { if (!content) return this.error("评论内容不能为空 "); this.handleChangeLoading("sCreateComment", true); const { API, type, businessId } = this.props; this.axios(`${API}/comments`, { method: "post", data: { type, business_id: businessId, content }, withCredentials: true }) .then(response => { if (this.props.showAlertComment) { message.success("评论成功!"); } if (isFunction(cb)) cb(); // 将数据写入到 list 中 // 临时插入 // 等到获取数据之后,删除临时数据 const { list, total } = this.state; list.unshift({ ...response.data, isTemporary: true // 临时的数据 }); this.setState({ list, total: total + 1 }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sCreateComment", false); }); } /** * 删除评论 */ sDeleteComment(commentId) { this.handleChangeLoading("sDeleteComment", true); const { API } = this.props; this.axios(`${API}/comments/${commentId}`, { method: "delete", withCredentials: true }) .then(() => { const { list, total } = this.state; const res = list.filter(item => item.id !== commentId); this.setState({ list: res, total: total - 1 }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sDeleteComment", false); }); } /** * 添加回复 * 回复评论/回复回复 * @param {object} data { comment_id, content, [reply_id] } */ sCreateReply(data, cb) { if (!data.content) return this.error("回复内容不能为空 "); this.handleChangeLoading("sCreateReply", true); const { API } = this.props; this.axios(`${API}/replies`, { method: "post", data, withCredentials: true }) .then(response => { if (this.props.showAlertReply) { message.success("回复成功!"); } if (isFunction(cb)) cb(); // 将数据写入到 list 中 // 临时插入 // 等到获取数据之后,删除临时数据 const list = this.state.list.map(item => { if (item.id === data.comment_id) { if (!item.replies) item.replies = []; item.replies.push({ ...response.data, isTemporary: true // 临时的数据 }); item.reply_count += 1; } return item; }); this.setState({ list }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sCreateReply", false); }); } /** * 删除回复 * @param {*} replyId * @param {*} commentId */ sDeleteReply(replyId, commentId) { this.handleChangeLoading("sDeleteReply", true); const { API } = this.props; this.axios(`${API}/replies/${replyId}?CommentID=${commentId}`, { method: "delete", withCredentials: true }) .then(() => { const list = this.state.list.map(item => { if (item.id === commentId) { const replies = item.replies.filter(item => item.id !== replyId); item.replies = replies; item.reply_count -= 1; } return item; }); this.setState({ list }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sDeleteReply", false); }); } /** * 评论 点赞/取消点赞 * @param {string} commentId { commentId } * @param {boolean} favored 是否已经点过赞 */ sCommentFavor(commentId, favored) { this.handleChangeLoading("sCommentFavor", true); const { API } = this.props; this.axios(`${API}/comments/${commentId}/favor`, { method: favored ? "delete" : "put", withCredentials: true }) .then(response => { if (this.props.showAlertFavor) { message.success(favored ? "取消点赞成功!" : "点赞成功!"); } // 更新 list 中的该项数据的 favored const list = this.state.list.map(item => { if (item.id === commentId) { item.favored = !favored; item.favor_count += favored ? -1 : 1; } return item; }); this.setState({ list }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sCommentFavor", false); }); } /** * 回复 点赞/取消点赞 * @param {string} replyId replyId * @param {string} commentId commentId * @param {boolean} favored 是否已经点过赞 */ sReplyFavor(replyId, commentId, favored) { this.handleChangeLoading("sReplyFavor", true); const { API } = this.props; this.axios(`${API}/replies/${replyId}/favor`, { method: favored ? "delete" : "put", data: { comment_id: commentId }, withCredentials: true }) .then(response => { message.success(favored ? "取消点赞成功!" : "点赞成功!"); // 更新 list 中的该项数据的 favored const list = this.state.list.map(item => { if (item.id === commentId) { item.replies = item.replies.map(r => { if (r.id === replyId) { r.favored = !favored; // r.favor_count = response.data.favor_count; // 点赞数 +1,而不是使用后端返回的点赞数 // 不然如果返回的不是增加 1,用户可能以为程序错误 r.favor_count += favored ? -1 : 1; } return r; }); } return item; }); this.setState({ list }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sReplyFavor", false); }); } /** * 获取 OSS 上传的参数 */ sOssSts() { this.handleChangeLoading("sOssSts", true); const { API } = this.props; this.axios .get(`${API}/oss/sts`) .then(response => { this.setState({ oss: { ...response.data } }); }) .catch(this.errorHandler) .finally(() => { this.handleChangeLoading("sOssSts", false); }); } render() { // 添加到 Context 的数据 const value = { ...this.state, ...this.props, sCreateComment: this.sCreateComment, sGetComment: this.sGetComment, sCommentFavor: this.sCommentFavor, sReplyFavor: this.sReplyFavor, sCreateReply: this.sCreateReply, sGetReply: this.sGetReply, sOssSts: this.sOssSts, sDeleteComment: this.sDeleteComment, sDeleteReply: this.sDeleteReply }; return (
{this.props.showEditor && ( )} {this.props.showList && (
)}
); } } App.propTypes = { type: PropTypes.number.isRequired, // 评论的 type businessId: PropTypes.string.isRequired, // 评论的 business_id API: PropTypes.string, // 评论的 API 前缀 showList: PropTypes.bool, // 是否显示评论列表 showEditor: PropTypes.bool, // 是否显示评论输入框 showAlertComment: PropTypes.bool, // 评论成功之后,是否通过 Antd 的 Message 组件进行提示 showAlertReply: PropTypes.bool, // 回复成功之后,是否通过 Antd 的 Message 组件进行提示 showAlertFavor: PropTypes.bool, // 点赞/取消点赞成功之后,是否通过 Antd 的 Message 组件进行提示 showError: PropTypes.bool, // 是否使用Antd的Message组件提示错误信息 onError: PropTypes.func, // 错误回调, 出错了会被调用 userId: PropTypes.number, // 用户id, comment内部不维护用户id, 调用组件时传递过来, 目前用于判断是否显示删除按钮 pageType: PropTypes.string, // 分页类型 page: PropTypes.number, // 页码 onPageChange: PropTypes.func // 页码变化回调 }; App.defaultProps = { API: "//api.links123.net/comment/v1", showList: true, showEditor: true, showAlertComment: false, showAlertReply: false, showAlertFavor: false, showError: true, pageType: "more", onPageChange: page => {} }; export { Editor, RenderText }; export default App;