完善圈子

This commit is contained in:
秋水浮尘
2024-07-29 23:56:19 +08:00
parent 2dd818762e
commit cd1e9fdfe5
3 changed files with 254 additions and 88 deletions

View File

@@ -33,6 +33,7 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
margin-bottom: 20px; margin-bottom: 20px;
margin-top: 10px;
.text-area-outer-box{ .text-area-outer-box{
border: 1px solid lightgray; border: 1px solid lightgray;
border-radius: 8px; border-radius: 8px;
@@ -46,13 +47,23 @@
} }
} }
.comment-list-wrapper{ .comment-list-wrapper{
.comment-list-item{ .comment-item-wrapper{
margin-top: 30px; margin-top: 30px;
.ope-btn-group{ width: 100%;
gap: 16px; .comment-detail-wrapper{
color: gray; flex: 1;
.reply-btn{ }
.comment-content{
margin: 10px 0;
}
.comment-bottom-wrapper{
color: rgb(211, 211, 211);
.bottom-btn{
margin-left: 10px;
cursor: pointer; cursor: pointer;
&:hover, &.active{
color: #1677ff;
}
} }
} }
} }

View File

@@ -1,26 +1,16 @@
import { Button, Input } from 'antd' import { Button, Input, Upload, Image } from 'antd'
import { useState, useEffect, FC } from 'react' import { useState, useEffect, FC } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { commentSave, getCommentList } from '../../service' import { commentSave, getCommentList } from '../../service'
import { CommentOutlined, FileImageOutlined, PlusOutlined, SmileOutlined } from '@ant-design/icons'
import './index.less' import './index.less'
import { CommentOutlined, FileImageOutlined, SmileOutlined } from '@ant-design/icons'
const CommentList: FC<any> = props => { const CommentInput = ({ momentId, getList, replyType, targetId = '' }) => {
const [comment, setComment] = useState<string>('')
const { userInfo } = useSelector(store => store.userInfo) const { userInfo } = useSelector(store => store.userInfo)
const { momentId } = props const userInfoStorage = localStorage.getItem('userInfo')
const [replyList, setReplyList] = useState([]) const { tokenValue = '' } = userInfoStorage ? JSON.parse(userInfoStorage) : {}
const [comment, setComment] = useState('') const [imgList, setImgList] = useState([])
const getList = async () => {
const res = await getCommentList({ id: momentId })
if (res.success && res.data) {
setReplyList(res.data)
} else {
setReplyList([])
}
}
useEffect(() => {
getList()
}, [])
const changeComment = e => { const changeComment = e => {
setComment(e.target.value) setComment(e.target.value)
@@ -29,83 +19,232 @@ const CommentList: FC<any> = props => {
const saveComment = () => { const saveComment = () => {
const params = { const params = {
momentId, momentId,
replyType: 2, replyType,
content: comment, content: comment,
targetId: 12 targetId
}
if (imgList.length) {
params.picUrlList = imgList.map(item => item.response.data)
} }
commentSave(params).then(() => { commentSave(params).then(() => {
setComment('')
setImgList([])
getList() getList()
}) })
} }
const uploadButton = (
<button style={{ border: 0, background: 'none' }} type='button'>
<PlusOutlined />
</button>
)
const handleChange = ({ fileList }) => {
setImgList(fileList)
}
return ( return (
<div className='comment-list-box'> <div className='comment-wrapper'>
<div className='top-arrow'></div> <img src={userInfo?.avatar} className='avatar' />
<div className='comment-number'> {replyList.length}</div> <div className='text-area-outer-box'>
<div className='comment-wrapper'> <div className='text-area-box'>
<img src={userInfo?.avatar} className='avatar' /> <Input.TextArea
<div className='text-area-outer-box'> onChange={changeComment}
<div className='text-area-box'> placeholder='和平发言'
<Input.TextArea style={{ border: 'none', paddingLeft: 0 }}
onChange={changeComment} maxLength={1000}
placeholder='和平发言' value={comment}
style={{ border: 'none', paddingLeft: 0 }} />
/> <Upload
name='uploadFile'
action='/oss/upload'
listType='picture-card'
fileList={imgList}
withCredentials
headers={{
satoken: 'jichi ' + tokenValue
}}
data={{
bucket: 'user',
objectName: 'icon'
}}
onChange={handleChange}
>
{imgList.length >= 8 || imgList.length === 0 ? null : uploadButton}
</Upload>
</div>
<div className='comment-bottom'>
<div className='icon-box flex'>
<div style={{ marginRight: 20 }}>
<SmileOutlined />
</div>
<div>
<Upload
name='uploadFile'
className='avatar-uploader'
accept='image/*'
showUploadList={false}
withCredentials
action='/oss/upload'
headers={{
satoken: 'jichi ' + tokenValue
}}
data={{
bucket: 'user',
objectName: 'icon'
}}
onChange={handleChange}
>
<div>
<FileImageOutlined />
{/* <span style={{ marginLeft: '8px' }}>图片</span> */}
</div>
</Upload>
{/* <FileImageOutlined /> */}
</div>
</div> </div>
<div className='comment-bottom'> <div className='submit-btn-box flex'>
<div className='icon-box flex'> <div className='text-num-box' style={{ marginRight: 20 }}>
<div style={{ marginRight: 20 }}> {comment.length}/1000
<SmileOutlined />
</div>
<div>
<FileImageOutlined />
</div>
</div>
<div className='submit-btn-box flex'>
<div className='text-num-box'>1/1000</div>
<Button onClick={saveComment} type='primary'>
</Button>
</div> </div>
<Button onClick={saveComment} type='primary'>
</Button>
</div> </div>
</div> </div>
</div> </div>
</div>
)
}
function formatDistanceToNow(date) {
if (!date) return
const delta = Math.abs(Date.now() - date)
if (delta < 30 * 1000) {
return '刚刚'
} else if (delta < 5 * 60 * 1000) {
return Math.round(delta / 1000) + '秒前'
} else if (delta < 60 * 60 * 1000) {
return Math.round(delta / 60000) + '分钟前'
} else if (delta < 24 * 60 * 60 * 1000) {
return Math.round(delta / 3600000) + '小时前'
} else if (delta < 7 * 24 * 60 * 60 * 1000) {
return Math.round(delta / 86400000) + '天前'
} else {
return new Date(date).toLocaleDateString()
return '很久之前'
}
}
const CommentItem = ({
momentId,
id,
avatar,
userName,
content,
createdTime,
replyType,
getList,
children,
picUrlList
}) => {
const [active, setActive] = useState(false)
const toggleActive = () => {
setActive(!active)
}
return (
<div className={`comment-item-wrapper`}>
<div className='flex align-top'>
<img src={avatar} className='avatar' alt='头像' />
<div className='comment-detail-wrapper'>
<div className='title'>{userName}</div>
<div className='comment-content'>{content}</div>
{picUrlList?.length && (
<Image.PreviewGroup items={picUrlList}>
<div className='comment-img-list'>
{picUrlList.map((t: string) => (
<Image key={t} width={90} src={t} />
))}
</div>
</Image.PreviewGroup>
)}
<div className='comment-bottom-wrapper flex'>
<div>{formatDistanceToNow(createdTime) || '12小时前'}</div>
<div onClick={toggleActive} className={`bottom-btn ${active ? 'active' : ''}`}>
<CommentOutlined />
<span style={{ marginLeft: 5 }}>{replyType === 1 ? '评论' : '回复'}</span>
</div>
</div>
{active && (
<CommentInput momentId={momentId} getList={getList} targetId={id} replyType={2} />
)}
{children?.length
? children?.map(item => {
return <CommentItem key={item.id} {...item} getList={getList} />
})
: ''}
</div>
</div>
</div>
)
}
function flattenNestedObjects(items) {
const result = []
function traverse(items) {
items.forEach(item => {
// 创建一个新对象来存储当前项的属性(除了 children
const flatItem = {}
for (const key in item) {
if (key !== 'children') {
flatItem[key] = item[key]
}
}
// 将扁平化的对象添加到结果数组中
result.push(flatItem)
// 如果还有 children则递归调用 traverse
if (item.children) {
traverse(item.children)
}
})
}
// 从顶层对象开始遍历
traverse(items)
return result
}
const CommentList: FC<any> = props => {
const { momentId, replyCount } = props
const [replyList, setReplyList] = useState<any[]>([])
const getList = async () => {
const res = await getCommentList({ id: momentId })
if (res.success && res.data) {
const data = res.data.map(item => {
return {
...item,
children: flattenNestedObjects(item.children || [])
}
})
setReplyList(data)
} else {
setReplyList([])
}
}
useEffect(() => {
getList()
}, [])
return (
<div className='comment-list-box'>
<div className='top-arrow'></div>
<div className='comment-number'> {replyCount}</div>
<CommentInput momentId={momentId} getList={getList} targetId={momentId} replyType={1} />
<div className='comment-list-wrapper'> <div className='comment-list-wrapper'>
{replyList.map((item: Record<string, any>) => { {replyList.map((item: Record<string, any>) => {
return ( return <CommentItem key={item.id} momentId={momentId} getList={getList} {...item} />
<div key={item.id} className='comment-list-item flex align-top'>
<img src={item.avatar} className='avatar' />
<div>
<div>{item.userName}</div>
<div style={{ margin: '10px 0' }}>{item.content}</div>
<div className='ope-btn-group flex'>
<div>12</div>
<div className='reply-btn'>
<CommentOutlined />
&nbsp;
</div>
</div>
{item.children?.length &&
item.children.map(child => {
return (
<div key={child.id} className='comment-list-item flex align-top'>
<img src={child.avatar} className='avatar' />
<div>
<div>{child.userName}</div>
<div style={{ margin: '10px 0' }}>{child.content}</div>
<div className='ope-btn-group flex'>
<div>12</div>
<div className='reply-btn'>
<CommentOutlined />
&nbsp;
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
)
})} })}
</div> </div>
</div> </div>

View File

@@ -106,6 +106,8 @@ const Circle = () => {
} }
const res = await saveMoment(params) const res = await saveMoment(params)
if (res.success) { if (res.success) {
setComment('')
setImgList([])
getMomentList() getMomentList()
return message.success('发布成功') return message.success('发布成功')
} }
@@ -154,6 +156,7 @@ const Circle = () => {
className='top-text-area' className='top-text-area'
onFocus={() => toggleFocus(false)} onFocus={() => toggleFocus(false)}
onBlur={() => toggleFocus(true)} onBlur={() => toggleFocus(true)}
value={comment}
/> />
<Upload <Upload
name='uploadFile' name='uploadFile'
@@ -228,7 +231,7 @@ const Circle = () => {
description={item.content} description={item.content}
/> />
{item.picUrlList?.length && ( {item.picUrlList?.length && (
<Image.PreviewGroup items={previewList.list}> <Image.PreviewGroup items={item.picUrlList}>
<div className='img-list'> <div className='img-list'>
{item.picUrlList.map((t: string) => ( {item.picUrlList.map((t: string) => (
<Image key={t} width={110} src={t} /> <Image key={t} width={110} src={t} />
@@ -237,7 +240,18 @@ const Circle = () => {
</Image.PreviewGroup> </Image.PreviewGroup>
)} )}
<div className='card-footer'> <div className='card-footer'>
<a key='share' className='footer-item'> <a
key='share'
className='footer-item'
onClick={() =>
window.open(
'https://service.weibo.com/share/share.php?url=' +
encodeURIComponent(location.href) +
'&title=' +
item.content
)
}
>
<ShareAltOutlined /> <ShareAltOutlined />
<span style={{ marginLeft: 8 }}></span> <span style={{ marginLeft: 8 }}></span>
</a> </a>
@@ -253,7 +267,9 @@ const Circle = () => {
</span> </span>
</a> </a>
</div> </div>
{currentReplyCommentId === item.id && <CommentList momentId={item.id} />} {currentReplyCommentId === item.id && (
<CommentList momentId={item.id} replyCount={item.replyCount} />
)}
</Card> </Card>
) )
})} })}