parent
0f21aa3586
commit
f5332c270b
After Width: | Height: | Size: 887 B |
After Width: | Height: | Size: 905 B |
@ -0,0 +1,66 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
commentCount: number;
|
||||||
|
thumbupCount: number;
|
||||||
|
haveThumbup: boolean;
|
||||||
|
className?: string;
|
||||||
|
onShowInput: () => void;
|
||||||
|
onShowAll: () => Promise<boolean>;
|
||||||
|
onThumbup: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
export default function ButtonBar({
|
||||||
|
commentCount,
|
||||||
|
thumbupCount,
|
||||||
|
haveThumbup,
|
||||||
|
className,
|
||||||
|
onShowAll,
|
||||||
|
onShowInput,
|
||||||
|
onThumbup,
|
||||||
|
}: Props) {
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(false);
|
||||||
|
const [thumbup, setThumbup] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleShowAll = async () => {
|
||||||
|
const res = await onShowAll();
|
||||||
|
setShowAll(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThumbup = async () => {
|
||||||
|
const res = await onThumbup();
|
||||||
|
if (res) setThumbup(!thumbup);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setThumbup(haveThumbup);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex flex-row items-start gap-[18px] ${className}`}>
|
||||||
|
{/* 回复 */}
|
||||||
|
<div
|
||||||
|
className="pt-[3px] text-[14px] leading-[19.6px] text-[rgba(0,0,0,0.4)] hover:text-theme cursor-pointer"
|
||||||
|
onClick={onShowInput}
|
||||||
|
>
|
||||||
|
回复
|
||||||
|
</div>
|
||||||
|
{/* 展开回复 */}
|
||||||
|
{commentCount > 1 && !showAll && (
|
||||||
|
<div className="pt-[3px] text-theme text-[14px] leading-[19.6px] cursor-pointer" onClick={handleShowAll}>
|
||||||
|
展开{commentCount}条回复
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 点赞 */}
|
||||||
|
<div className="flex flex-row items-center w-[49px] h-[24px] group cursor-pointer" onClick={handleThumbup}>
|
||||||
|
{thumbupCount > 0 && (
|
||||||
|
<div className="mr-[2px] pt-[5px] text-[rgba(0,0,0,0.7)] text-[12px] leading-[16.8px] group-hover:text-theme">
|
||||||
|
{thumbupCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`w-[24px] h-[24px] ${thumbup ? 'bg-[url(/img/icon/thumb-active.svg)]' : 'bg-[url(/img/icon/thumb.svg)]'} group-hover:bg-[bg-[url(/img/icon/thumb-active.svg)]]`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,3 +1,42 @@
|
|||||||
export default function CommentItem() {
|
'use client';
|
||||||
return <div></div>;
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { Button } from '@/components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSubmit: (value: string) => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CommentItem({ onSubmit }: Props) {
|
||||||
|
const [value, setValue] = useState<string>('');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
await onSubmit(value);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
value={value}
|
||||||
|
cols={30}
|
||||||
|
rows={10}
|
||||||
|
disabled={loading}
|
||||||
|
placeholder="发布评论"
|
||||||
|
className="w-[712px] h-[89px] p-[15px] radounded-[3px] bg-[#F2F3F7] text-[15px] leading-[21px] resize-none"
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={loading || !value}
|
||||||
|
className="w-[80px] h-[35px] my-[15px] rounded-[16px] text-[13px] leading-[18.2px]"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
/** 评论输入框 */
|
||||||
|
import { useState, useRef, useMemo, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { Avatar, Button, Input } from '@/components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
nickName: string;
|
||||||
|
avatar: string;
|
||||||
|
className?: string;
|
||||||
|
onSubmit: (value: string) => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CommentInput({ nickName, avatar, className, onSubmit }: Props) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [value, setValue] = useState<string>('');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const btnDisabled = useMemo(() => {
|
||||||
|
return loading && !value;
|
||||||
|
}, [loading, value]);
|
||||||
|
|
||||||
|
const handleReplySubmit = async () => {
|
||||||
|
if (!value) return;
|
||||||
|
setLoading(true);
|
||||||
|
await onSubmit(value);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current && inputRef.current.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${className}`}>
|
||||||
|
<div className={`flex flex-row items-center`}>
|
||||||
|
<Avatar size={40} src={avatar} className="mr-[6px]" />
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
disabled={loading}
|
||||||
|
value={value}
|
||||||
|
placeholder={` 回复 ${nickName}`}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
className="w-[600px] h-[48px] px-[14px] rounded-[3px] bg-[#F2F3F7] text-[rgba(0,0,0,0.4)] text-[14px] leading-[19.6px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={btnDisabled}
|
||||||
|
className="w-[80px] h-[35px] mt-[15px] rounded-[16px] text-[13px] leading-[18.2px]"
|
||||||
|
onClick={handleReplySubmit}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,3 +1,102 @@
|
|||||||
export default function CommentItem() {
|
'use client';
|
||||||
return <div></div>;
|
|
||||||
|
import { useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import ButtonBar from './ButtonBar';
|
||||||
|
import CommentInput from './CommentInput';
|
||||||
|
import SubCommentItem from './CommentSubItem';
|
||||||
|
|
||||||
|
import { Avatar } from '@/components';
|
||||||
|
import { apiCommentThumbup, apiCommentSave, apiGetSubComment } from '@/services';
|
||||||
|
|
||||||
|
// 回复
|
||||||
|
const handleReply = async (params: { content: string; journalId: string; parentId: string }) => {
|
||||||
|
await apiCommentSave(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CommentItem({
|
||||||
|
_id,
|
||||||
|
avatar,
|
||||||
|
nickName,
|
||||||
|
publishTime,
|
||||||
|
location,
|
||||||
|
content,
|
||||||
|
haveThumbup,
|
||||||
|
thumbupCount,
|
||||||
|
commentCount,
|
||||||
|
topChildrenComment,
|
||||||
|
journalId,
|
||||||
|
className,
|
||||||
|
}: Comment & { className?: string }) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(false);
|
||||||
|
const [showInput, setShowInput] = useState<boolean>(false);
|
||||||
|
const [subCommentList, setSubCommentList] = useState<Comment[]>(topChildrenComment ? [topChildrenComment] : []);
|
||||||
|
|
||||||
|
// 展示全部评论
|
||||||
|
const handleShowAll = async () => {
|
||||||
|
setShowAll(true);
|
||||||
|
const res = await apiGetSubComment({ parentId: _id, size: commentCount, page: 1 });
|
||||||
|
if (res.code === 200) setSubCommentList(res.data.rows);
|
||||||
|
return res.code === 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 展示回复框
|
||||||
|
const handleShowInput = () => {
|
||||||
|
setShowInput(true);
|
||||||
|
inputRef.current && inputRef.current.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交回复
|
||||||
|
const handleReplySubmit = async (value: string) => {
|
||||||
|
const res = await apiCommentSave({ content: value, journalId: journalId, parentId: _id });
|
||||||
|
if (res.code === 200) setShowInput(false);
|
||||||
|
return res.code === 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点赞
|
||||||
|
const handleThumbup = async () => {
|
||||||
|
const res = await apiCommentThumbup(_id);
|
||||||
|
return res.code === 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${className}`}>
|
||||||
|
{/* 一级评论 */}
|
||||||
|
<div className={`flex flex-row flex-shrink`}>
|
||||||
|
<Avatar size={40} src={avatar} />
|
||||||
|
<div className="ml-[15px]">
|
||||||
|
<div className="text-[rgba(0,0,0,0.7)] text-[13px] leading-[18.2px]">{nickName}</div>
|
||||||
|
<div className="mt-[4px] text-[rgba(0,0,0,0.4)] text-[10px] leading-[14px]">{`${publishTime} ${location}`}</div>
|
||||||
|
<div className="mt-[6px] w-[657px] text-base text-[14px] leading-[19.6spx] text-overflow">{content}</div>
|
||||||
|
{/* 子评论 */}
|
||||||
|
<div>
|
||||||
|
{subCommentList.length > 0 &&
|
||||||
|
subCommentList.map((subComment) => (
|
||||||
|
<SubCommentItem key={subComment._id} className="mt-[12px]" {...subComment} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* 按钮 */}
|
||||||
|
<ButtonBar
|
||||||
|
className={`mt-[15px] ${topChildrenComment ? 'ml-[79px]' : 'ml-[54px]'}`}
|
||||||
|
commentCount={commentCount}
|
||||||
|
thumbupCount={thumbupCount}
|
||||||
|
haveThumbup={haveThumbup}
|
||||||
|
onShowInput={handleShowInput}
|
||||||
|
onShowAll={handleShowAll}
|
||||||
|
onThumbup={handleThumbup}
|
||||||
|
/>
|
||||||
|
{/* 回复 */}
|
||||||
|
{showInput && (
|
||||||
|
<CommentInput
|
||||||
|
nickName={nickName}
|
||||||
|
avatar={avatar}
|
||||||
|
className="mt-[25px] ml-[55px]"
|
||||||
|
onSubmit={handleReplySubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
import CommentItem from './CommentItem';
|
||||||
|
|
||||||
export default function CommentList({ commentList }: { commentList: Comment[] }) {
|
export default function CommentList({ commentList }: { commentList: Comment[] }) {
|
||||||
return <div></div>;
|
return (
|
||||||
|
<div>
|
||||||
|
{commentList.length > 0 &&
|
||||||
|
commentList.map((comment) => <CommentItem key="_id" {...comment} className="py-[20px]" />)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
/** 子评论输入框 */
|
||||||
|
import { Avatar } from '@/components';
|
||||||
|
|
||||||
|
export default function CommentSubInput() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Avatar />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Avatar } from '@/components';
|
||||||
|
|
||||||
|
export default function SubCommentItem({
|
||||||
|
avatar,
|
||||||
|
nickName,
|
||||||
|
publishTime,
|
||||||
|
location,
|
||||||
|
content,
|
||||||
|
className,
|
||||||
|
}: Comment & { className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`w-[655px] ${className}`}>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Avatar size={20} src={avatar} />
|
||||||
|
<div className="ml-[5px]">
|
||||||
|
<div className="w-[630px] text-[13px] leading-[20px] text-overflow">
|
||||||
|
<span className="text-[rgba(0,0,0,0.4)]">{`${nickName}: `}</span>
|
||||||
|
<span className="text-base">{content}</span>
|
||||||
|
{/* 归属地 */}
|
||||||
|
</div>
|
||||||
|
<div className="mt-[4px] text-[rgba(0,0,0,0.4)] text-[10px] leading-[14px]">{`${publishTime} ${location}`}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue