Skip to content

Commit 023f479

Browse files
committed
fix: double comment
1 parent 76d5cf3 commit 023f479

4 files changed

Lines changed: 135 additions & 43 deletions

File tree

public/locales/en/repository.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
"err1": "Comment cannot be empty",
9494
"err2": "Comment must be at least 5 characters",
9595
"err3": "Comment cannot exceed 500 characters",
96-
"err4": "Please provide a rating"
96+
"err4": "Please provide a rating",
97+
"submitting": "Submitting..."
9798
}
9899
}
99100
}

public/locales/zh/repository.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
"err1": "评论内容不能为空",
9494
"err2": "评论内容不能少于 5 个字",
9595
"err3": "评论内容不能超过 500 个字",
96-
"err4": "请评分"
96+
"err4": "请评分",
97+
"submitting": "提交中..."
9798
}
9899
}
99100
}

src/components/respository/CommentSubmit.tsx

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FormEventHandler } from 'react';
1+
import { FormEventHandler, useState } from 'react';
22

33
import useCommentData from '@/hooks/useCommentData';
44
import { useLoginContext } from '@/hooks/useLoginContext';
@@ -29,6 +29,9 @@ function CommentSubmit(props: CommentSubmitProps) {
2929
const { login, userInfo, isLogin } = useLoginContext();
3030
const { t, belongId, className, onSuccess, onFail } = props;
3131

32+
// 添加提交状态管理
33+
const [isSubmitting, setIsSubmitting] = useState(false);
34+
3235
const handleInput: FormEventHandler<HTMLTextAreaElement> = (e) => {
3336
const { value } = e.currentTarget;
3437

@@ -50,6 +53,7 @@ function CommentSubmit(props: CommentSubmitProps) {
5053
const handleChangeRating = (rating: number) => {
5154
setCommentData({ ...commentData, score: rating });
5255
};
56+
5357
const getErrMessage = (commentData: {
5458
comment: string;
5559
isUsed: boolean;
@@ -70,38 +74,50 @@ function CommentSubmit(props: CommentSubmitProps) {
7074
return '';
7175
};
7276

73-
const handleSubmit = () => {
77+
const handleSubmit = async () => {
78+
// 防止重复提交
79+
if (isSubmitting) {
80+
return;
81+
}
82+
7483
if (getErrMessage(commentData)) {
7584
return Message.error(getErrMessage(commentData));
7685
}
7786
if (!isLogin) {
7887
return login();
7988
}
80-
let request;
81-
82-
if (props.replyUser) {
83-
request = submitReplyComment(props.replyUser.cid, {
84-
comment: commentData.comment,
85-
reply_uid: props.replyUser.user.uid,
86-
});
87-
} else {
88-
request = submitComment(belongId, commentData);
89-
}
9089

91-
request
92-
.then((data) => {
93-
setCommentData(DEFAULT_INITITAL_COMMENT_DATA);
90+
// 设置提交状态
91+
setIsSubmitting(true);
9492

95-
if (data.success) {
96-
onSuccess && onSuccess(data);
97-
Message.success(t('comment.submit.success'));
98-
} else {
99-
onFail && onFail(data);
100-
}
101-
})
102-
.catch((err) => {
103-
Message.error(err.message || t('comment.submit.fail'));
104-
});
93+
try {
94+
let request;
95+
96+
if (props.replyUser) {
97+
request = submitReplyComment(props.replyUser.cid, {
98+
comment: commentData.comment,
99+
reply_uid: props.replyUser.user.uid,
100+
});
101+
} else {
102+
request = submitComment(belongId, commentData);
103+
}
104+
105+
const data = await request;
106+
107+
setCommentData(DEFAULT_INITITAL_COMMENT_DATA);
108+
109+
if (data.success) {
110+
onSuccess && onSuccess(data);
111+
Message.success(t('comment.submit.success'));
112+
} else {
113+
onFail && onFail(data);
114+
}
115+
} catch (err: any) {
116+
Message.error(err.message || t('comment.submit.fail'));
117+
} finally {
118+
// 重置提交状态
119+
setIsSubmitting(false);
120+
}
105121
};
106122

107123
const placeholder = props.replyUser
@@ -130,6 +146,7 @@ function CommentSubmit(props: CommentSubmitProps) {
130146
placeholder={placeholder}
131147
value={commentData.comment}
132148
onInput={handleInput}
149+
disabled={isSubmitting}
133150
></textarea>
134151
<div className='flex flex-wrap items-center gap-2 text-xs sm:gap-4 sm:text-sm'>
135152
{!props.replyUser && (
@@ -142,6 +159,7 @@ function CommentSubmit(props: CommentSubmitProps) {
142159
style={{ boxShadow: 'none' }}
143160
checked={!commentData.isUsed}
144161
onChange={() => handleRadioChange(false)}
162+
disabled={isSubmitting}
145163
/>
146164
<span>{t('comment.unused')}</span>
147165
</label>
@@ -153,6 +171,7 @@ function CommentSubmit(props: CommentSubmitProps) {
153171
style={{ boxShadow: 'none' }}
154172
checked={commentData.isUsed}
155173
onChange={() => handleRadioChange(true)}
174+
disabled={isSubmitting}
156175
/>
157176
<span>{t('comment.used')}</span>
158177
</label>
@@ -162,6 +181,7 @@ function CommentSubmit(props: CommentSubmitProps) {
162181
<Rating
163182
value={commentData.score}
164183
onRateChange={handleChangeRating}
184+
disabled={isSubmitting}
165185
/>
166186
</div>
167187
</>
@@ -171,23 +191,76 @@ function CommentSubmit(props: CommentSubmitProps) {
171191
<div className='flex flex-1 justify-end space-x-4'>
172192
<button
173193
onClick={props.onCancelReply}
174-
className='inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg border border-gray-300 pl-3 pr-3 text-sm font-semibold text-gray-500 transition-transform focus:outline-none active:scale-90'
194+
className='inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg border border-gray-300 pl-3 pr-3 text-sm font-semibold text-gray-500 transition-transform focus:outline-none active:scale-90 disabled:cursor-not-allowed disabled:opacity-50'
195+
disabled={isSubmitting}
175196
>
176197
{t('comment.cancel')}
177198
</button>
178199
<button
179-
className='inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg bg-gray-700 pl-3 pr-3 text-sm font-semibold text-white transition-transform focus:outline-none active:scale-90'
200+
className='inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg bg-gray-700 pl-3 pr-3 text-sm font-semibold text-white transition-transform focus:outline-none active:scale-90 disabled:cursor-not-allowed disabled:opacity-50'
180201
onClick={handleSubmit}
202+
disabled={isSubmitting}
181203
>
182-
{t('comment.reply')}
204+
{isSubmitting ? (
205+
<>
206+
<svg
207+
className='mr-2 h-4 w-4 animate-spin'
208+
viewBox='0 0 24 24'
209+
>
210+
<circle
211+
className='opacity-25'
212+
cx='12'
213+
cy='12'
214+
r='10'
215+
stroke='currentColor'
216+
strokeWidth='4'
217+
fill='none'
218+
></circle>
219+
<path
220+
className='opacity-75'
221+
fill='currentColor'
222+
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
223+
></path>
224+
</svg>
225+
{t('comment.submitting') || '提交中...'}
226+
</>
227+
) : (
228+
t('comment.reply')
229+
)}
183230
</button>
184231
</div>
185232
) : (
186233
<button
187-
className='ml-auto inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg bg-gray-700 pl-3 pr-3 text-sm font-semibold text-white transition-transform focus:outline-none active:scale-90'
234+
className='ml-auto inline-flex h-8 min-h-[2rem] flex-shrink-0 cursor-pointer select-none flex-wrap items-center justify-center rounded-lg bg-gray-700 pl-3 pr-3 text-sm font-semibold text-white transition-transform focus:outline-none active:scale-90 disabled:cursor-not-allowed disabled:opacity-50'
188235
onClick={handleSubmit}
236+
disabled={isSubmitting}
189237
>
190-
{t('comment.submit.save')}
238+
{isSubmitting ? (
239+
<>
240+
<svg
241+
className='mr-2 h-4 w-4 animate-spin'
242+
viewBox='0 0 24 24'
243+
>
244+
<circle
245+
className='opacity-25'
246+
cx='12'
247+
cy='12'
248+
r='10'
249+
stroke='currentColor'
250+
strokeWidth='4'
251+
fill='none'
252+
></circle>
253+
<path
254+
className='opacity-75'
255+
fill='currentColor'
256+
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
257+
></path>
258+
</svg>
259+
{t('comment.submitting')}
260+
</>
261+
) : (
262+
t('comment.submit.save')
263+
)}
191264
</button>
192265
)}
193266
</>

src/components/respository/Rating.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,62 @@ function Rating(props: {
77
value: number;
88
size?: number;
99
onRateChange?: (value: number) => void;
10+
disabled?: boolean;
1011
}) {
11-
const { value = 5, onRateChange = NOOP, size = 18 } = props;
12+
const { value = 5, onRateChange = NOOP, size = 18, disabled = false } = props;
13+
14+
const handleClick = (rating: number) => {
15+
if (!disabled) {
16+
onRateChange(rating);
17+
}
18+
};
1219

1320
return (
1421
<div className='flex items-center'>
1522
<GoStar
1623
size={size}
17-
className={clsx('cursor-pointer text-gray-400', {
24+
className={clsx('text-gray-400', {
1825
'text-yellow-300': value >= 1,
26+
'cursor-pointer': !disabled,
27+
'cursor-not-allowed opacity-50': disabled,
1928
})}
20-
onClick={() => onRateChange(1)}
29+
onClick={() => handleClick(1)}
2130
/>
2231
<GoStar
2332
size={size}
24-
className={clsx('cursor-pointer text-gray-400', {
33+
className={clsx('text-gray-400', {
2534
'text-yellow-300': value >= 2,
35+
'cursor-pointer': !disabled,
36+
'cursor-not-allowed opacity-50': disabled,
2637
})}
27-
onClick={() => onRateChange(2)}
38+
onClick={() => handleClick(2)}
2839
/>
2940
<GoStar
3041
size={size}
31-
className={clsx('cursor-pointer text-gray-400', {
42+
className={clsx('text-gray-400', {
3243
'text-yellow-300': value >= 3,
44+
'cursor-pointer': !disabled,
45+
'cursor-not-allowed opacity-50': disabled,
3346
})}
34-
onClick={() => onRateChange(3)}
47+
onClick={() => handleClick(3)}
3548
/>
3649
<GoStar
3750
size={size}
38-
className={clsx('cursor-pointer text-gray-400', {
51+
className={clsx('text-gray-400', {
3952
'text-yellow-300': value >= 4,
53+
'cursor-pointer': !disabled,
54+
'cursor-not-allowed opacity-50': disabled,
4055
})}
41-
onClick={() => onRateChange(4)}
56+
onClick={() => handleClick(4)}
4257
/>
4358
<GoStar
4459
size={size}
45-
className={clsx('cursor-pointer text-gray-400', {
60+
className={clsx('text-gray-400', {
4661
'text-yellow-300': value >= 5,
62+
'cursor-pointer': !disabled,
63+
'cursor-not-allowed opacity-50': disabled,
4764
})}
48-
onClick={() => onRateChange(5)}
65+
onClick={() => handleClick(5)}
4966
/>
5067
</div>
5168
);

0 commit comments

Comments
 (0)