forgeplus-react-v2/src/components/commentsByZone/CommentsList.jsx

261 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import { Button, Popconfirm, Input, message, Spin, Pagination } from 'antd';
import { getImageUrl, timeAgo, isPhone } from 'educoder';
import RenderHtml from '../render-html';
import Attachment from '../../forge/Upload/attachment';
import EditComment from './EditComment';
import CheckProfile from '../../forge/Component/ProfileModal/Profile';
import './comments.scss';
import { Link } from 'react-router-dom';
import { normalizeUrl } from '../../forge/common/util';
import { COMMENT_PAGE_SIZE, COMMENT_CATEGORY } from './constants';
import * as commentApi from './api';
/**
* 通用评论列表组件
* @param {Object} props - 组件属性
* @property {string} type - 评论类型:'news' | 'project' | 'resource' | 'skill'
* @property {string|number} id - 资源ID文章ID、项目ID等
* @property {Object} [extraParams] - 额外的业务参数
* @property {string} [owner] - 所有者/组织名(用于 Markdown 渲染)
* @property {string} [projectsId] - 项目 ID用于 Markdown 渲染)
* @property {Object} current_user - 当前用户信息
* @property {string} current_user.login - 用户登录名
* @property {boolean} current_user.admin - 是否平台管理员
* @property {string} current_user.image_url - 用户头像URL
* @property {string} current_user.username - 用户姓名
* @property {boolean} [isManager] - 是否为仓库/项目管理员
* @property {Function} [showNotification] - 显示通知
* @property {Function} [showLoginDialog] - 显示登录弹窗
* @property {string} [author] - 评论作者(用于权限判断)
*/
function CommentsList(props){
const {type, id, extraParams, owner, projectsId, current_user, isManager, showLoginDialog, author, showNotification} = props;
const {login, admin, image_url, username} = current_user;
const [category, setCategory] = useState('comment');
const [journals, setJournals] = useState(undefined);
// 是否展示新建/编辑评论markdown部分新建评论1 编辑父级评论2 回复评论3 编辑回复内容4 回复子机评论5
const [showEdit, setShowEdit] = useState(false);
const [parentId, setParentId] = useState(undefined);
const [replyId, setReplyId] = useState(undefined);
// 修改评论id
const [updateId, setUpdateId] = useState(undefined);
// 操作日志展开效果
const [open, setOpen] = useState(undefined);
// 加载中效果缓冲css 线条效果)
const [spin, setSpin] = useState(false);
// 评论总数
const [journalsCount, setJournalsCount] = useState(undefined);
// 分页
const limit = COMMENT_PAGE_SIZE;
const [page, setPage] = useState(1);
const [totalCount, setTotalCount] = useState(undefined);
const [reload, setReload] = useState(undefined);
// 根据 type 加载评论列表
useEffect(()=>{
setSpin(true);
let params = {
limit: limit,
page: page
};
commentApi.getCommentList(type, id, page, limit).then(res=>{
if(res && res.data && res.data.data){
const {journals: journalList, total_comment_journals_count, total_count} = res.data.data;
if(category === COMMENT_CATEGORY.ALL){
const array = [];
let start = undefined;
let isJournalCount = 0;
journalList.map((item, index)=>{
!isJournalCount && (start = index);
item.is_journal_detail && (isJournalCount++);
if(!item.is_journal_detail && isJournalCount < 10){
isJournalCount = 0;
return;
}
if(isJournalCount >= 10 && (!item.is_journal_detail || journalList.length-1 === index)){
array.push({start, count: isJournalCount});
isJournalCount = 0;
}
});
array.map((item, i)=>{
journalList[item.start].numCount = item.count-1;
for (let index = 0; index < item.count; index++) {
journalList[item.start+index].start = journalList[item.start].id;
journalList[item.start+index].closeAndSpan = true;
}
});
}
setTotalCount(total_count);
setJournalsCount(total_comment_journals_count);
setJournals(journalList);
setSpin(false);
}
}).catch(err => {
console.error('获取评论列表失败:', err);
setSpin(false);
message.error('获取评论列表失败');
});
}, [type, id, reload, category, page]);
// 刷新评论列表的回调
const reloadComment = () => {
setReload(Math.random());
};
function commentCtx(v){
return <RenderHtml owner={owner} projectsId={projectsId} className="break_word_comments imageLayerParent commentRenderHtml" value={v} url={{pathname: ''}}/>;
};
// 删除评论内容
function deleteCommentFunc(commentId){
commentApi.deleteComment(type, id, commentId).then(res=>{
if(res && res.data && res.data.code === 200){
reloadComment();
message.success('删除成功');
}
}).catch(err => {
console.error('删除评论失败:', err);
message.error('删除评论失败');
});
};
// markdown 取消操作
function cancelMd(){
setShowEdit(false);
};
// 创建不包含已解构属性的 props 对象传递给子组件
const childProps = {
...props,
type,
id,
extraParams,
owner,
projectsId,
current_user,
isManager,
showLoginDialog,
author,
showNotification,
reloadComment,
cancelMd
};
return(
(isPhone() && journalsCount === 0) ? '' : <div className="commentListBox info_text">
{/* 评论快速入口-仅有评论且登录时展示 */}
{login && category !== COMMENT_CATEGORY.OPERATE && journalsCount > 0 && !isPhone() && <div className={`pb30 ${journalsCount > 0 && 'pt15'}`}><div className='gotoComment'>点击<a className='ml5' href='#addComments'>添加评论</a></div></div>}
{/* 评论/操作日志 展示列表 */}
<Spin spinning={spin}>
<div className={`issueCommentsBox ${category === COMMENT_CATEGORY.COMMENT && !spin && 'justComment'}`}>
{journals && (journals.length > 0 && journals.map(item => {return <div key={item.id} className='commentContentBox pb30'>
{/* 评论 */}
<div className='commentOperationBor'></div>
{
item.user.userName ?
<Link to={`/${item.user.userName}`}><img src={normalizeUrl(item.user.avatar)} alt="" className='commentUserImg mr15'/></Link>
:
<span><img src={normalizeUrl(item.user.avatar)} alt="" className='commentUserImg mr15'/></span>
}
<div className='commentContentRight'>
{/* 判断是否是编辑状态 */}
{(showEdit === 2 && updateId === item.id) ? <div className='mt15 mr20'><EditComment extraParams={extraParams} {...childProps} updateId={updateId} content={item.notes} defaultFileList={item.attachments} showUserImg={false}/></div> : <div>
<div className='commentContent'>
<div className='flexCenter font-14'>
<div>
{item.user.userName ? <Link to={`/${item.user.userName}`}>{item.user.nickName}</Link>:<span>{item.user.nickName}</span>}
<span className='ml15 timeAgo font-14'>{timeAgo(item.created_at)}</span>
</div>
{login && !isPhone() && <div>
{/* 平台管理员/仓库管理员/发布评论者/issue创建者 */}
{(admin || isManager || login === item.user.userName || username === author) && <Popconfirm
placement="bottom"
title={`确定要删除此条评论吗?${item.children_journals.length > 0 ? '子评论也将被一起删除。' : ''}`}
okText="是"
cancelText="否"
onConfirm={() => deleteCommentFunc(item.id)}
>
<Button type='link' className='color-grey-89'><i className='iconfont icon-fuzhi-shanchu font-14 mr8'></i></Button>
</Popconfirm>}
{/* 仅评论者可修改 */}
{login === item.user.userName && <Button type='link' className='color-grey-89' onClick={()=>{setUpdateId(item.id); setShowEdit(2)}}><i className='iconfont icon-a-bianji12 font-14 mr8'></i></Button>}
<CheckProfile {...props} sureFunc={()=>{setParentId(item.id); setReplyId(item.id); setShowEdit(3)}}><Button type='link' className='color-grey-89'><i className='iconfont icon-a-xiaoxi1 font-14 mr8'></i></Button></CheckProfile>
</div>}
</div>
<div className='contentHtml mb5'>{commentCtx(item.notes)}</div>
{item && item.attachments && item.attachments.length > 0 && <div className='attachmentBox mb5'><Attachment
attachments={item.attachments}
showNotification={showNotification}
canDelete={false}
/></div>}
</div>
</div>}
{showEdit === 3 && replyId === item.id && <div className='contentHtml mr20'><EditComment extraParams={extraParams} {...childProps} parentId={parentId} replyId={replyId}/></div>}
{/* 评论回复部分 */}
{item.children_journals.map(i =>{
return <div className='commentReply' key={i.id}>
{(showEdit === 4 && updateId === i.id) ? <div className='mr20'><EditComment extraParams={extraParams} {...childProps} updateId={updateId} content={i.notes} defaultFileList={i.attachments}/></div> : <div>
<div className='flexCenter'>
<div>
<Link to={`/${i.user.userName}`}><img src={normalizeUrl(i.user.avatar)} alt="" className='commentUserImg mr8'/></Link>
<Link to={`/${i.user.userName}`}>{i.user.nickName}</Link>
{i.reply_user && <span className='ml5 timeAgo mr3'>回复</span>}
<span>{i.reply_user && i.reply_user.name}</span>
<span className='ml15 timeAgo font-14'>{timeAgo(i.created_at)}</span>
</div>
{login && !isPhone() && <div>
{/* 平台管理员/仓库管理员/发布评论者/回复评论者/issue创建者 */}
{(admin || isManager || login === i.user.userName || (i.reply_user && login === i.reply_user.login) || username === author) && <Popconfirm
placement="bottom"
title={"确定要删除当前回复吗?"}
okText="是"
cancelText="否"
onConfirm={() => deleteCommentFunc(i.id)}
>
<Button type='link' className='color-grey-89'><i className='iconfont icon-fuzhi-shanchu font-14 mr8'></i></Button>
</Popconfirm>}
{/* 仅回复评论者可修改 */}
{login === i.user.userName && <Button type='link' className='color-grey-89' onClick={()=>{setUpdateId(i.id); setShowEdit(4)}}><i className='iconfont icon-a-bianji12 font-14 mr8'></i></Button>}
<CheckProfile {...props} sureFunc={()=>{setParentId(item.id);setReplyId(i.id); setShowEdit(5)}}>
<Button type='link' className='color-grey-89'><i className='iconfont icon-a-xiaoxi1 font-14 mr8'></i></Button>
</CheckProfile>
</div>}
</div>
<div className='contentHtml mt5 mb10'>{commentCtx(i.notes)}</div>
{i && i.attachments && i.attachments.length > 0 && <div className='attachmentBox'><Attachment
attachments={i.attachments}
showNotification={showNotification}
canDelete={false}
/></div>}
</div>}
{showEdit === 5 && replyId === i.id && <div className='contentHtml mr20'><EditComment extraParams={extraParams} {...childProps} parentId={parentId} replyId={replyId}/></div>}
</div>})}
</div>
</div>}))}
</div>
</Spin>
{totalCount > limit && <div className='mt20 paginationIssueComment mb20'>
<Pagination simple current={page} pageSize={limit} total={totalCount} onChange={(page)=>setPage(page)}/>
</div>}
{/* 添加评论 */}
{category !== COMMENT_CATEGORY.OPERATE && !isPhone() && <div className="pb30">
{login ? showEdit === 1 ? <EditComment extraParams={extraParams} {...childProps}/> : <div className="addComments" id='addComments'>
<img src={getImageUrl(image_url)} alt="" />
<div style={{flex:1}}>
<CheckProfile {...props} sureFunc={()=>{setShowEdit(1);}}>
<Input className='addCommentBox' placeholder="添加评论"/>
</CheckProfile>
</div>
</div> : <div className='unLoginComment font-15 pl20'>
{/* 未登录用户 */}
<a className='mr5 loginBtn' onClick={()=>{showLoginDialog()}}>登录</a>
</div>}
</div>}
</div>
);
};
export default CommentsList;