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
9662ea89
Commit
9662ea89
authored
Dec 15, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 perf: 继续优化
parent
1a8cb204
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
336 additions
and
180 deletions
+336
-180
index.ts
src/api/article/index.ts
+1
-1
types.ts
src/api/article/types.ts
+1
-0
index.vue
src/components/common/Comment/index.vue
+19
-18
index.vue
src/components/common/CommentDialog/index.vue
+6
-7
index.vue
src/views/homePage/askTab/index.vue
+13
-17
practiceList.vue
src/views/homePage/yaTab/components/practiceList.vue
+51
-23
index.vue
src/views/questionDetail/index.vue
+245
-114
No files found.
src/api/article/index.ts
View file @
9662ea89
...
...
@@ -335,7 +335,7 @@ export const getSecondCommentChildren = (data: {
articleId
:
number
})
=>
{
return
service
.
request
<
BackendServicePageResult
<
CommentItemDto
>>
({
url
:
`/api/cultureComment/
comment/children
`
,
url
:
`/api/cultureComment/
getAllComment
`
,
method
:
'POST'
,
data
,
})
...
...
src/api/article/types.ts
View file @
9662ea89
...
...
@@ -178,6 +178,7 @@ export interface ArticleItemDto {
type
:
ArticleTypeEnum
userId
:
string
viewCount
:
number
childNum
:
number
}
}
...
...
src/components/common/Comment/index.vue
View file @
9662ea89
...
...
@@ -298,11 +298,13 @@
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:total=
"
t
otal"
:total=
"
firstComentT
otal"
@
current-change=
"handleCurrentChange"
@
size-change=
"changePageSize"
layout=
"prev, pager, next, total"
/>
layout=
"prev, pager, next, slot"
>
<div>
共 {{ total }} 条
</div>
</el-pagination>
</div>
</div>
</div>
...
...
@@ -318,7 +320,7 @@ import {
getCommentChildren
,
getSecondCommentList
,
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
,
useHintAnimation
}
from
'@/hooks'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
BooleanFlag
}
from
'@/constants'
import
type
{
CommentItemDto
}
from
'@/api'
import
dayjs
from
'dayjs'
...
...
@@ -333,7 +335,7 @@ const {
isQuestion
=
false
,
commentId
=
0
,
}
=
defineProps
<
{
id
:
number
id
:
number
// 文章ID
defaultSize
?:
number
isReal
:
BooleanFlag
isQuestion
?:
boolean
// 如果是问题的话 展示有点不一样
...
...
@@ -351,7 +353,6 @@ const total = defineModel<number>('total', { required: true, default: 0 })
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
userAvatar
=
computed
(()
=>
(
isReal
?
userInfo
.
value
.
avatar
:
userInfo
.
value
.
hiddenAvatar
))
console
.
log
(
userAvatar
)
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentDialogRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentDialogRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentInputRef'
)
...
...
@@ -361,13 +362,17 @@ const { handleBackTop } = useScrollTop(commentRef)
// 回滚到子评论框
const
{
handleBackTop
:
handleBackTopChildren
}
=
useScrollTop
(
commentItemRefList
)
const
{
triggerAnimation
}
=
useHintAnimation
(
commentInputRef
,
{
classes
:
[
'scale-bounce'
,
'highlight'
,
'shake-x'
],
})
const
{
list
,
searchParams
,
goToPage
,
loading
,
changePageSize
,
refresh
,
search
}
=
usePageSearch
(
isQuestion
?
getSecondCommentList
:
getCommentList
,
{
console
.
log
(
commentId
,
'commentId'
)
const
{
list
,
searchParams
,
goToPage
,
loading
,
changePageSize
,
refresh
,
search
,
total
:
firstComentTotal
,
}
=
usePageSearch
(
isQuestion
?
getSecondCommentList
:
getCommentList
,
{
defaultParams
:
{
...(
commentId
?
{
pid
:
commentId
,
sortType
:
2
}
...
...
@@ -399,14 +404,10 @@ const { list, searchParams, goToPage, loading, changePageSize, refresh, search }
})
},
immediate
,
},
)
})
const
handleCurrentChange
=
async
(
e
:
number
)
=>
{
await
goToPage
(
e
)
handleBackTop
()
setTimeout
(()
=>
{
triggerAnimation
()
},
500
)
}
// 自己发出的评论
...
...
src/components/common/CommentDialog/index.vue
View file @
9662ea89
...
...
@@ -3,7 +3,7 @@
v-model=
"visible"
:title=
"dialogTitle"
width=
"600px"
class=
"
comment-reply-dialog
rounded-xl overflow-hidden"
class=
"rounded-xl overflow-hidden"
:show-close=
"false"
append-to-body
destroy-on-close
...
...
@@ -168,7 +168,8 @@
<input
ref=
"bottomInputRef"
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"
:placeholder=
"`回复 ${parentComment?.replyUser || '...'}`"
@
keyup
.
enter=
"submitReply(parentComment?.id)"
...
...
@@ -233,16 +234,14 @@ const scrollContainer = ref<HTMLElement>()
// --- Actions ---
const
{
list
,
total
,
search
,
searchParams
,
goToPage
,
changePageSize
}
=
usePageSearch
(
getSecondCommentChildren
,
{
const
{
list
,
total
,
search
,
searchParams
,
goToPage
,
changePageSize
,
refresh
,
loading
}
=
usePageSearch
(
getSecondCommentChildren
,
{
defaultParams
:
{
articleId
:
articleId
,
pid
:
0
,
},
immediate
:
false
,
},
)
})
const
open
=
async
(
item
:
CommentItemDto
)
=>
{
// const { data } = await getSecondCommentChildren({
...
...
src/views/homePage/askTab/index.vue
View file @
9662ea89
...
...
@@ -109,9 +109,10 @@
</el-button>
<!-- 回答按钮保持不变 -->
<!-- 当前最热评论的评论有几条 -->
<el-button
size=
"small"
plain
@
click
.
stop=
"handleComment(item, index)"
>
<el-icon><Edit
/></el-icon>
回答
{{
item
.
cultureCommentListVo
?.
childNum
||
0
}}
条评论
</el-button>
<ActionMore
class=
"ml-4"
:articleDetail=
"item"
/>
...
...
@@ -121,13 +122,13 @@
<!-- 右侧:统计信息 -->
<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>
<span
class=
"text-sm"
>
{{
item
.
viewCount
||
0
}}
</span>
</el-button>
</el-button>
-->
<!-- 收藏 -->
<el-button
<
!--
<
el-button
text
class=
"flex items-center gap-2 text-gray-500 transition-colors"
@
click
.
stop=
"handleCollect(item)"
...
...
@@ -136,17 +137,17 @@
<span
class=
"text-sm"
:class=
"
{ 'text-blue-500': item.hasCollect }">
{{
item
.
collectionCount
||
0
}}
</span>
</el-button>
</el-button>
-->
<!-- 评论 -->
<el-button
<
!--
<
el-button
text
class=
"flex items-center gap-2 text-gray-500 transition-colors"
@
click
.
stop=
"handleComment(item, index)"
>
<el-icon><ChatDotRound
/></el-icon>
<span
class=
"text-sm"
>
{{
item
.
replyCount
||
0
}}
</span>
</el-button>
<span
class=
"text-sm"
>
{{
item
.
cultureCommentListVo
?.
childNum
||
0
}}
</span>
</el-button>
-->
</div>
</div>
<Transition
name=
"fade"
>
...
...
@@ -154,7 +155,7 @@
v-show=
"item.showComment"
:ref=
"(e) => (commentRefList[index] = e as InstanceType
<typeof
Comment
>
)"
:id="item.id"
:total="item.
replyCount
"
:total="item.
cultureCommentListVo?.childNum || 0
"
:defaultSize="5"
:isReal="0"
:immediate="false"
...
...
@@ -211,19 +212,14 @@
<
script
setup
lang=
"ts"
>
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
{
useScrollTop
,
usePageSearch
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
PublishBox
from
'@/components/common/PublishBox/index.vue'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
dayjs
from
'dayjs'
import
{
getArticleList
,
addOrCanceArticlelCollect
,
addOrCancelToAnswerList
,
getSecondCommentList
,
}
from
'@/api'
import
{
getArticleList
,
addOrCanceArticlelCollect
,
addOrCancelToAnswerList
}
from
'@/api'
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
import
{
useQuestionStore
}
from
'@/stores/question'
import
ActionMore
from
'@/components/common/ActionMore/index.vue'
...
...
@@ -271,7 +267,7 @@ const handleComment = (item: ArticleItemDto, index: number) => {
}
const
handleCommentSuccess
=
(
item
:
ArticleItemDto
)
=>
{
item
.
replyCount
++
item
.
cultureCommentListVo
.
childNum
++
// 同时更新下
// 如果已经添加了回答 则改为未添加 并且更新问题数量
if
(
item
.
hasAddQuestion
)
{
...
...
src/views/homePage/yaTab/components/practiceList.vue
View file @
9662ea89
...
...
@@ -6,18 +6,26 @@
<!-- 标签导航 -->
<div
class=
"bg-white p-4 mb-6 rounded-lg shadow-sm"
>
<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'"
:effect=
"tag.id === searchParams.sortLogic ? 'dark' : 'plain'"
class=
"cursor-pointer"
@
click=
"toggleFilter(tag.id)"
>
:effect=
"tag.id === searchParams.sortLogic ? 'dark' : 'plain'"
class=
"cursor-pointer"
@
click=
"toggleFilter(tag.id)"
>
{{
tag
.
title
}}
</el-tag>
</div>
<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'"
:effect=
"searchParams.tagIdList?.includes(tag.id) ? 'dark' : 'plain'"
class=
"cursor-pointer"
@
click=
"toggleTag(tag.id)"
>
:effect=
"searchParams.tagIdList?.includes(tag.id) ? 'dark' : 'plain'"
class=
"cursor-pointer"
@
click=
"toggleTag(tag.id)"
>
{{
tag
.
title
}}
</el-tag>
</div>
...
...
@@ -33,40 +41,51 @@
{{
filterText
}}
</h2>
</div>
<div
class=
"text-#999 cursor-pointer text-sm"
@
click=
"
<div
class=
"text-#999 cursor-pointer text-sm"
@
click=
"
router.push(
{
path: '/searchPage',
query: {
type: ArticleTypeEnum.PRACTICE,
},
})
">
"
>
查看更多 >>
</div>
</div>
<div
class=
"divide-y bg-#fff"
>
<div
@
click=
"openArticleDetail(item.id)"
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
@
click=
"openArticleDetail(item.id)"
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"
>
<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
}}
</h1>
<!-- 带图片的内容 -->
<div
class=
"flex gap-3 mb-3 align-center"
style=
"border-right: 1.5px solid #ddd;"
>
<img
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 gap-3 mb-2 align-center"
>
<img
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=
"text-gray-600 text-
sm
leading-relaxed line-clamp-4"
>
<div
class=
"text-gray-600 text-
base
leading-relaxed line-clamp-4"
>
{{
item
.
content
}}
</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"
>
<el-icon
class=
"text-sm"
>
<View
/>
...
...
@@ -111,14 +130,23 @@
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
>
<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=
"
<div
class=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
>
<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) =>
{
;
(handleBackTop(), goToPage(e))
;(handleBackTop(), goToPage(e))
}
" @size-change="changePageSize" />
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
...
...
src/views/questionDetail/index.vue
View file @
9662ea89
<
template
>
<div
class=
"min-h-screen p-6 font-sans text-gray-800 flex justify-center"
>
<!--
背景调整:bg-[#f9fafb]
这是一种极接近白色的淡灰,视觉上非常干净,
但能保证卡片的白色阴影依然可见。
-->
<div
class=
"w-full max-w-4xl space-y-4"
>
<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"
>
<!-- 1. 问题卡片 -->
<!-- 圆角调整:rounded-xl (12px),这是最标准的圆角大小 -->
<div
class=
"bg-white rounded-xl p-8 shadow-sm border border-gray-100"
>
<!-- 标签 -->
<div
class=
"flex gap-2 mb-4"
>
<el-tag
v-for=
"tag in tags"
:key=
"tag.id"
class=
"!border-none !bg-blue-50 !text-blue-600 !px-3 !h-7 !text-xs font-bold rounded-md"
<div
class=
"bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100"
>
<!-- 顶部标签行 -->
<div
class=
"flex flex-wrap gap-2 mb-4"
>
<span
v-for=
"tag in questionDetail.tagNameList"
: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
}}
</
el-tag
>
#
{{
tag
}}
</
span
>
</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
}}
</h1>
<!-- 描述 -->
<p
class=
"text-gray-600 leading-relaxed mb-6 text-sm md:text-base"
>
{{
questionDetail
.
description
}}
<span
class=
"text-blue-600 cursor-pointer hover:underline text-sm font-medium ml-1"
>
显示全部
</span
<!-- 描述:次要信息,深灰 -->
<div
class=
"text-slate-700 leading-relaxed text-[15px] mb-6"
>
<span
:class=
"
{ 'line-clamp-3': !isExpandDesc }">
{{
questionDetail
.
content
}}
</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"
>
<el-button
type=
"primary"
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"
<button
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"
>
<el-icon
class=
"mr-1"
><Plus
/></el-icon>
<el-icon
><EditPen
/></el-icon>
写回答
</el-button>
<el-button
class=
"!rounded-lg !h-9 !px-4 !text-gray-500 hover:!text-gray-700 !bg-transparent !border-gray-200"
>
邀请回答
</el-button>
</button>
</div>
<div
class=
"flex gap-5 text-gray-400 text-sm"
>
<div
class=
"flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors"
<!-- 右侧数据 -->
<div
class=
"flex items-center gap-6 text-slate-400 text-sm select-none"
>
<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>
<span>
2 条评论
</span>
</div>
<div
class=
"flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors"
<el-icon>
<svg-icon
:name=
"questionDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
:class=
"
{ 'text-blue-500': questionDetail?.hasPraised }">
{{
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>
<span>
分享
</span>
</div>
<el-icon>
<svg-icon
: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>
<!-- 2. 分隔栏 -->
<div
class=
"flex items-center justify-between px-1 py-1"
>
<span
class=
"font-bold text-gray-900 text-base"
>
105 个回答
</span>
<!-- 2. 列表控制栏 -->
<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
>
按时间排序
<el-icon
class=
"ml-1"
><CaretBottom
/></el-icon>
<span
@
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>
<!-- 3. 回答列表 -->
<div
class=
"space-y-3"
>
<div
v-for=
"
answer in answers
"
v-for=
"
(answer, index) in list
"
: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"
>
<img
:src=
"answer.user.avatar"
class=
"w-9 h-9 rounded-full object-cover bg-gray-100"
/>
<div
class=
"ml-3"
>
<div
class=
"font-bold text-gray-900 text-sm"
>
{{
answer
.
user
.
name
}}
</div>
<div
class=
"text-xs text-gray-400 mt-0.5"
>
{{
answer
.
user
.
bio
}}
</div>
<!-- 用户头 -->
<div
class=
"flex items-center gap-3 mb-3"
>
<img
:src=
"answer.avatar"
class=
"w-9 h-9 rounded bg-slate-100 object-cover"
/>
<div>
<div
class=
"font-bold text-slate-900 text-sm flex items-center gap-2"
>
{{
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
class=
"mb-3 text-xs text-gray-400 flex items-center"
>
<span
class=
"bg-gray-50 px-2 py-0.5 rounded text-gray-500"
>
{{
answer
.
votes
}}
人赞同了该回答
</span>
<!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<div
class=
"text-xs text-slate-400 mb-2"
>
{{
answer
.
praiseCount
||
0
}}
人赞同了该回答
</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"
></div>
<!-- 底部操作 -->
<div
class=
"flex items-center text-sm text-gray-400 gap-5 select-none"
>
<!-- 赞同按钮组:稍微方正一点的圆角 -->
<div
class=
"flex bg-blue-50/50 rounded-lg p-0.5 border border-blue-100/50"
>
<div
class=
"text-xs text-slate-400 mb-4"
>
发布于
{{
dayjs
(
answer
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</div>
<!-- 底部吸附操作栏 -->
<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
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>
赞同
{{
answer
.
votes
}}
</button>
<button
class=
"flex items-center px-2 py-1 text-blue-400 hover:text-blue-600 transition-colors ml-0.5"
<el-icon><CaretTop
/></el-icon>
<span
>
{{
answer
.
hasPraise
?
'已赞同'
:
'赞同'
}}
{{
answer
.
praiseCount
||
''
}}
</span
>
<el-icon><CaretBottom
/></el-icon>
</button>
</div>
<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
<!--
<button
class=
"px-2 py-1.5 text-blue-600 hover:bg-blue-100 transition-colors border-l border-blue-100/50"
>
<span
v-else
class=
"text-xs"
>
添加评论
</span>
<el-icon><CaretBottom
/></el-icon>
</button>
-->
</div>
<div
class=
"flex items-center gap-1 cursor-pointer hover:text-gray-600 transition-colors"
<button
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>
<span
class=
"text-xs"
>
分享
</span>
<el-icon
class=
"text-base"
><ChatRound
/></el-icon>
<span
:class=
"
{ 'text-slate-800 font-medium': answer.showComment }">
{{
answer
.
childrenNum
?
`${answer.childrenNum
}
条评论`
:
'添加评论'
}}
<
/span
>
<
/button
>
<
/div
>
<!--
内嵌评论区
-->
<!--
增加一个边框和浅灰色背景,让它看起来像一个“容器”
-->
<
Transition
name
=
"fade"
>
<
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>
<span
class=
"text-xs"
>
收藏
</span>
<
Comment
:
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
>
<span
class=
"ml-auto text-xs text-gray-300 hidden sm:block"
>
{{
answer
.
publishDate
}}
</span>
<
/Transition
>
<
/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
>
<
/template
>
<
script
setup
lang
=
"ts"
>
import
{
getArticleDetail
}
from
'@/api/article'
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
import
{
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
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
getQuestionDetail
=
async
()
=>
{
const
res
=
await
getArticleDetail
(
questionId
)
console
.
log
(
res
)
const
{
data
}
=
await
getArticleDetail
(
questionId
)
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
(()
=>
{
getQuestionDetail
()
}
)
<
/script
>
<
style
scoped
></
style
>
<
style
lang
=
"scss"
scoped
>
.
fade
-
enter
-
from
,
.
fade
-
leave
-
to
{
opacity
:
0
;
transform
:
translateY
(
-
10
px
);
filter
:
blur
(
4
px
);
}
.
fade
-
enter
-
active
,
.
fade
-
leave
-
active
{
transition
:
all
0.4
s
cubic
-
bezier
(
0.25
,
0.46
,
0.45
,
0.94
);
}
<
/style
>
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