Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporateCulture-qd
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
王立鹏
corporateCulture-qd
Commits
5051b4c6
Commit
5051b4c6
authored
Dec 02, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 feat: 完成评论 子评论相关内容
parent
45657088
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
148 additions
and
40 deletions
+148
-40
types.ts
src/api/article/types.ts
+5
-1
index.vue
src/components/common/Comment/index.vue
+126
-33
useScrollTop.tsx
src/hooks/useScrollTop.tsx
+17
-6
No files found.
src/api/article/types.ts
View file @
5051b4c6
...
@@ -256,7 +256,7 @@ export interface CommentSearchParams extends PageSearchParams {
...
@@ -256,7 +256,7 @@ export interface CommentSearchParams extends PageSearchParams {
* 获取子评论列表
* 获取子评论列表
*/
*/
export
interface
CommentChildrenSearchParams
extends
PageSearchParams
{
export
interface
CommentChildrenSearchParams
extends
PageSearchParams
{
p
I
d
:
number
|
string
p
i
d
:
number
|
string
articleId
:
number
|
string
articleId
:
number
|
string
}
}
...
@@ -293,4 +293,8 @@ export interface CommentItemDto {
...
@@ -293,4 +293,8 @@ export interface CommentItemDto {
userId
:
number
userId
:
number
isHaveChildren
:
BooleanFlag
isHaveChildren
:
BooleanFlag
childrenNum
:
number
childrenNum
:
number
showChilderenPage
:
boolean
childrenPageCurrent
:
number
childrenPageList
:
CommentItemDto
[]
loadingChildren
:
boolean
}
}
src/components/common/Comment/index.vue
View file @
5051b4c6
...
@@ -93,7 +93,7 @@
...
@@ -93,7 +93,7 @@
<!-- 评论列表 -->
<!-- 评论列表 -->
<div
v-loading=
"loading"
class=
"divide-y divide-gray-100"
v-if=
"list.length"
>
<div
v-loading=
"loading"
class=
"divide-y divide-gray-100"
v-if=
"list.length"
>
<div
v-for=
"item in list"
:key=
"item.id"
>
<div
ref=
"commentItemRef"
v-for=
"item in list"
:key=
"item.id"
>
<div
class=
"p-4 transition-colors"
>
<div
class=
"p-4 transition-colors"
>
<div
class=
"flex gap-3"
>
<div
class=
"flex gap-3"
>
<img
:src=
"item.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<img
:src=
"item.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
...
@@ -108,26 +108,23 @@
...
@@ -108,26 +108,23 @@
<!--
<span
class=
"px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>
置顶
</span>
-->
<!--
<span
class=
"px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>
置顶
</span>
-->
</div>
</div>
<!-- 换行 -->
<!-- 换行 -->
<p
class=
"text-gray-700 m
b-3
break-all"
>
<p
class=
"text-gray-700 m
y-2
break-all"
>
{{
item
.
content
}}
{{
item
.
content
}}
</p>
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-
4
text-sm text-gray-500"
>
<div
class=
"flex items-center gap-
8
text-sm text-gray-500"
>
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
<!--
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
-->
<div
class=
"flex gap-2 items-center hover:text-blue-500"
>
<div
<div
class=
"flex items-center gap-1 cursor-pointer"
class=
"flex items-center gap-1 cursor-pointer"
@
click=
"handleLickComment(item)"
@
click=
"handleLickComment(item)"
>
<el-icon
:size=
"16"
:style=
"
{ color: item.hasPraise ? '#409eff' : '#606266' }"
>
>
<Pointer
/>
<el-icon
:size=
"16"
>
</el-icon>
<svg-icon
:name=
"item.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
<span>
{{
item
.
postPriseCount
}}
</span>
</el-icon>
<span>
{{
item
.
postPriseCount
}}
</span>
</div>
</div>
</div>
<!--
</button>
-->
<button
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(item)"
@
click=
"handleReply(item)"
...
@@ -138,10 +135,12 @@
...
@@ -138,10 +135,12 @@
</div>
</div>
<!-- 回复列表 -->
<!-- 回复列表 -->
<div
v-if=
"item.children?.length"
class=
"mt-3 ml-4 space-y-3"
>
<div
v-if=
"item.children?.length"
class=
"mt-3 ml-4 space-y-3"
>
<!-- 回复评论的内容 里面可能是 展示全部的 也有可能是展示5条之内容 -->
<div
<div
v-for=
"child in item.children"
v-for=
"child in getCurrentChildrenList(item)"
v-loading=
"item.loadingChildren"
:key=
"child.id"
:key=
"child.id"
class=
"flex gap-2 p-3
bg-gray-50
rounded-lg"
class=
"flex gap-2 p-3 rounded-lg"
>
>
<img
:src=
"child.avatar"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<img
:src=
"child.avatar"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex-1"
>
...
@@ -150,7 +149,7 @@
...
@@ -150,7 +149,7 @@
>
{{
child
.
replyUser
}}
回复 @
{{
child
.
replyName
}}
</span
>
{{
child
.
replyUser
}}
回复 @
{{
child
.
replyName
}}
</span
>
>
</div>
</div>
<p
class=
"text-gray-700"
>
<p
class=
"text-gray-700
my-2 break-all
"
>
{{
child
.
content
}}
{{
child
.
content
}}
</p>
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
...
@@ -158,17 +157,18 @@
...
@@ -158,17 +157,18 @@
<span>
{{
<span>
{{
dayjs
(
child
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
dayjs
(
child
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
}}
</span>
<div
<div
class=
"flex gap-2 items-center hover:text-blue-500"
>
class=
"flex items-center gap-1 cursor-pointer"
<div
@
click=
"handleLickComment(child)"
class=
"flex items-center gap-1 cursor-pointer"
>
@
click=
"handleLickComment(child)"
<el-icon
:size=
"16"
:style=
"
{ color: child.hasPraise ? '#409eff' : '#606266' }"
>
>
<Pointer
/>
<el-icon
:size=
"16"
>
</el-icon>
<svg-icon
<span>
{{
child
.
postPriseCount
}}
</span>
:name=
"child.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span>
{{
child
.
postPriseCount
}}
</span>
</div>
</div>
</div>
<button
<button
@
click=
"handleReply(child)"
@
click=
"handleReply(child)"
...
@@ -181,6 +181,38 @@
...
@@ -181,6 +181,38 @@
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 只有大于5 才会显示这个 -->
<div
class=
"ml-4"
v-if=
"item.childrenNum > 5"
>
<!-- 展示 展开回复 -->
<button
v-show=
"!item.showChilderenPage"
class=
"cursor-pointer hover:text-blue-500 transition-colors text-sm text-gray-500"
@
click=
"handleExpandReply(item)"
>
还有
{{
item
.
childrenNum
-
5
}}
条回复,点击查看
</button>
<!-- 展示 收起回复 以及分页 -->
<div
class=
"flex items-center gap-2"
v-show=
"item.showChilderenPage"
>
<span
class=
"text-sm text-gray-500"
>
共
{{
Math
.
ceil
(
item
.
childrenNum
/
10
)
}}
页
</span
>
<el-pagination
:pager-count=
"5"
layout=
"pager"
v-show=
"item.showChilderenPage"
:current-page=
"item.childrenPageCurrent"
:total=
"item.childrenNum"
@
current-change=
"handleChildrenCurrentChange($event, item, index)"
/>
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors text-sm text-gray-500"
@
click=
"handleCollapseReply(item, index)"
>
收起回复
</button>
</div>
</div>
<!-- 展示 回复评论的输入框 -->
<div
v-show=
"showCommentBox(item)"
class=
"flex gap-3 mt-4"
>
<div
v-show=
"showCommentBox(item)"
class=
"flex gap-3 mt-4"
>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex-1"
>
...
@@ -235,7 +267,7 @@
...
@@ -235,7 +267,7 @@
</div>
</div>
</
template
>
</
template
>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
getCommentList
,
addOrCancelCommentLike
,
addComment
}
from
'@/api'
import
{
getCommentList
,
addOrCancelCommentLike
,
addComment
,
getCommentChildren
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
,
useHintAnimation
}
from
'@/hooks'
import
{
usePageSearch
,
useScrollTop
,
useHintAnimation
}
from
'@/hooks'
import
{
BooleanFlag
}
from
'@/constants'
import
{
BooleanFlag
}
from
'@/constants'
import
type
{
CommentItemDto
}
from
'@/api'
import
type
{
CommentItemDto
}
from
'@/api'
...
@@ -255,8 +287,11 @@ const { userInfo } = storeToRefs(userStore)
...
@@ -255,8 +287,11 @@ const { userInfo } = storeToRefs(userStore)
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentInputRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentInputRef'
)
const
commentItemRefList
=
useTemplateRef
<
HTMLElement
[]
|
null
>
(
'commentItemRef'
)
// 回滚到评论框
const
{
handleBackTop
}
=
useScrollTop
(
commentRef
)
const
{
handleBackTop
}
=
useScrollTop
(
commentRef
)
// 回滚到子评论框
const
{
handleBackTop
:
handleBackTopChildren
}
=
useScrollTop
(
commentItemRefList
)
const
{
triggerAnimation
}
=
useHintAnimation
(
commentInputRef
,
{
const
{
triggerAnimation
}
=
useHintAnimation
(
commentInputRef
,
{
classes
:
[
'scale-bounce'
,
'highlight'
,
'shake-x'
],
classes
:
[
'scale-bounce'
,
'highlight'
,
'shake-x'
],
...
@@ -270,6 +305,19 @@ const { list, searchParams, goToPage, loading, changePageSize, refresh } = usePa
...
@@ -270,6 +305,19 @@ const { list, searchParams, goToPage, loading, changePageSize, refresh } = usePa
sortType
:
2
,
sortType
:
2
,
},
},
defaultSize
,
defaultSize
,
formatList
(
list
:
CommentItemDto
[])
{
return
list
.
map
((
item
)
=>
{
// 添加新的字段 是否展示分页子评论 默认是false 当前子评论分页current 以及子评论分页列表 loading效果
return
{
...
item
,
showChildPage
:
false
,
childrenPageCurrent
:
1
,
childrenPageList
:
[],
loadingChildren
:
false
,
}
})
},
},
},
)
)
const
handleCurrentChange
=
async
(
e
:
number
)
=>
{
const
handleCurrentChange
=
async
(
e
:
number
)
=>
{
...
@@ -304,7 +352,6 @@ const handleLickComment = async (item: CommentItemDto) => {
...
@@ -304,7 +352,6 @@ const handleLickComment = async (item: CommentItemDto) => {
}
}
const
handleReply
=
(
item
:
CommentItemDto
)
=>
{
const
handleReply
=
(
item
:
CommentItemDto
)
=>
{
console
.
log
(
item
)
replyPlaceholder
.
value
=
`回复@
${
item
.
replyUser
}
:`
replyPlaceholder
.
value
=
`回复@
${
item
.
replyUser
}
:`
comment
.
value
=
''
comment
.
value
=
''
currentCommentId
.
value
=
item
.
id
currentCommentId
.
value
=
item
.
id
...
@@ -327,19 +374,65 @@ const handleMyComment = async () => {
...
@@ -327,19 +374,65 @@ const handleMyComment = async () => {
total
.
value
++
total
.
value
++
}
}
const
handleComment
=
async
()
=>
{
const
handleComment
=
async
()
=>
{
console
.
log
(
comment
.
value
)
const
res
=
await
addComment
({
const
res
=
await
addComment
({
articleId
:
id
,
articleId
:
id
,
content
:
comment
.
value
,
content
:
comment
.
value
,
...(
currentCommentId
.
value
?
{
pid
:
currentCommentId
.
value
}
:
{}),
...(
currentCommentId
.
value
?
{
pid
:
currentCommentId
.
value
}
:
{}),
})
})
console
.
log
(
res
)
ElMessage
.
success
(
'发表评论成功'
)
ElMessage
.
success
(
'发表评论成功'
)
refresh
()
refresh
()
comment
.
value
=
''
comment
.
value
=
''
total
.
value
++
total
.
value
++
}
}
// 展开回复 获取子评论列表
const
handleExpandReply
=
async
(
item
:
CommentItemDto
)
=>
{
item
.
loadingChildren
=
true
try
{
// 获取子评论
await
getCommentChildrenList
(
item
)
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
item
.
loadingChildren
=
false
}
item
.
showChilderenPage
=
true
}
// 收起回复
const
handleCollapseReply
=
(
item
:
CommentItemDto
,
index
:
number
)
=>
{
item
.
showChilderenPage
=
false
handleBackTopChildren
(
index
)
}
// 改变子评论的当前页数
const
handleChildrenCurrentChange
=
(
e
:
number
,
item
:
CommentItemDto
,
index
:
number
)
=>
{
item
.
childrenPageCurrent
=
e
getCommentChildrenList
(
item
)
handleBackTopChildren
(
index
)
}
// 根据页数获取子评论列表
const
getCommentChildrenList
=
async
(
item
:
CommentItemDto
)
=>
{
const
{
data
}
=
await
getCommentChildren
({
pid
:
item
.
id
,
articleId
:
id
,
current
:
item
.
childrenPageCurrent
,
size
:
10
,
})
item
.
childrenPageList
=
data
.
list
}
// 获取当前要渲染的子列表
const
getCurrentChildrenList
=
(
item
:
CommentItemDto
)
=>
{
if
(
item
.
showChilderenPage
)
{
return
item
.
childrenPageList
}
else
{
return
item
.
children
}
}
defineExpose
({
defineExpose
({
scrollToCommentBox
:
()
=>
handleBackTop
(),
scrollToCommentBox
:
()
=>
handleBackTop
(),
})
})
...
...
src/hooks/useScrollTop.tsx
View file @
5051b4c6
...
@@ -19,16 +19,27 @@ function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
...
@@ -19,16 +19,27 @@ function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
)
)
}
}
// 顺便兼容下多个的
export
const
useScrollTop
=
(
export
const
useScrollTop
=
(
el
:
MaybeRef
<
HTMLElement
|
null
|
Window
>
,
el
:
MaybeRef
<
HTMLElement
|
null
|
Window
|
HTMLElement
[]
|
[
Window
]
>
,
options
:
{
compatFixedHeader
?:
boolean
}
=
{},
options
:
{
compatFixedHeader
?:
boolean
}
=
{},
)
=>
{
)
=>
{
const
{
compatFixedHeader
=
true
}
=
options
const
{
compatFixedHeader
=
true
}
=
options
const
handleBackTop
=
()
=>
{
const
handleBackTop
=
(
currentIndex
:
number
=
0
)
=>
{
const
dom
=
unref
(
el
)
const
initDoms
=
unref
(
el
)
if
(
!
dom
)
return
if
(
!
initDoms
)
return
let
doms
=
[]
if
(
!
Array
.
isArray
(
initDoms
))
{
doms
=
[
initDoms
]
}
else
{
doms
=
initDoms
}
const
dom
=
doms
[
currentIndex
]
as
HTMLElement
|
Window
if
(
dom
instanceof
Window
)
{
if
(
dom
instanceof
Window
)
{
window
.
scrollTo
({
window
.
scrollTo
({
top
:
0
,
top
:
0
,
...
@@ -38,13 +49,13 @@ export const useScrollTop = (
...
@@ -38,13 +49,13 @@ export const useScrollTop = (
}
}
if
(
compatFixedHeader
)
{
if
(
compatFixedHeader
)
{
const
top
=
dom
.
getBoundingClientRect
().
top
+
window
.
scrollY
-
52
const
top
=
dom
?.
getBoundingClientRect
?.
().
top
+
window
.
scrollY
-
52
window
.
scrollTo
({
window
.
scrollTo
({
top
,
top
,
behavior
:
'smooth'
,
behavior
:
'smooth'
,
})
})
}
else
{
}
else
{
dom
.
scrollIntoView
({
dom
?.
scrollIntoView
?.
({
behavior
:
'smooth'
,
behavior
:
'smooth'
,
block
:
'start'
,
block
:
'start'
,
})
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment