Commit 9662ea89 by lijiabin

【需求 17679】 perf: 继续优化

parent 1a8cb204
...@@ -335,7 +335,7 @@ export const getSecondCommentChildren = (data: { ...@@ -335,7 +335,7 @@ export const getSecondCommentChildren = (data: {
articleId: number articleId: number
}) => { }) => {
return service.request<BackendServicePageResult<CommentItemDto>>({ return service.request<BackendServicePageResult<CommentItemDto>>({
url: `/api/cultureComment/comment/children`, url: `/api/cultureComment/getAllComment`,
method: 'POST', method: 'POST',
data, data,
}) })
......
...@@ -178,6 +178,7 @@ export interface ArticleItemDto { ...@@ -178,6 +178,7 @@ export interface ArticleItemDto {
type: ArticleTypeEnum type: ArticleTypeEnum
userId: string userId: string
viewCount: number viewCount: number
childNum: number
} }
} }
......
...@@ -298,11 +298,13 @@ ...@@ -298,11 +298,13 @@
<el-pagination <el-pagination
v-model:current-page="searchParams.current" v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size" v-model:page-size="searchParams.size"
:total="total" :total="firstComentTotal"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
@size-change="changePageSize" @size-change="changePageSize"
layout="prev, pager, next, total" layout="prev, pager, next, slot"
/> >
<div>共 {{ total }} 条</div>
</el-pagination>
</div> </div>
</div> </div>
</div> </div>
...@@ -318,7 +320,7 @@ import { ...@@ -318,7 +320,7 @@ import {
getCommentChildren, getCommentChildren,
getSecondCommentList, getSecondCommentList,
} from '@/api' } from '@/api'
import { usePageSearch, useScrollTop, useHintAnimation } from '@/hooks' import { usePageSearch, useScrollTop } from '@/hooks'
import { BooleanFlag } from '@/constants' import { BooleanFlag } from '@/constants'
import type { CommentItemDto } from '@/api' import type { CommentItemDto } from '@/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
...@@ -333,7 +335,7 @@ const { ...@@ -333,7 +335,7 @@ const {
isQuestion = false, isQuestion = false,
commentId = 0, commentId = 0,
} = defineProps<{ } = defineProps<{
id: number id: number // 文章ID
defaultSize?: number defaultSize?: number
isReal: BooleanFlag isReal: BooleanFlag
isQuestion?: boolean // 如果是问题的话 展示有点不一样 isQuestion?: boolean // 如果是问题的话 展示有点不一样
...@@ -351,7 +353,6 @@ const total = defineModel<number>('total', { required: true, default: 0 }) ...@@ -351,7 +353,6 @@ const total = defineModel<number>('total', { required: true, default: 0 })
const userStore = useUserStore() const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar)) const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar))
console.log(userAvatar)
const commentRef = useTemplateRef<HTMLElement | null>('commentRef') const commentRef = useTemplateRef<HTMLElement | null>('commentRef')
const commentDialogRef = useTemplateRef<HTMLElement | null>('commentDialogRef') const commentDialogRef = useTemplateRef<HTMLElement | null>('commentDialogRef')
const commentInputRef = useTemplateRef<HTMLElement | null>('commentInputRef') const commentInputRef = useTemplateRef<HTMLElement | null>('commentInputRef')
...@@ -361,13 +362,17 @@ const { handleBackTop } = useScrollTop(commentRef) ...@@ -361,13 +362,17 @@ const { handleBackTop } = useScrollTop(commentRef)
// 回滚到子评论框 // 回滚到子评论框
const { handleBackTop: handleBackTopChildren } = useScrollTop(commentItemRefList) const { handleBackTop: handleBackTopChildren } = useScrollTop(commentItemRefList)
const { triggerAnimation } = useHintAnimation(commentInputRef, { console.log(commentId, 'commentId')
classes: ['scale-bounce', 'highlight', 'shake-x'], const {
}) list,
searchParams,
const { list, searchParams, goToPage, loading, changePageSize, refresh, search } = usePageSearch( goToPage,
isQuestion ? getSecondCommentList : getCommentList, loading,
{ changePageSize,
refresh,
search,
total: firstComentTotal,
} = usePageSearch(isQuestion ? getSecondCommentList : getCommentList, {
defaultParams: { defaultParams: {
...(commentId ...(commentId
? { pid: commentId, sortType: 2 } ? { pid: commentId, sortType: 2 }
...@@ -399,14 +404,10 @@ const { list, searchParams, goToPage, loading, changePageSize, refresh, search } ...@@ -399,14 +404,10 @@ const { list, searchParams, goToPage, loading, changePageSize, refresh, search }
}) })
}, },
immediate, immediate,
}, })
)
const handleCurrentChange = async (e: number) => { const handleCurrentChange = async (e: number) => {
await goToPage(e) await goToPage(e)
handleBackTop() handleBackTop()
setTimeout(() => {
triggerAnimation()
}, 500)
} }
// 自己发出的评论 // 自己发出的评论
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
v-model="visible" v-model="visible"
:title="dialogTitle" :title="dialogTitle"
width="600px" width="600px"
class="comment-reply-dialog rounded-xl overflow-hidden" class="rounded-xl overflow-hidden"
:show-close="false" :show-close="false"
append-to-body append-to-body
destroy-on-close destroy-on-close
...@@ -168,7 +168,8 @@ ...@@ -168,7 +168,8 @@
<input <input
ref="bottomInputRef" ref="bottomInputRef"
v-model="bottomCommentContent" v-model="bottomCommentContent"
type="text" :autosize="{ minRows: 1, maxRows: 4 }"
type="textarea"
class="bg-transparent w-full outline-none text-sm text-gray-700 placeholder-gray-400" class="bg-transparent w-full outline-none text-sm text-gray-700 placeholder-gray-400"
:placeholder="`回复 ${parentComment?.replyUser || '...'}`" :placeholder="`回复 ${parentComment?.replyUser || '...'}`"
@keyup.enter="submitReply(parentComment?.id)" @keyup.enter="submitReply(parentComment?.id)"
...@@ -233,16 +234,14 @@ const scrollContainer = ref<HTMLElement>() ...@@ -233,16 +234,14 @@ const scrollContainer = ref<HTMLElement>()
// --- Actions --- // --- Actions ---
const { list, total, search, searchParams, goToPage, changePageSize } = usePageSearch( const { list, total, search, searchParams, goToPage, changePageSize, refresh, loading } =
getSecondCommentChildren, usePageSearch(getSecondCommentChildren, {
{
defaultParams: { defaultParams: {
articleId: articleId, articleId: articleId,
pid: 0, pid: 0,
}, },
immediate: false, immediate: false,
}, })
)
const open = async (item: CommentItemDto) => { const open = async (item: CommentItemDto) => {
// const { data } = await getSecondCommentChildren({ // const { data } = await getSecondCommentChildren({
......
...@@ -109,9 +109,10 @@ ...@@ -109,9 +109,10 @@
</el-button> </el-button>
<!-- 回答按钮保持不变 --> <!-- 回答按钮保持不变 -->
<!-- 当前最热评论的评论有几条 -->
<el-button size="small" plain @click.stop="handleComment(item, index)"> <el-button size="small" plain @click.stop="handleComment(item, index)">
<el-icon><Edit /></el-icon> <el-icon><Edit /></el-icon>
回答 {{ item.cultureCommentListVo?.childNum || 0 }}条评论
</el-button> </el-button>
<ActionMore class="ml-4" :articleDetail="item" /> <ActionMore class="ml-4" :articleDetail="item" />
...@@ -121,13 +122,13 @@ ...@@ -121,13 +122,13 @@
<!-- 右侧:统计信息 --> <!-- 右侧:统计信息 -->
<div class="flex items-center"> <div class="flex items-center">
<!-- 浏览量 --> <!-- 浏览量 -->
<el-button text class="flex items-center gap-2 text-gray-500"> <!-- <el-button text class="flex items-center gap-2 text-gray-500">
<el-icon><View /></el-icon> <el-icon><View /></el-icon>
<span class="text-sm">{{ item.viewCount || 0 }}</span> <span class="text-sm">{{ item.viewCount || 0 }}</span>
</el-button> </el-button> -->
<!-- 收藏 --> <!-- 收藏 -->
<el-button <!-- <el-button
text text
class="flex items-center gap-2 text-gray-500 transition-colors" class="flex items-center gap-2 text-gray-500 transition-colors"
@click.stop="handleCollect(item)" @click.stop="handleCollect(item)"
...@@ -136,17 +137,17 @@ ...@@ -136,17 +137,17 @@
<span class="text-sm" :class="{ 'text-blue-500': item.hasCollect }">{{ <span class="text-sm" :class="{ 'text-blue-500': item.hasCollect }">{{
item.collectionCount || 0 item.collectionCount || 0
}}</span> }}</span>
</el-button> </el-button> -->
<!-- 评论 --> <!-- 评论 -->
<el-button <!-- <el-button
text text
class="flex items-center gap-2 text-gray-500 transition-colors" class="flex items-center gap-2 text-gray-500 transition-colors"
@click.stop="handleComment(item, index)" @click.stop="handleComment(item, index)"
> >
<el-icon><ChatDotRound /></el-icon> <el-icon><ChatDotRound /></el-icon>
<span class="text-sm">{{ item.replyCount || 0 }}</span> <span class="text-sm">{{ item.cultureCommentListVo?.childNum || 0 }}</span>
</el-button> </el-button> -->
</div> </div>
</div> </div>
<Transition name="fade"> <Transition name="fade">
...@@ -154,7 +155,7 @@ ...@@ -154,7 +155,7 @@
v-show="item.showComment" v-show="item.showComment"
:ref="(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)" :ref="(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)"
:id="item.id" :id="item.id"
:total="item.replyCount" :total="item.cultureCommentListVo?.childNum || 0"
:defaultSize="5" :defaultSize="5"
:isReal="0" :isReal="0"
:immediate="false" :immediate="false"
...@@ -211,19 +212,14 @@ ...@@ -211,19 +212,14 @@
<script setup lang="ts"> <script setup lang="ts">
import Tabs from '@/components/common/Tabs' import Tabs from '@/components/common/Tabs'
import { Star, View, ChatDotRound, Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
import Comment from '@/components/common/Comment/index.vue' import Comment from '@/components/common/Comment/index.vue'
import { useScrollTop, usePageSearch } from '@/hooks' import { useScrollTop, usePageSearch } from '@/hooks'
import { TABS_REF_KEY } from '@/constants' import { TABS_REF_KEY } from '@/constants'
import PublishBox from '@/components/common/PublishBox/index.vue' import PublishBox from '@/components/common/PublishBox/index.vue'
import { ArticleTypeEnum } from '@/constants' import { ArticleTypeEnum } from '@/constants'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import { getArticleList, addOrCanceArticlelCollect, addOrCancelToAnswerList } from '@/api'
getArticleList,
addOrCanceArticlelCollect,
addOrCancelToAnswerList,
getSecondCommentList,
} from '@/api'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { useQuestionStore } from '@/stores/question' import { useQuestionStore } from '@/stores/question'
import ActionMore from '@/components/common/ActionMore/index.vue' import ActionMore from '@/components/common/ActionMore/index.vue'
...@@ -271,7 +267,7 @@ const handleComment = (item: ArticleItemDto, index: number) => { ...@@ -271,7 +267,7 @@ const handleComment = (item: ArticleItemDto, index: number) => {
} }
const handleCommentSuccess = (item: ArticleItemDto) => { const handleCommentSuccess = (item: ArticleItemDto) => {
item.replyCount++ item.cultureCommentListVo.childNum++
// 同时更新下 // 同时更新下
// 如果已经添加了回答 则改为未添加 并且更新问题数量 // 如果已经添加了回答 则改为未添加 并且更新问题数量
if (item.hasAddQuestion) { if (item.hasAddQuestion) {
......
...@@ -6,18 +6,26 @@ ...@@ -6,18 +6,26 @@
<!-- 标签导航 --> <!-- 标签导航 -->
<div class="bg-white p-4 mb-6 rounded-lg shadow-sm"> <div class="bg-white p-4 mb-6 rounded-lg shadow-sm">
<div class="flex flex-wrap gap-2 mb-2"> <div class="flex flex-wrap gap-2 mb-2">
<el-tag v-for="tag in filterOptions" :key="tag.id" <el-tag
v-for="tag in filterOptions"
:key="tag.id"
:type="tag.id === searchParams.sortLogic ? 'primary' : 'info'" :type="tag.id === searchParams.sortLogic ? 'primary' : 'info'"
:effect="tag.id === searchParams.sortLogic ? 'dark' : 'plain'" class="cursor-pointer" :effect="tag.id === searchParams.sortLogic ? 'dark' : 'plain'"
@click="toggleFilter(tag.id)"> class="cursor-pointer"
@click="toggleFilter(tag.id)"
>
{{ tag.title }} {{ tag.title }}
</el-tag> </el-tag>
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<el-tag v-for="tag in tagList" :key="tag.id" <el-tag
v-for="tag in tagList"
:key="tag.id"
:type="searchParams.tagIdList?.includes(tag.id) ? 'primary' : 'info'" :type="searchParams.tagIdList?.includes(tag.id) ? 'primary' : 'info'"
:effect="searchParams.tagIdList?.includes(tag.id) ? 'dark' : 'plain'" class="cursor-pointer" :effect="searchParams.tagIdList?.includes(tag.id) ? 'dark' : 'plain'"
@click="toggleTag(tag.id)"> class="cursor-pointer"
@click="toggleTag(tag.id)"
>
{{ tag.title }} {{ tag.title }}
</el-tag> </el-tag>
</div> </div>
...@@ -33,40 +41,51 @@ ...@@ -33,40 +41,51 @@
{{ filterText }} {{ filterText }}
</h2> </h2>
</div> </div>
<div class="text-#999 cursor-pointer text-sm" @click=" <div
class="text-#999 cursor-pointer text-sm"
@click="
router.push({ router.push({
path: '/searchPage', path: '/searchPage',
query: { query: {
type: ArticleTypeEnum.PRACTICE, type: ArticleTypeEnum.PRACTICE,
}, },
}) })
"> "
>
查看更多 >> 查看更多 >>
</div> </div>
</div> </div>
<div class="divide-y bg-#fff"> <div class="divide-y bg-#fff">
<div @click="openArticleDetail(item.id)" v-for="item in list" :key="item.id" <div
class="p-4 hover:bg-gray-50 transition-colors cursor-pointer pl-8" > @click="openArticleDetail(item.id)"
<div class="flex gap-3 items-center h-100%" style="border-bottom: 1.5px solid #ddd;"> v-for="item in list"
:key="item.id"
class="p-4 hover:bg-gray-50 transition-colors cursor-pointer pl-8"
>
<div class="flex gap-3 items-center h-100%" style="border-bottom: 1.5px solid #ddd">
<!-- 左侧内容 --> <!-- 左侧内容 -->
<div class="flex-1"> <div class="flex-1">
<h1 class="font-medium text-gray-900 mb-2 leading-relaxed line-clamp-1 text-18px"> <h1 class="font-medium text-gray-900 mb-2 leading-relaxed line-clamp-1 text-lg">
{{ item.title }} {{ item.title }}
</h1> </h1>
<!-- 带图片的内容 --> <!-- 带图片的内容 -->
<div class="flex gap-3 mb-3 align-center" style="border-right: 1.5px solid #ddd;"> <div class="flex gap-3 mb-2 align-center">
<img v-if="item.faceUrl" :src="item.faceUrl" :alt="item.title" <img
class="w-40 h-25 object-cover rounded-lg flex-shrink-0" /> v-if="item.faceUrl"
:src="item.faceUrl"
:alt="item.title"
class="w-40 h-25 object-cover rounded-lg flex-shrink-0"
/>
<div class="flex-1 mr-4"> <div class="flex-1 mr-4">
<div class="text-gray-600 text-sm leading-relaxed line-clamp-4"> <div class="text-gray-600 text-base leading-relaxed line-clamp-4">
{{ item.content }} {{ item.content }}
</div> </div>
</div> </div>
</div> </div>
<!-- 互动数据 --> <!-- 互动数据 -->
<div class="flex items-center gap-5 text-gray-400 text-base mb-3"> <div class="flex items-center gap-5 text-gray-400 text-sm mb-2">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<el-icon class="text-sm"> <el-icon class="text-sm">
<View /> <View />
...@@ -111,14 +130,23 @@ ...@@ -111,14 +130,23 @@
<!-- 右侧:分页器 --> <!-- 右侧:分页器 -->
<div class="right"> <div class="right">
<div class="pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"> <div
<el-pagination v-model:current-page="searchParams.current" v-model:page-size="searchParams.size" class="pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
:page-sizes="[5, 20, 1]" layout="prev, pager, next, jumper, total" :total="total" >
class="custom-pagination" @current-change=" <el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
:page-sizes="[5, 20, 1]"
layout="prev, pager, next, jumper, total"
:total="total"
class="custom-pagination"
@current-change="
(e) => { (e) => {
; (handleBackTop(), goToPage(e)) ;(handleBackTop(), goToPage(e))
} }
" @size-change="changePageSize" /> "
@size-change="changePageSize"
/>
</div> </div>
</div> </div>
</div> </div>
......
<template> <template>
<div class="min-h-screen p-6 font-sans text-gray-800 flex justify-center"> <div class="min-h-screen pb-10 font-sans text-slate-800 flex justify-center">
<!-- <div class="w-full max-w-[1000px] space-y-4">
背景调整:bg-[#f9fafb]
这是一种极接近白色的淡灰,视觉上非常干净,
但能保证卡片的白色阴影依然可见。
-->
<div class="w-full max-w-4xl space-y-4">
<!-- 1. 问题卡片 --> <!-- 1. 问题卡片 -->
<!-- 圆角调整:rounded-xl (12px),这是最标准的圆角大小 --> <div
<div class="bg-white rounded-xl p-8 shadow-sm border border-gray-100"> class="bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100"
<!-- 标签 --> >
<div class="flex gap-2 mb-4"> <!-- 顶部标签行 -->
<el-tag <div class="flex flex-wrap gap-2 mb-4">
v-for="tag in tags" <span
:key="tag.id" v-for="tag in questionDetail.tagNameList"
class="!border-none !bg-blue-50 !text-blue-600 !px-3 !h-7 !text-xs font-bold rounded-md" :key="tag"
class="px-2.5 py-0.5 rounded-full bg-blue-50 text-blue-500 text-xs font-semibold hover:bg-blue-100 transition-colors cursor-pointer"
> >
{{ tag.name }} #{{ tag }}
</el-tag> </span>
</div> </div>
<!-- 标题 --> <!-- 标题:主要信息,黑重粗 -->
<h1 class="text-2xl font-bold text-gray-900 mb-3 leading-snug"> <h1 class="text-2xl font-bold text-slate-900 mb-3 leading-snug tracking-tight">
{{ questionDetail.title }} {{ questionDetail.title }}
</h1> </h1>
<!-- 描述 --> <!-- 描述:次要信息,深灰 -->
<p class="text-gray-600 leading-relaxed mb-6 text-sm md:text-base"> <div class="text-slate-700 leading-relaxed text-[15px] mb-6">
{{ questionDetail.description }} <span :class="{ 'line-clamp-3': !isExpandDesc }">
<span class="text-blue-600 cursor-pointer hover:underline text-sm font-medium ml-1" {{ questionDetail.content }}
>显示全部</span </span>
<button
v-if="questionDetail.content && questionDetail.content.length > 100"
class="text-blue-500 text-sm font-medium hover:text-blue-600 mt-1 flex items-center gap-0.5"
@click="isExpandDesc = !isExpandDesc"
> >
</p> {{ isExpandDesc ? '收起' : '显示全部' }}
<el-icon :class="{ 'rotate-180': isExpandDesc }" class="transition-transform"
><CaretBottom
/></el-icon>
</button>
</div>
<!-- 操作栏 --> <!-- 底部操作栏 -->
<div class="flex flex-wrap items-center justify-between gap-4 pt-2"> <div class="flex items-center justify-between mt-4">
<div class="flex gap-3"> <div class="flex gap-3">
<el-button <button
type="primary" class="px-5 py-1.5 border border-blue-500 text-blue-500 hover:bg-blue-50 rounded-[4px] text-sm font-medium transition-colors flex items-center gap-1 cursor-pointer"
class="!rounded-lg !px-5 !h-9 !text-sm !font-medium !bg-blue-600 !border-blue-600 hover:!bg-blue-700 hover:!shadow-md transition-all"
@click="isFollowing = !isFollowing"
>
{{ isFollowing ? '已关注' : '关注问题' }}
</el-button>
<el-button
class="!rounded-lg !px-5 !h-9 !text-sm !font-medium !text-blue-600 !border-blue-200 !bg-blue-50 hover:!bg-blue-100 transition-colors"
> >
<el-icon class="mr-1"><Plus /></el-icon> <el-icon><EditPen /></el-icon>
写回答 写回答
</el-button> </button>
<el-button
class="!rounded-lg !h-9 !px-4 !text-gray-500 hover:!text-gray-700 !bg-transparent !border-gray-200"
>
邀请回答
</el-button>
</div> </div>
<div class="flex gap-5 text-gray-400 text-sm"> <!-- 右侧数据 -->
<div <div class="flex items-center gap-6 text-slate-400 text-sm select-none">
class="flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors" <span
@click="handleLikeArticle"
class="hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
> >
<el-icon class="text-base"><ChatDotRound /></el-icon> <el-icon>
<span>2 条评论</span> <svg-icon :name="questionDetail?.hasPraised ? 'praise_fill' : 'praise'"></svg-icon>
</div> </el-icon>
<div <span :class="{ 'text-blue-500': questionDetail?.hasPraised }">{{
class="flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors" questionDetail?.praiseCount || 0
}}</span>
</span>
<span
@click="handleCollectArticle"
class="hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
> >
<el-icon class="text-base"><Share /></el-icon> <el-icon>
<span>分享</span> <svg-icon
</div> :name="questionDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon>
</el-icon>
<span :class="{ 'text-blue-500': questionDetail?.hasCollect }">{{
questionDetail?.collectionCount || 0
}}</span>
</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 2. 分隔栏 --> <!-- 2. 列表控制栏 -->
<div class="flex items-center justify-between px-1 py-1">
<span class="font-bold text-gray-900 text-base">105 个回答</span>
<div <div
class="flex items-center text-xs text-gray-500 cursor-pointer hover:text-blue-600 font-medium bg-white px-3 py-1 rounded-md shadow-sm border border-gray-100" class="bg-white rounded-t-lg border-b border-slate-100 p-6 flex justify-between items-center shadow-sm"
>
<h3 class="font-bold text-slate-800 text-base">{{ total }} 个回答</h3>
<div class="flex text-sm text-slate-500 bg-slate-50 rounded p-0.5">
<el-radio-group
size="small"
v-model="searchParams.sortType"
@change="(val) => changeSortType(val as number)"
>
<el-radio-button label="最新" :value="2" />
<el-radio-button label="最多评论" :value="1" />
<el-radio-button label="最多点赞" :value="4" />
</el-radio-group>
<!-- <span
@click="changeSortType(2)"
class="px-3 py-0.5 rounded shadow-sm font-medium cursor-pointer"
:class="{ 'text-slate-800 bg-white': searchParams.sortType === 2 }"
>最新</span
> >
按时间排序 <span
<el-icon class="ml-1"><CaretBottom /></el-icon> @click="changeSortType(1)"
class="px-3 py-0.5 rounded hover:text-slate-700 cursor-pointer transition-colors"
:class="{ 'text-slate-800 bg-white': searchParams.sortType === 1 }"
>最多评论</span
>
<span
@click="changeSortType(4)"
class="px-3 py-0.5 rounded hover:text-slate-700 cursor-pointer transition-colors"
:class="{ 'text-slate-800 bg-white': searchParams.sortType === 4 }"
>最多点赞</span
> -->
</div> </div>
</div> </div>
<!-- 3. 回答列表 --> <!-- 3. 回答列表 -->
<div class="space-y-3"> <div class="space-y-3">
<div <div
v-for="answer in answers" v-for="(answer, index) in list"
:key="answer.id" :key="answer.id"
class="bg-white rounded-xl p-8 shadow-sm border border-gray-100 hover:shadow-md transition-shadow duration-200" class="bg-white rounded-lg p-6 shadow-sm border border-slate-100 hover:border-slate-200 transition-colors"
> >
<!-- 用户 --> <!-- 用户头 -->
<div class="flex items-center mb-3"> <div class="flex items-center gap-3 mb-3">
<img :src="answer.user.avatar" class="w-9 h-9 rounded-full object-cover bg-gray-100" /> <img :src="answer.avatar" class="w-9 h-9 rounded bg-slate-100 object-cover" />
<div class="ml-3"> <div>
<div class="font-bold text-gray-900 text-sm">{{ answer.user.name }}</div> <div class="font-bold text-slate-900 text-sm flex items-center gap-2">
<div class="text-xs text-gray-400 mt-0.5">{{ answer.user.bio }}</div> {{ answer.hiddenName }}
<!-- 徽章示例 -->
<!-- <span
v-if="index === 0"
class="px-1.5 py-0.5 bg-yellow-50 text-yellow-600 text-[10px] rounded scale-90 origin-left"
>优秀回答</span
> -->
</div>
<div class="text-xs text-slate-400 mt-0.5 max-w-md truncate">
{{ answer.description || '暂无简介' }}
</div>
</div> </div>
</div> </div>
<!-- 赞同信息 --> <!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<div class="mb-3 text-xs text-gray-400 flex items-center"> <div class="text-xs text-slate-400 mb-2">
<span class="bg-gray-50 px-2 py-0.5 rounded text-gray-500"> {{ answer.praiseCount || 0 }} 人赞同了该回答
{{ answer.votes }} 人赞同了该回答
</span>
</div> </div>
<!-- 正文 --> <!-- 正文 -->
<div <div
class="text-gray-800 leading-7 text-sm md:text-base mb-6" class="text-slate-800 leading-7 text-[15px] mb-4 rich-text-content"
v-html="answer.content" v-html="answer.content"
></div> ></div>
<!-- 底部操作 --> <div class="text-xs text-slate-400 mb-4">
<div class="flex items-center text-sm text-gray-400 gap-5 select-none"> 发布于 {{ dayjs(answer.createTime * 1000).format('YYYY-MM-DD HH:mm') }}
<!-- 赞同按钮组:稍微方正一点的圆角 --> </div>
<div class="flex bg-blue-50/50 rounded-lg p-0.5 border border-blue-100/50">
<!-- 底部吸附操作栏 -->
<div class="flex items-center gap-4 select-none sticky bottom-0 bg-white pt-2">
<!-- 核心交互:赞同/反对胶囊 -->
<div class="flex items-center bg-blue-50/60 rounded-[4px] overflow-hidden">
<button <button
class="flex items-center px-3 py-1 rounded-md bg-white text-blue-600 shadow-sm text-xs font-bold hover:text-blue-700 transition-colors" class="flex items-center gap-1 px-3 py-1.5 text-blue-500 hover:bg-blue-100 transition-colors text-sm font-medium cursor-pointer"
:class="{ '!bg-blue-500 !text-white': answer.hasPraise }"
@click="handleLikeAnswer(answer)"
> >
<el-icon class="mr-1"><CaretTop /></el-icon> <el-icon><CaretTop /></el-icon>
赞同 {{ answer.votes }} <span
</button> >{{ answer.hasPraise ? '已赞同' : '赞同' }} {{ answer.praiseCount || '' }}</span
<button
class="flex items-center px-2 py-1 text-blue-400 hover:text-blue-600 transition-colors ml-0.5"
> >
<el-icon><CaretBottom /></el-icon>
</button> </button>
</div> <!-- <button
class="px-2 py-1.5 text-blue-600 hover:bg-blue-100 transition-colors border-l border-blue-100/50"
<div
class="flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors"
>
<el-icon class="text-base"><ChatDotRound /></el-icon>
<span v-if="answer.commentCount" class="text-xs"
>{{ answer.commentCount }} 条评论</span
> >
<span v-else class="text-xs">添加评论</span> <el-icon><CaretBottom /></el-icon>
</button> -->
</div> </div>
<div <button
class="flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors" class="flex items-center gap-1.5 text-slate-500 hover:text-slate-800 transition-colors text-sm cursor-pointer"
@click="handleComment(answer, index)"
> >
<el-icon class="text-base"><Share /></el-icon> <el-icon class="text-base"><ChatRound /></el-icon>
<span class="text-xs">分享</span> <span :class="{ 'text-slate-800 font-medium': answer.showComment }">
{{ answer.childrenNum ? `${answer.childrenNum} 条评论` : '添加评论' }}
</span>
</button>
</div> </div>
<!-- 内嵌评论区 -->
<!-- 增加一个边框和浅灰色背景,让它看起来像一个“容器” -->
<Transition name="fade">
<div <div
class="flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors" v-show="answer.showComment"
class="mt-4 border border-slate-200 rounded-lg bg-slate-50/50 overflow-hidden"
> >
<el-icon class="text-base"><Star /></el-icon> <Comment
<span class="text-xs">收藏</span> :ref="(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)"
:id="questionId"
:total="answer.childrenNum"
:defaultSize="5"
:isReal="0"
:immediate="false"
:isQuestion="true"
:commentId="answer.id"
@commentSuccess="() => handleCommentSuccess(answer)"
/>
</div> </div>
</Transition>
<span class="ml-auto text-xs text-gray-300 hidden sm:block">
{{ answer.publishDate }}
</span>
</div> </div>
</div> </div>
<!-- 底部加载更多 -->
<div class="py-3 flex justify-center bg-#fff">
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
:total="total"
layout="prev, pager, next,total"
background
small
@current-change="goToPage"
/>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getArticleDetail } from '@/api/article' import {
import type { ArticleItemDto } from '@/api/article/types' getArticleDetail,
getCommentList,
addOrCanceArticlelLike,
addOrCanceArticlelCollect,
addOrCancelCommentLike,
} from '@/api'
import type { ArticleItemDto } from '@/api/types'
import { usePageSearch } from '@/hooks'
import Comment from '@/components/common/Comment/index.vue'
import dayjs from 'dayjs'
const route = useRoute() const route = useRoute()
const questionId = route.params.id as string const questionId = Number(route.params.id)
const commentRefList = ref<InstanceType<typeof Comment>[]>([])
const questionDetail = ref<ArticleItemDto>({} as ArticleItemDto) const questionDetail = ref<ArticleItemDto>({} as ArticleItemDto)
const getQuestionDetail = async () => { const getQuestionDetail = async () => {
const res = await getArticleDetail(questionId) const { data } = await getArticleDetail(questionId)
console.log(res) questionDetail.value = data
console.log(questionDetail.value)
}
const { list, total, searchParams, goToPage, refresh } = usePageSearch(getCommentList, {
// immediate: false,
defaultParams: {
articleId: questionId,
sortType: 1,
},
formatList: (list) =>
list.map((item) => ({
...item,
showComment: false,
isExpand: false,
})),
})
const changeSortType = (type: number) => {
searchParams.value.sortType = type
refresh()
}
const handleLikeArticle = async () => {
await addOrCanceArticlelLike(questionId)
questionDetail.value.hasPraised = !questionDetail.value.hasPraised
questionDetail.value.praiseCount = questionDetail.value.hasPraised
? questionDetail.value.praiseCount + 1
: questionDetail.value.praiseCount - 1
ElMessage.success(`${questionDetail.value.hasPraised ? '点赞成功' : '取消点赞成功'}`)
}
const handleCollectArticle = async () => {
await addOrCanceArticlelCollect(questionId)
questionDetail.value.hasCollect = !questionDetail.value.hasCollect
questionDetail.value.collectionCount = questionDetail.value.hasCollect
? questionDetail.value.collectionCount + 1
: questionDetail.value.collectionCount - 1
ElMessage.success(`${questionDetail.value.hasCollect ? '收藏成功' : '取消收藏成功'}`)
}
const handleLikeAnswer = async (answer: any) => {
await addOrCancelCommentLike(answer.id)
ElMessage.success(`${answer.hasPraise ? '点赞成功' : '取消点赞成功'}`)
answer.hasPraise = !answer.hasPraise
answer.praiseCount = answer.hasPraise ? answer.praiseCount + 1 : answer.praiseCount - 1
}
const handleComment = (answer: any, index: number) => {
commentRefList.value[index]?.search()
answer.showComment = !answer.showComment
} }
getQuestionDetail()
onMounted(() => { onMounted(() => {
getQuestionDetail() getQuestionDetail()
}) })
</script> </script>
<style scoped></style> <style lang="scss" scoped>
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(-10px);
filter: blur(4px);
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
</style>
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