Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
corporate-culture-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
王立鹏
corporate-culture-qd
Commits
6f582a53
Commit
6f582a53
authored
Mar 24, 2026
by
王立鹏
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/21254-YAYA文化岛评论区新增功能需求' into 'master'
Feature/21254 yaya文化岛评论区新增功能需求 See merge request
!15
parents
7d1289e9
8e4c1e13
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
293 additions
and
36 deletions
+293
-36
index.ts
src/api/article/index.ts
+10
-0
index.vue
src/components/common/Comment/index.vue
+146
-16
usePageSearch.ts
src/hooks/usePageSearch.ts
+2
-2
index.vue
src/views/articleDetail/index.vue
+1
-0
index.vue
src/views/homePage/askTab/index.vue
+1
-0
index.vue
src/views/questionDetail/index.vue
+132
-18
index.vue
src/views/videoDetail/index.vue
+1
-0
No files found.
src/api/article/index.ts
View file @
6f582a53
...
@@ -369,3 +369,13 @@ export const getSpecificVideoWatchReward = (pageKey: SpecificVideoRewardEnum) =>
...
@@ -369,3 +369,13 @@ export const getSpecificVideoWatchReward = (pageKey: SpecificVideoRewardEnum) =>
},
},
})
})
}
}
/**
* 置顶 取消置顶评论
*/
export
const
topOrCancelTopComment
=
(
commentId
:
number
)
=>
{
return
service
.
request
<
boolean
>
({
url
:
`/api/cultureComment/topComment?commentId=
${
commentId
}
`
,
method
:
'POST'
,
})
}
src/components/common/Comment/index.vue
View file @
6f582a53
<
template
>
<
template
>
<div
<div
ref=
"commentRef"
class=
"bg-white rounded-3 shadow-sm border border-gray-100 overflow-hidden"
>
ref=
"commentRef"
class=
"bg-white rounded-lg shadow-sm border border-white/50 overflow-hidden"
>
<!-- 评论筛选 -->
<!-- 评论筛选 -->
<div
class=
"p-4 border-b border-gray-100"
>
<div
class=
"p-4 border-b border-gray-100"
>
<div
class=
"flex items-center gap-4 justify-between"
>
<div
class=
"flex items-center gap-4 justify-between"
>
...
@@ -58,12 +55,12 @@
...
@@ -58,12 +55,12 @@
<div>
<div>
<!-- 发表评论 -->
<!-- 发表评论 -->
<div
class=
"p-4 border-b border-gray-100"
>
<div
class=
"p
x-5 py
-4 border-b border-gray-100"
>
<div
class=
"flex gap-3"
>
<div
class=
"flex gap-3"
>
<img
<img
:src=
"userAvatar"
:src=
"userAvatar"
alt=
""
alt=
""
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
class=
"w-10 h-10 rounded-full object-cover cursor-pointer
ring-2 ring-slate-100
"
@
click=
"jumpToUserHomePage(
{ userId: userInfo.userId, type })"
@
click=
"jumpToUserHomePage(
{ userId: userInfo.userId, type })"
/>
/>
<CommentBox
<CommentBox
...
@@ -98,19 +95,50 @@
...
@@ -98,19 +95,50 @@
:key=
"item.id"
:key=
"item.id"
:ref=
"(el) => (commentItemRefList[index] = el as HTMLElement)"
:ref=
"(el) => (commentItemRefList[index] = el as HTMLElement)"
>
>
<div
class=
"p-4 transition-colors"
>
<div
class=
"px-5 py-4"
:class=
"{ 'top-comment-highlight': highlightCommentId === item.id }"
>
<div
class=
"flex gap-3"
>
<div
class=
"flex gap-3"
>
<img
<img
@
click=
"jumpToUserHomePage({ userId: item.userId, type })"
@
click=
"jumpToUserHomePage({ userId: item.userId, type })"
:src=
"isReal ? item.avatar : item.hiddenAvatar"
:src=
"isReal ? item.avatar : item.hiddenAvatar"
alt=
""
alt=
""
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
class=
"w-10 h-10 rounded-full object-cover cursor-pointer
ring-2 ring-slate-100
"
/>
/>
<div
class=
"flex-1"
>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<div
class=
"flex items-center justify-between mb-2"
>
<div
class=
"flex items-center gap-2"
>
<span
class=
"font-semibold text-gray-800"
>
{{
<span
class=
"font-semibold text-gray-800"
>
{{
isReal ? item.replyUser : item.hiddenName
isReal ? item.replyUser : item.hiddenName
}}
</span>
}}
</span>
<span
v-if=
"item.isTop === BooleanFlag.YES"
class=
"inline-flex items-center gap-1 px-2 py-0.5 text-13px leading-4 font-medium text-amber-700 bg-amber-50/80 border border-amber-200/70 rounded-full"
>
<span
class=
"top-badge-dot w-1.5 h-1.5 rounded-full bg-amber-500 shadow-[0_0_0_2px_rgba(245,158,11,0.18)]"
></span>
置顶评论
</span>
</div>
<!-- 作者有权利置顶 并且不是问吧(问吧是获取的二级评论列表) -->
<button
v-if=
"isAuthor && !isQuestion"
type=
"button"
class=
"group cursor-pointer inline-flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all duration-200 active:scale-98 disabled:opacity-55 disabled:cursor-not-allowed"
:class=
"
item.isTop === BooleanFlag.YES
? 'text-amber-700 border-amber-300/80 bg-amber-50 hover:bg-amber-100/80'
: 'text-slate-600 border-slate-200 bg-white hover:text-indigo-600 hover:border-indigo-200 hover:bg-indigo-50/70'
"
:disabled=
"topCommentPendingId !== null"
@
click=
"handleTopComment(item)"
>
<span
class=
"tracking-[0.2px]"
>
{{
item.isTop === BooleanFlag.YES ? '取消置顶' : '置顶评论'
}}
</span>
</button>
<!-- <span
<!-- <span
class="px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
class="px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
>
>
...
@@ -140,8 +168,8 @@
...
@@ -140,8 +168,8 @@
/>
/>
</div>
</div>
</div>
</div>
<div
class=
"flex items-center
justify-between
"
>
<div
class=
"flex items-center"
>
<div
class=
"flex items-center gap-
6
text-gray-500"
>
<div
class=
"flex items-center gap-
5
text-gray-500"
>
<span
class=
"flex items-center gap-2 text-[14px]"
>
<span
class=
"flex items-center gap-2 text-[14px]"
>
<span
<span
>
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
>
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
...
@@ -149,7 +177,7 @@
...
@@ -149,7 +177,7 @@
<span>
·
</span>
<span>
·
</span>
<span
class=
"text-gray-500"
>
{{ item.region }}
</span>
<span
class=
"text-gray-500"
>
{{ item.region }}
</span>
</span>
</span>
<div
class=
"flex gap-2 items-center hover:text-blue-500"
>
<div
class=
"flex gap-2 items-center hover:text-blue-500
transition-colors
"
>
<div
<div
class=
"flex items-center gap-1 cursor-pointer"
class=
"flex items-center gap-1 cursor-pointer"
@
click=
"handleLickComment(item)"
@
click=
"handleLickComment(item)"
...
@@ -183,7 +211,7 @@
...
@@ -183,7 +211,7 @@
@
click=
"jumpToUserHomePage(
{ userId: child.userId, type })"
@
click=
"jumpToUserHomePage(
{ userId: child.userId, type })"
:src="child.avatar"
:src="child.avatar"
alt=""
alt=""
class="w-8 h-8 rounded-full object-cover cursor-pointer"
class="w-8 h-8 rounded-full object-cover cursor-pointer
ring-1 ring-slate-200
"
/>
/>
<div
class=
"flex-1"
>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
...
@@ -329,8 +357,8 @@
...
@@ -329,8 +357,8 @@
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"px-
4
"
>
<div
class=
"px-
5
"
>
<el-divider
class=
"my-
2
!"
/>
<el-divider
class=
"my-
1.5! border-slate-100
!"
/>
</div>
</div>
</div>
</div>
<!-- 底部分页 -->
<!-- 底部分页 -->
...
@@ -367,6 +395,7 @@ import {
...
@@ -367,6 +395,7 @@ import {
addComment
,
addComment
,
getCommentChildren
,
getCommentChildren
,
getSecondCommentList
,
getSecondCommentList
,
topOrCancelTopComment
,
}
from
'@/api'
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
ArticleTypeEnum
,
BooleanFlag
}
from
'@/constants'
import
{
ArticleTypeEnum
,
BooleanFlag
}
from
'@/constants'
...
@@ -382,6 +411,7 @@ import { push } from 'notivue'
...
@@ -382,6 +411,7 @@ import { push } from 'notivue'
const
{
jumpToUserHomePage
}
=
useNavigation
()
const
{
jumpToUserHomePage
}
=
useNavigation
()
const
{
const
{
authorId
=
''
,
id
,
id
,
defaultSize
=
10
,
defaultSize
=
10
,
immediate
=
true
,
immediate
=
true
,
...
@@ -389,6 +419,7 @@ const {
...
@@ -389,6 +419,7 @@ const {
commentId
=
0
,
commentId
=
0
,
type
,
type
,
}
=
defineProps
<
{
}
=
defineProps
<
{
authorId
?:
string
// 文章作者id
id
:
number
// 文章ID
id
:
number
// 文章ID
defaultSize
?:
number
defaultSize
?:
number
isQuestion
?:
boolean
// 如果是问题的话 展示有点不一样
isQuestion
?:
boolean
// 如果是问题的话 展示有点不一样
...
@@ -414,7 +445,9 @@ const isReal = computed(
...
@@ -414,7 +445,9 @@ const isReal = computed(
const
userAvatar
=
computed
(()
=>
{
const
userAvatar
=
computed
(()
=>
{
return
isReal
.
value
?
userInfo
.
value
.
avatar
:
userInfo
.
value
.
hiddenAvatar
return
isReal
.
value
?
userInfo
.
value
.
avatar
:
userInfo
.
value
.
hiddenAvatar
})
})
const
isAuthor
=
computed
(()
=>
{
return
authorId
===
userInfo
.
value
.
userId
})
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentListDialogRef
=
useTemplateRef
<
typeof
CommentListDialog
>
(
'commentListDialogRef'
)
const
commentListDialogRef
=
useTemplateRef
<
typeof
CommentListDialog
>
(
'commentListDialogRef'
)
const
replyToOtherBoxRefList
=
ref
<
HTMLElement
[]
>
([])
const
replyToOtherBoxRefList
=
ref
<
HTMLElement
[]
>
([])
...
@@ -498,6 +531,40 @@ const handleLickComment = async (item: CommentItemDto) => {
...
@@ -498,6 +531,40 @@ const handleLickComment = async (item: CommentItemDto) => {
}
}
}
}
const
highlightCommentId
=
ref
<
number
|
null
>
(
null
)
const
topCommentPendingId
=
ref
<
number
|
null
>
(
null
)
const
handleTopComment
=
async
(
item
:
CommentItemDto
)
=>
{
if
(
topCommentPendingId
.
value
!==
null
)
return
try
{
topCommentPendingId
.
value
=
item
.
id
await
topOrCancelTopComment
(
item
.
id
)
const
isTopNow
=
item
.
isTop
===
BooleanFlag
.
YES
push
.
success
(
isTopNow
?
'取消置顶成功'
:
'置顶成功'
)
await
refresh
()
if
(
isTopNow
)
return
await
nextTick
()
const
idx
=
list
.
value
.
findIndex
((
i
:
CommentItemDto
)
=>
i
.
id
===
item
.
id
)
if
(
idx
!==
-
1
)
{
await
handleBackTopChildren
(
idx
)
highlightCommentId
.
value
=
item
.
id
setTimeout
(()
=>
{
if
(
highlightCommentId
.
value
===
item
.
id
)
{
highlightCommentId
.
value
=
null
}
},
2400
)
}
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
topCommentPendingId
.
value
=
null
}
}
const
handleReply
=
(
item
:
CommentItemDto
,
index
:
number
)
=>
{
const
handleReply
=
(
item
:
CommentItemDto
,
index
:
number
)
=>
{
replyPlaceholder
.
value
=
`回复@
${
item
.
replyUser
}
:`
replyPlaceholder
.
value
=
`回复@
${
item
.
replyUser
}
:`
commentToOther
.
value
=
''
commentToOther
.
value
=
''
...
@@ -663,4 +730,67 @@ defineExpose({
...
@@ -663,4 +730,67 @@ defineExpose({
opacity
:
1
;
opacity
:
1
;
transform
:
translateY
(
0
);
transform
:
translateY
(
0
);
}
}
.top-badge-dot
{
animation
:
topDotPulse
1.8s
ease-in-out
infinite
;
}
@keyframes
topDotPulse
{
0
%,
100
%
{
transform
:
scale
(
1
);
box-shadow
:
0
0
0
0
rgba
(
245
,
158
,
11
,
0.3
);
}
50
%
{
transform
:
scale
(
1.2
);
box-shadow
:
0
0
0
4px
rgba
(
245
,
158
,
11
,
0.12
);
}
}
.top-comment-highlight
{
position
:
relative
;
overflow
:
hidden
;
animation
:
commentFlash
2.4s
ease-out
;
}
.top-comment-highlight
::before
{
content
:
''
;
position
:
absolute
;
left
:
0
;
top
:
0%
;
width
:
3px
;
height
:
100%
;
border-radius
:
999px
;
background
:
linear-gradient
(
180deg
,
rgba
(
99
,
102
,
241
,
0.95
),
rgba
(
129
,
140
,
248
,
0.55
));
box-shadow
:
0
0
0
1px
rgba
(
99
,
102
,
241
,
0.08
);
animation
:
commentBarFlash
2.4s
ease-out
;
}
@keyframes
commentFlash
{
0
%
{
background-color
:
rgba
(
99
,
102
,
241
,
0.16
);
}
100
%
{
background-color
:
transparent
;
}
}
@keyframes
commentBarFlash
{
0
%
{
opacity
:
0
;
transform
:
translateY
(
6px
);
}
20
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
80
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
100
%
{
opacity
:
0
;
transform
:
translateY
(
-4px
);
}
}
</
style
>
</
style
>
src/hooks/usePageSearch.ts
View file @
6f582a53
...
@@ -108,9 +108,9 @@ export function usePageSearch<
...
@@ -108,9 +108,9 @@ export function usePageSearch<
await
search
()
await
search
()
}
}
const
refresh
=
()
=>
{
const
refresh
=
async
()
=>
{
searchParams
.
value
[
pageField
]
=
1
searchParams
.
value
[
pageField
]
=
1
search
()
await
search
()
}
}
// 清空搜索数据和列表
// 清空搜索数据和列表
...
...
src/views/articleDetail/index.vue
View file @
6f582a53
...
@@ -17,6 +17,7 @@
...
@@ -17,6 +17,7 @@
:id=
"id"
:id=
"id"
v-model:total=
"articleDetail.replyCount"
v-model:total=
"articleDetail.replyCount"
:type=
"articleDetail.type"
:type=
"articleDetail.type"
:authorId=
"articleDetail.createUserId"
/>
/>
</div>
</div>
</div>
</div>
...
...
src/views/homePage/askTab/index.vue
View file @
6f582a53
...
@@ -141,6 +141,7 @@
...
@@ -141,6 +141,7 @@
<Transition
name=
"fadeCommentBox"
mode=
"out-in"
>
<Transition
name=
"fadeCommentBox"
mode=
"out-in"
>
<Comment
<Comment
v-show=
"item.showComment"
v-show=
"item.showComment"
:authorId=
"item.createUserId"
: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.cultureCommentListVo?.childNum || 0"
:total=
"item.cultureCommentListVo?.childNum || 0"
...
...
src/views/questionDetail/index.vue
View file @
6f582a53
...
@@ -256,32 +256,52 @@
...
@@ -256,32 +256,52 @@
<div
class=
"space-y-3"
>
<div
class=
"space-y-3"
>
<div
<div
v-for=
"(answer, index) in list"
v-for=
"(answer, index) in list"
:ref=
"(el) => (answerRefList[index] = el as HTMLElement)"
:key=
"answer.id"
:key=
"answer.id"
class=
"bg-white rounded-lg p-6 shadow-sm border border-slate-100 hover:border-slate-200 transition-colors"
class=
"bg-white rounded-lg p-6 shadow-sm border border-slate-100 hover:border-slate-200 transition-colors"
:class=
"{ 'top-answer-highlight': highlightCommentId === answer.id }"
>
>
<!-- 用户头 -->
<!-- 用户头 -->
<div
class=
"flex items-center gap-3 mb-3"
>
<div
class=
"flex items-center justify-between gap-3 mb-3"
>
<div
class=
"flex items-center gap-3 min-w-0"
>
<el-avatar
<el-avatar
:src=
"answer.avatar"
:src=
"answer.avatar"
:size=
"36"
:size=
"36"
class=
"flex-shrink-0 cursor-pointer"
class=
"flex-shrink-0 cursor-pointer"
@
click=
"jumpToUserHomePage({ userId: answer.userId, type: ArticleTypeEnum.QUESTION })"
@
click=
"
jumpToUserHomePage({ userId: answer.userId, type: ArticleTypeEnum.QUESTION })
"
/>
/>
<div
>
<div
class=
"min-w-0"
>
<div
class=
"text-slate-900 text-sm flex items-center gap-2"
>
<div
class=
"text-slate-900 text-sm flex items-center gap-2"
>
{{ answer.replyUser }}
{{ answer.replyUser }}
<!-- 徽章示例 -->
<span
<!-- <span
v-if=
"answer.isTop === 1"
v-if="index === 0"
class=
"inline-flex items-center gap-1 px-2 py-0.5 text-13px leading-4 font-medium text-amber-700 bg-amber-50/80 border border-amber-200/70 rounded-full"
class="px-1.5 py-0.5 bg-yellow-50 text-yellow-600 text-[10px] rounded scale-90 origin-left"
>
>优秀回答</span
<span
> -->
class=
"top-badge-dot w-1.5 h-1.5 rounded-full bg-amber-500 shadow-[0_0_0_2px_rgba(245,158,11,0.18)]"
></span>
置顶回答
</span>
</div>
</div>
<!-- <div class="text-xs text-slate-500 mt-0.5 max-w-md truncate">
{{ answer.description || '暂无简介' }}
</div> -->
</div>
</div>
</div>
</div>
<button
v-if=
"isAuthor"
type=
"button"
class=
"group cursor-pointer inline-flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-full border transition-all duration-200 active:scale-98 disabled:opacity-55 disabled:cursor-not-allowed"
:class=
"
answer.isTop === 1
? 'text-amber-700 border-amber-300/80 bg-amber-50 hover:bg-amber-100/80'
: 'text-slate-600 border-slate-200 bg-white hover:text-indigo-600 hover:border-indigo-200 hover:bg-indigo-50/70'
"
:disabled=
"topCommentPendingId !== null"
@
click=
"handleTopAnswer(answer)"
>
{{ answer.isTop === 1 ? '取消置顶' : '置顶回答' }}
</button>
</div>
<!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<div
v-if=
"answer.postPriseCount"
class=
"text-[14px] text-slate-500 mb-2"
>
<div
v-if=
"answer.postPriseCount"
class=
"text-[14px] text-slate-500 mb-2"
>
...
@@ -317,7 +337,7 @@
...
@@ -317,7 +337,7 @@
</div>
</div>
<!-- 底 部吸附操作栏 -->
<!-- 底 部吸附操作栏 -->
<div
class=
"flex items-center gap-4 select-none
sticky bottom-0 bg-white
"
>
<div
class=
"flex items-center gap-4 select-none"
>
<!-- 核心交互:赞同/反对胶囊 -->
<!-- 核心交互:赞同/反对胶囊 -->
<div
class=
"flex items-center bg-blue-50/60 rounded-[4px] overflow-hidden"
>
<div
class=
"flex items-center bg-blue-50/60 rounded-[4px] overflow-hidden"
>
<button
<button
...
@@ -357,6 +377,7 @@
...
@@ -357,6 +377,7 @@
class=
"mt-4 border border-slate-200 rounded-lg bg-slate-50/50 overflow-hidden"
class=
"mt-4 border border-slate-200 rounded-lg bg-slate-50/50 overflow-hidden"
>
>
<Comment
<Comment
:authorId=
"questionDetail.createUserId"
:ref=
"(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)"
:ref=
"(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)"
:id=
"questionId"
:id=
"questionId"
:total=
"answer.childrenNum"
:total=
"answer.childrenNum"
...
@@ -393,8 +414,9 @@ import {
...
@@ -393,8 +414,9 @@ import {
addOrCanceArticlelLike
,
addOrCanceArticlelLike
,
addOrCanceArticlelCollect
,
addOrCanceArticlelCollect
,
addOrCancelCommentLike
,
addOrCancelCommentLike
,
topOrCancelTopComment
,
}
from
'@/api'
}
from
'@/api'
import
type
{
ArticleItemDto
}
from
'@/api'
import
type
{
ArticleItemDto
,
CommentItemDto
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
usePageSearch
}
from
'@/hooks'
import
Comment
from
'@/components/common/Comment/index.vue'
import
Comment
from
'@/components/common/Comment/index.vue'
import
CommentDialog
from
'@/components/common/CommentDialog/index.vue'
import
CommentDialog
from
'@/components/common/CommentDialog/index.vue'
...
@@ -402,9 +424,9 @@ import BackButton from '@/components/common/BackButton/index.vue'
...
@@ -402,9 +424,9 @@ import BackButton from '@/components/common/BackButton/index.vue'
import
dayjs
from
'dayjs'
import
dayjs
from
'dayjs'
import
{
useUserStore
}
from
'@/stores/user'
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
import
{
storeToRefs
}
from
'pinia'
import
{
useNavigation
}
from
'@/hooks'
import
{
useNavigation
,
useScrollTop
}
from
'@/hooks'
import
{
parseEmoji
}
from
'@/utils/emoji'
import
{
parseEmoji
}
from
'@/utils/emoji'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
,
BooleanFlag
}
from
'@/constants'
import
{
push
}
from
'notivue'
import
{
push
}
from
'notivue'
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
{
userInfo
}
=
storeToRefs
(
userStore
)
...
@@ -414,9 +436,12 @@ const router = useRouter()
...
@@ -414,9 +436,12 @@ const router = useRouter()
const
questionId
=
Number
(
route
.
params
.
id
)
const
questionId
=
Number
(
route
.
params
.
id
)
const
commentRefList
=
ref
<
InstanceType
<
typeof
Comment
>
[]
>
([])
const
commentRefList
=
ref
<
InstanceType
<
typeof
Comment
>
[]
>
([])
const
answerRefList
=
ref
<
HTMLElement
[]
>
([])
const
questionDetail
=
ref
<
ArticleItemDto
>
({}
as
ArticleItemDto
)
const
questionDetail
=
ref
<
ArticleItemDto
>
({}
as
ArticleItemDto
)
const
commentDialogRef
=
useTemplateRef
<
typeof
CommentDialog
>
(
'commentDialogRef'
)
const
commentDialogRef
=
useTemplateRef
<
typeof
CommentDialog
>
(
'commentDialogRef'
)
// 回滚到子评论框
const
{
handleBackTop
:
handleBackTopChildren
}
=
useScrollTop
(
answerRefList
)
const
loading
=
computed
(()
=>
!
questionDetail
.
value
.
title
)
const
loading
=
computed
(()
=>
!
questionDetail
.
value
.
title
)
const
isAuthor
=
computed
(()
=>
{
const
isAuthor
=
computed
(()
=>
{
...
@@ -479,13 +504,45 @@ const handleCollectArticle = async () => {
...
@@ -479,13 +504,45 @@ const handleCollectArticle = async () => {
push
.
success
(
`
${
questionDetail
.
value
.
hasCollect
?
'收藏成功'
:
'取消收藏成功'
}
`
)
push
.
success
(
`
${
questionDetail
.
value
.
hasCollect
?
'收藏成功'
:
'取消收藏成功'
}
`
)
}
}
const
handleLikeAnswer
=
async
(
answer
:
any
)
=>
{
const
handleLikeAnswer
=
async
(
answer
:
CommentItemDto
)
=>
{
await
addOrCancelCommentLike
(
answer
.
id
)
await
addOrCancelCommentLike
(
answer
.
id
)
answer
.
hasPraise
=
!
answer
.
hasPraise
answer
.
hasPraise
=
answer
.
hasPraise
===
BooleanFlag
.
YES
?
BooleanFlag
.
NO
:
BooleanFlag
.
YES
answer
.
postPriseCount
=
answer
.
hasPraise
?
answer
.
postPriseCount
+
1
:
answer
.
postPriseCount
-
1
answer
.
postPriseCount
=
answer
.
hasPraise
?
answer
.
postPriseCount
+
1
:
answer
.
postPriseCount
-
1
push
.
success
(
`
${
answer
.
hasPraise
?
'点赞该回答'
:
'取消点赞该回答'
}
`
)
push
.
success
(
`
${
answer
.
hasPraise
?
'点赞该回答'
:
'取消点赞该回答'
}
`
)
}
}
const
topCommentPendingId
=
ref
<
number
|
null
>
(
null
)
const
highlightCommentId
=
ref
<
number
|
null
>
(
null
)
const
handleTopAnswer
=
async
(
answer
:
CommentItemDto
)
=>
{
if
(
topCommentPendingId
.
value
!==
null
)
return
try
{
topCommentPendingId
.
value
=
answer
.
id
await
topOrCancelTopComment
(
answer
.
id
)
const
isTopNow
=
answer
.
isTop
===
BooleanFlag
.
YES
push
.
success
(
isTopNow
?
'取消置顶成功'
:
'置顶成功'
)
await
refresh
()
if
(
isTopNow
)
return
await
nextTick
()
const
idx
=
list
.
value
.
findIndex
((
i
:
CommentItemDto
)
=>
i
.
id
===
answer
.
id
)
if
(
idx
!==
-
1
)
{
await
handleBackTopChildren
(
idx
)
highlightCommentId
.
value
=
answer
.
id
setTimeout
(()
=>
{
if
(
highlightCommentId
.
value
===
answer
.
id
)
{
highlightCommentId
.
value
=
null
}
},
2400
)
}
}
catch
(
error
)
{
console
.
error
(
error
)
}
finally
{
topCommentPendingId
.
value
=
null
}
}
const
handleComment
=
(
answer
:
any
,
index
:
number
)
=>
{
const
handleComment
=
(
answer
:
any
,
index
:
number
)
=>
{
commentRefList
.
value
[
index
]?.
search
()
commentRefList
.
value
[
index
]?.
search
()
answer
.
showComment
=
!
answer
.
showComment
answer
.
showComment
=
!
answer
.
showComment
...
@@ -528,4 +585,61 @@ onMounted(() => {
...
@@ -528,4 +585,61 @@ onMounted(() => {
background-color
:
#f1f5f9
;
background-color
:
#f1f5f9
;
}
}
}
}
.top-badge-dot
{
animation
:
topDotPulse
2.4s
ease-in-out
infinite
;
}
@keyframes
topDotPulse
{
0
%,
100
%
{
transform
:
scale
(
1
);
box-shadow
:
0
0
0
0
rgba
(
245
,
158
,
11
,
0.28
);
}
50
%
{
transform
:
scale
(
1.2
);
box-shadow
:
0
0
0
4px
rgba
(
245
,
158
,
11
,
0.1
);
}
}
.top-answer-highlight
{
position
:
relative
;
overflow
:
hidden
;
border-left
:
3px
solid
rgba
(
99
,
102
,
241
,
0.65
);
animation
:
answerFlash
2.4s
ease-out
;
}
@keyframes
answerFlash
{
0
%
{
border-left-color
:
rgba
(
99
,
102
,
241
,
0.78
);
background-color
:
rgba
(
99
,
102
,
241
,
0.12
);
}
60
%
{
border-left-color
:
rgba
(
99
,
102
,
241
,
0.52
);
background-color
:
rgba
(
99
,
102
,
241
,
0.06
);
}
100
%
{
border-left-color
:
rgba
(
99
,
102
,
241
,
0.2
);
background-color
:
transparent
;
}
}
@keyframes
answerBarFlash
{
0
%
{
opacity
:
0
;
transform
:
translateY
(
6px
);
}
20
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
80
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
100
%
{
opacity
:
0
;
transform
:
translateY
(
-4px
);
}
}
</
style
>
</
style
>
src/views/videoDetail/index.vue
View file @
6f582a53
...
@@ -302,6 +302,7 @@
...
@@ -302,6 +302,7 @@
</div>
</div>
<Comment
<Comment
:authorId=
"videoDetail.createUserId"
ref=
"commentRef"
ref=
"commentRef"
:id=
"videoId"
:id=
"videoId"
v-model:total=
"videoDetail.replyCount"
v-model:total=
"videoDetail.replyCount"
...
...
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