Commit 5051b4c6 by lijiabin

【需求 17679】 feat: 完成评论 子评论相关内容

parent 45657088
...@@ -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 {
pId: number | string pid: 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
} }
...@@ -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 mb-3 break-all"> <p class="text-gray-700 my-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 <el-icon :size="16">
:size="16" <svg-icon :name="item.hasPraise ? 'praise_fill' : 'praise'"></svg-icon>
:style="{ color: item.hasPraise ? '#409eff' : '#606266' }"
>
<Pointer />
</el-icon> </el-icon>
<span>{{ item.postPriseCount }}</span> <span>{{ item.postPriseCount }}</span>
</div> </div>
<!-- </button> --> </div>
<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,18 +157,19 @@ ...@@ -158,18 +157,19 @@
<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 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(child)" @click="handleLickComment(child)"
> >
<el-icon <el-icon :size="16">
:size="16" <svg-icon
:style="{ color: child.hasPraise ? '#409eff' : '#606266' }" :name="child.hasPraise ? 'praise_fill' : 'praise'"
> ></svg-icon>
<Pointer />
</el-icon> </el-icon>
<span>{{ child.postPriseCount }}</span> <span>{{ child.postPriseCount }}</span>
</div> </div>
</div>
<button <button
@click="handleReply(child)" @click="handleReply(child)"
class="cursor-pointer hover:text-blue-500 transition-colors" class="cursor-pointer hover:text-blue-500 transition-colors"
...@@ -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(),
}) })
......
...@@ -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',
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment