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
96bad3c0
Commit
96bad3c0
authored
Feb 13, 2026
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 20331】 feat: 文章详情页(帖子等相关、视频、问吧)加入骨架屏
parent
e6d3866d
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
676 additions
and
439 deletions
+676
-439
index.vue
src/components/common/ArticleContent/index.vue
+211
-120
index.vue
src/views/questionDetail/index.vue
+196
-121
rewardDialog.vue
src/views/videoDetail/components/rewardDialog.vue
+13
-10
index.vue
src/views/videoDetail/index.vue
+256
-188
No files found.
src/components/common/ArticleContent/index.vue
View file @
96bad3c0
<
template
>
<div
class=
"bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden"
class=
"
p-6
bg-white backdrop-blur-sm rounded-lg shadow-sm border border-white/50 overflow-hidden"
>
<!-- 发布者信息 -->
<div
class=
"p-6 border-b border-gray-100 pb-0"
>
<div
class=
"flex items-center gap-4"
>
<div
class=
"relative"
>
<img
:src=
"articleDetail?.createUserAvatar"
alt=
""
class=
"w-12 h-12 rounded-full object-cover cursor-pointer"
@
click=
"
jumpToUserHomePage(
{
userId: articleDetail?.createUserId,
isReal:
articleDetail.type === 'practice' || articleDetail.type === 'interview' ? 1 : 0,
})
"
/>
<!--
<div
<el-skeleton
:rows=
"5"
animated
:loading=
"loading"
:throttle=
"
{ leading: 0, trailing: 1500 }">
<template
#
template
>
<!-- 发布者信息 -->
<div
class=
"border-b border-gray-100 pb-0"
>
<div
class=
"flex items-center gap-4"
>
<!-- 头像 -->
<el-skeleton-item
variant=
"circle"
style=
"width: 48px; height: 48px"
/>
<!-- 用户名 + 时间 -->
<div
class=
"flex-1"
>
<el-skeleton-item
variant=
"text"
style=
"width: 120px; height: 16px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 220px; height: 14px; margin-top: 6px"
/>
</div>
<!-- 编辑 -->
<!--
<el-skeleton-item
variant=
"text"
style=
"width: 40px; height: 16px"
/>
-->
<!-- 右侧标签 / 操作 -->
<div
class=
"flex items-center gap-3"
>
<el-skeleton-item
variant=
"text"
style=
"width: 72px; height: 28px; border-radius: 6px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 72px; height: 28px; border-radius: 6px"
/>
<!--
<el-skeleton-item
variant=
"circle"
style=
"width: 24px; height: 24px"
/>
-->
</div>
</div>
</div>
<!-- 正文区域 -->
<div
class=
"mt-6"
>
<!-- 标题 -->
<el-skeleton-item
variant=
"text"
style=
"width: 90%; height: 32px; margin-bottom: 16px"
/>
<!-- 顶部视频占位 -->
<!--
<el-skeleton-item
variant=
"image"
style=
"width: 80%; height: 320px; margin: 0 auto 24px; border-radius: 8px"
/>
-->
<!-- 文章内容 -->
<div
class=
"space-y-3"
>
<el-skeleton-item
variant=
"text"
style=
"width: 100%"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 96%"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 92%"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 88%"
/>
</div>
<!-- 图片内容 -->
<!--
<div
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<el-skeleton-item
v-for=
"i in 2"
:key=
"i"
variant=
"image"
style=
"width: 100%; height: 256px; border-radius: 12px"
/>
</div>
-->
<!-- 底部视频 -->
<!--
<el-skeleton-item
variant=
"image"
style=
"width: 80%; height: 320px; margin: 24px auto; border-radius: 8px"
/>
-->
<!-- 标签 -->
<div
class=
"flex flex-wrap gap-2 mt-6"
>
<el-skeleton-item
v-for=
"i in 3"
:key=
"i"
variant=
"text"
style=
"width: 72px; height: 28px; border-radius: 9999px"
/>
</div>
</div>
</
template
>
<
template
#
default
>
<!-- 发布者信息 -->
<div
class=
"border-b border-gray-100 pb-0"
>
<div
class=
"flex items-center gap-4"
>
<div
class=
"relative"
>
<img
:src=
"articleDetail?.createUserAvatar"
alt=
""
class=
"w-12 h-12 rounded-full object-cover cursor-pointer"
@
click=
"
jumpToUserHomePage(
{
userId: articleDetail?.createUserId,
isReal:
articleDetail.type === 'practice' || articleDetail.type === 'interview'
? 1
: 0,
})
"
/>
<!--
<div
class=
"absolute -bottom-1 -right-1 w-6 h-6 bg-gradient-to-r from-yellow-400 to-orange-400 rounded-full flex items-center justify-center text-xs font-bold text-white"
>
8
</div>
-->
</div>
</div>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2"
>
<h3
class=
"font-semibold text-gray-800"
>
{{
articleDetail
?.
createUserName
}}
</h3>
<!--
<span
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2"
>
<h3
class=
"font-semibold text-gray-800"
>
{{
articleDetail
?.
createUserName
}}
</h3>
<!--
<span
class=
"px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
>
资深前端工程师
</span>
-->
</div>
<p
class=
"text-sm text-gray-500 mt-1"
>
{{
dayjs
((
articleDetail
?.
createTime
||
0
)
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
·
{{
articleDetail
?.
viewCount
||
0
}}
阅读 ·
{{
articleDetail
?.
region
||
0
}}
</p>
</div>
<!-- 再次编辑按钮 -->
<el-link
v-if=
"isAuthor"
type=
"primary"
:underline=
"false"
@
click=
"
router.push(`/publishLongArticle/$
{articleDetail.type}?id=${articleDetail.id}`)
"
class="text-sm"
>
编辑
</el-link>
<!-- 优化后的右侧内容 -->
<div
class=
"flex items-center gap-3"
>
<span
class=
"px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
v-if=
"articleDetail.relateColumn"
>
{{
articleDetail
.
relateColumn
}}
</span>
<span
class=
"px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
>
{{
articleType
}}
</span>
<ActionMore
:articleDetail=
"articleDetail"
/>
</div>
</div>
<p
class=
"text-sm text-gray-500 mt-1"
>
{{
dayjs
((
articleDetail
?.
createTime
||
0
)
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
·
{{
articleDetail
?.
viewCount
||
0
}}
阅读 ·
{{
articleDetail
?.
region
||
0
}}
</p>
</div>
<!-- 再次编辑按钮 -->
<el-link
v-if=
"isAuthor"
type=
"primary"
:underline=
"false"
@
click=
"router.push(`/publishLongArticle/$
{articleDetail.type}?id=${articleDetail.id}`)"
class="text-sm"
>
编辑
</el-link>
<!-- 优化后的右侧内容 -->
<div
class=
"flex items-center gap-3"
>
<span
class=
"px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
v-if=
"articleDetail.relateColumn"
>
{{
articleDetail
.
relateColumn
}}
</span>
<span
class=
"px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
>
{{
articleType
}}
</span>
<div
class=
"mt-6"
>
<h1
class=
"text-2xl font-bold text-gray-900 mb-4 leading-tight"
>
{{
articleDetail
?.
title
}}
</h1>
<!-- 顶部添加的视频 剧中固定宽度 -->
<div
v-if=
"showTopVideo"
class=
"flex justify-center"
>
<video
:src=
"articleDetail.articleVideoUrl"
controls
class=
"w-100%! aspect-video bg-black"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
<!-- 文章内容 -->
<div
v-if=
"!isHtml"
class=
"prose prose-lg max-w-none"
>
<div
class=
"text-gray-700 leading-relaxed space-y-4 whitespace-pre-line text-17px"
>
{{
articleDetail
?.
content
}}
</div>
<ActionMore
:articleDetail=
"articleDetail"
/>
</div>
</div>
</div>
<div
class=
"p-6"
>
<h1
class=
"text-2xl font-bold text-gray-900 mb-4 leading-tight"
>
{{
articleDetail
?.
title
}}
</h1>
<!-- 顶部添加的视频 剧中固定宽度 -->
<div
v-if=
"showTopVideo"
class=
"flex justify-center"
>
<video
:src=
"articleDetail.articleVideoUrl"
controls
class=
"w-80% aspect-video bg-black"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
<!-- 文章内容 -->
<div
v-if=
"!isHtml"
class=
"prose prose-lg max-w-none"
>
<div
class=
"text-gray-700 leading-relaxed space-y-4 whitespace-pre-line text-17px"
>
{{
articleDetail
?.
content
}}
</div>
<!-- 图片内容 -->
<div
v-if=
"articleDetail.imgUrl && articleDetail.type !== ArticleTypeEnum.VIDEO"
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<el-image
v-for=
"item in articleDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
fit=
"cover"
class=
"rounded-lg w-full h-64 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"articleDetail.imgUrl.split(',')"
:preview-teleported=
"true"
/>
</div>
<div
v-if=
"articleDetail.type === ArticleTypeEnum.VIDEO"
class=
"flex justify-center"
>
<video
:src=
"articleDetail.videoUrl"
controls
class=
"w-100%! aspect-video bg-black"
:poster=
"`$
{articleDetail.videoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
</div>
<!-- 图片内容 --
>
<div
v-if=
"articleDetail.imgUrl && articleDetail.type !== ArticleTypeEnum.VIDEO"
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<el-image
v-for=
"item in articleDetail.imgUrl.split(',')
"
:key=
"item"
:src=
"item
"
fit=
"cover
"
class=
"rounded-lg w-full h-64 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"articleDetail.imgUrl.split(',')"
:preview-teleported=
"true"
/
>
</div>
<div
v-if=
"articleDetail.type === ArticleTypeEnum.VIDEO"
class=
"flex justify-center"
>
<video
:src=
"articleDetail.videoUrl
"
controls
class=
"w-80% aspect-video bg-black"
:poster=
"`$
{articleDetail.videoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/
>
<div
v-else
v-html=
"articleDetail.content"
class=
"html-content"
v-image-preview
/
>
<!-- 底部添加的视频 -->
<div
v-if=
"showBottomVideo"
class=
"flex justify-center"
>
<!-- 长宽 1/1 --
>
<video
:src=
"articleDetail.articleVideoUrl
"
controls
class=
"w-100%! aspect-video bg-black
"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`
"
/>
</div>
<!-- 标签 -->
<div
class=
"flex flex-wrap gap-2 mt-6"
>
<span
v-for=
"item in articleDetail?.tagNameList"
:key=
"item"
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer
"
>
#
{{
item
}}
</span>
</div
>
</div>
</div>
<div
v-else
v-html=
"articleDetail.content"
class=
"html-content"
v-image-preview
/>
<!-- 底部添加的视频 -->
<div
v-if=
"showBottomVideo"
class=
"flex justify-center"
>
<!-- 长宽 1/1 -->
<video
:src=
"articleDetail.articleVideoUrl"
controls
class=
"w-80% aspect-video bg-black"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
<!-- 标签 -->
<div
class=
"flex flex-wrap gap-2 mt-6"
>
<span
v-for=
"item in articleDetail?.tagNameList"
:key=
"item"
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
#
{{
item
}}
</span>
</div>
</div>
</
template
>
</el-skeleton>
<!-- 富文本内容的 图片预览 -->
<el-image-viewer
v-if=
"showPreview"
...
...
@@ -168,6 +256,9 @@ const { articleDetail } = defineProps<{
const
articleType
=
computed
(()
=>
{
return
articleTypeListOptions
.
find
((
item
)
=>
item
.
value
===
articleDetail
.
type
)?.
label
})
const
loading
=
computed
(()
=>
!
articleDetail
.
title
)
// 是否是作者
const
isAuthor
=
computed
(()
=>
{
return
articleDetail
.
createUserId
===
userInfo
.
value
.
userId
...
...
src/views/questionDetail/index.vue
View file @
96bad3c0
...
...
@@ -5,130 +5,203 @@
<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 justify-between"
>
<div>
<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 mr-2"
>
#
{{
tag
}}
</span>
</div>
<!-- 后面加一个编辑 -->
<el-link
v-if=
"isAuthor"
type=
"primary"
:underline=
"false"
@
click=
"
router.push(`/publishLongArticle/$
{questionDetail.type}?id=${questionDetail.id}`)
"
class="text-sm"
>
编辑
</el-link>
</div>
<!-- 👇 新增:发布人信息 -->
<div
class=
"flex items-center gap-3 pb-3 border-b border-slate-100"
>
<el-avatar
:src=
"questionDetail.createUserAvatar"
:size=
"40"
class=
"flex-shrink-0 cursor-pointer"
@
click=
"jumpToUserHomePage(
{ userId: questionDetail.createUserId, isReal: 0 })"
>
{{
questionDetail
.
createUserName
}}
</el-avatar>
<div
class=
"flex flex-col"
>
<span
class=
"text-sm font-medium text-slate-900"
>
{{
questionDetail
.
createUserName
}}
</span>
<span
class=
"text-xs text-slate-500 flex items-center gap-1"
>
发布于
{{
dayjs
(
questionDetail
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
·
<span
class=
"text-xs text-slate-500 flex items-center gap-1"
>
{{
questionDetail
.
region
}}
</span>
</span>
</div>
</div>
<!-- 标题:主要信息,黑重粗 -->
<h1
class=
"text-2xl font-bold text-slate-900 mb-3 leading-snug tracking-tight"
>
{{
questionDetail
.
title
}}
</h1>
<!-- 描述:次要信息,深灰 -->
<div
class=
"text-gray-600 text-base leading-relaxed transition-all duration-300"
>
<div
:class=
"
{ 'line-clamp-3': !isExpand }" ref="questionContentRef">
{{
questionDetail
.
content
}}
</div>
<button
v-if=
"isOverThreeLine"
class=
"text-blue-500 text-sm font-medium hover:text-blue-600 mt-1 flex items-center gap-0.5 cursor-pointer"
@
click=
"isExpand = !isExpand"
>
{{
isExpand
?
'收起'
:
'显示全部'
}}
<el-icon
:class=
"
{ 'rotate-180': isExpand }" class="transition-transform"
>
<IEpCaretBottom
/></el-icon>
</button>
</div>
<div
v-if=
"questionDetail.imgUrl"
class=
"mt-3 flex gap-2 flex-wrap items-center justify-start"
<el-skeleton
:rows=
"5"
animated
:loading=
"loading"
:throttle=
"
{ leading: 0, trailing: 1500 }"
>
<el-image
v-for=
"(item, i) in questionDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
fit=
"cover"
class=
"rounded-lg w-24 h-24 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"questionDetail.imgUrl.split(',')"
:initial-index=
"i"
:preview-teleported=
"true"
/>
</div>
<!-- 底部操作栏 -->
<div
class=
"flex items-center justify-between mt-4"
>
<div
class=
"flex gap-3"
>
<button
class=
"px-5 py-1.5 border !bg-blue-500 !text-white rounded-[4px] text-sm font-medium transition-colors flex items-center gap-1 cursor-pointer"
@
click=
"openCommentDialog"
>
<el-icon><IEpEditPen
/></el-icon>
写回答
</button>
</div>
<template
#
template
>
<!-- 顶部标签行 -->
<div
class=
"flex flex-wrap gap-2 mb-4 justify-between"
>
<!-- 标签 -->
<div
class=
"flex gap-2"
>
<el-skeleton-item
v-for=
"i in 3"
:key=
"i"
variant=
"text"
style=
"width: 60px; height: 18px; border-radius: 9999px"
/>
</div>
<!-- 右侧数据 -->
<div
class=
"flex items-center gap-6 text-slate-500 text-sm select-none"
>
<span
@
click=
"handleLikeArticle"
class=
"hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
>
<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-skeleton-item
variant=
"text"
style=
"width: 40px; height: 18px"
/>
</div>
<!-- 发布人信息 -->
<div
class=
"flex items-center gap-3 pb-3 border-b border-slate-100"
>
<!-- 头像 -->
<el-skeleton-item
variant=
"circle"
style=
"width: 40px; height: 40px"
/>
<!-- 用户名 + 时间 -->
<div
class=
"flex flex-col gap-2"
>
<el-skeleton-item
variant=
"text"
style=
"width: 100px; height: 14px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 180px; height: 12px"
/>
</div>
</div>
<!-- 标题 -->
<div
class=
"mt-3 mb-3"
>
<el-skeleton-item
variant=
"text"
style=
"width: 90%; height: 28px"
/>
</div>
<!-- 内容描述 -->
<div
class=
"space-y-2"
>
<el-skeleton-item
variant=
"text"
style=
"width: 100%"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 95%"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 85%"
/>
</div>
<!-- 图片列表 -->
<!--
<div
class=
"mt-3 flex gap-2 flex-wrap"
>
<el-skeleton-item
v-for=
"i in 3"
:key=
"i"
variant=
"image"
style=
"width: 96px; height: 96px; border-radius: 8px"
/>
</div>
-->
<!-- 底部操作栏 -->
<div
class=
"flex items-center justify-between mt-4"
>
<!-- 写回答按钮 -->
<el-skeleton-item
variant=
"button"
style=
"width: 88px; height: 32px"
/>
<!-- 右侧点赞 / 收藏 -->
<div
class=
"flex items-center gap-6"
>
<el-skeleton-item
variant=
"text"
style=
"width: 48px; height: 16px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 48px; height: 16px"
/>
</div>
</div>
</
template
>
<
template
#
default
>
<!-- 顶部标签行 -->
<div
class=
"flex flex-wrap gap-2 mb-4 justify-between"
>
<div>
<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 mr-2"
>
#
{{
tag
}}
</span>
</div>
<!-- 后面加一个编辑 -->
<el-link
v-if=
"isAuthor"
type=
"primary"
:underline=
"false"
@
click=
"
router.push(`/publishLongArticle/$
{questionDetail.type}?id=${questionDetail.id}`)
"
class="text-sm"
>
编辑
</el-link>
</div>
<!-- 👇 新增:发布人信息 -->
<div
class=
"flex items-center gap-3 pb-3 border-b border-slate-100"
>
<el-avatar
:src=
"questionDetail.createUserAvatar"
:size=
"40"
class=
"flex-shrink-0 cursor-pointer"
@
click=
"jumpToUserHomePage(
{ userId: questionDetail.createUserId, isReal: 0 })"
>
{{
questionDetail
.
createUserName
}}
</el-avatar>
<div
class=
"flex flex-col"
>
<span
class=
"text-sm font-medium text-slate-900"
>
{{
questionDetail
.
createUserName
}}
</span>
<span
class=
"text-xs text-slate-500 flex items-center gap-1"
>
发布于
{{
dayjs
(
questionDetail
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
·
<span
class=
"text-xs text-slate-500 flex items-center gap-1"
>
{{
questionDetail
.
region
}}
</span>
</span>
</div>
</div>
<!-- 标题:主要信息,黑重粗 -->
<h1
class=
"text-2xl font-bold text-slate-900 mb-3 leading-snug tracking-tight"
>
{{
questionDetail
.
title
}}
</h1>
<!-- 描述:次要信息,深灰 -->
<div
class=
"text-gray-600 text-base leading-relaxed transition-all duration-300"
>
<div
:class=
"
{ 'line-clamp-3': !isExpand }" ref="questionContentRef">
{{
questionDetail
.
content
}}
</div>
<button
v-if=
"isOverThreeLine"
class=
"text-blue-500 text-sm font-medium hover:text-blue-600 mt-1 flex items-center gap-0.5 cursor-pointer"
@
click=
"isExpand = !isExpand"
>
{{
isExpand
?
'收起'
:
'显示全部'
}}
<el-icon
:class=
"
{ 'rotate-180': isExpand }" class="transition-transform"
>
<IEpCaretBottom
/></el-icon>
</button>
</div>
<div
v-if=
"questionDetail.imgUrl"
class=
"mt-3 flex gap-2 flex-wrap items-center justify-start"
>
<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>
<!-- 展示图片相关 -->
<el-image
v-for=
"(item, i) in questionDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
fit=
"cover"
class=
"rounded-lg w-24 h-24 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list=
"questionDetail.imgUrl.split(',')"
:initial-index=
"i"
:preview-teleported=
"true"
/>
</div>
<!-- 底部操作栏 -->
<div
class=
"flex items-center justify-between mt-4"
>
<div
class=
"flex gap-3"
>
<button
class=
"px-5 py-1.5 border !bg-blue-500 !text-white rounded-[4px] text-sm font-medium transition-colors flex items-center gap-1 cursor-pointer"
@
click=
"openCommentDialog"
>
<el-icon><IEpEditPen
/></el-icon>
写回答
</button>
</div>
<!-- 右侧数据 -->
<div
class=
"flex items-center gap-6 text-slate-500 text-sm select-none"
>
<span
@
click=
"handleLikeArticle"
class=
"hover:text-slate-600 cursor-pointer transition-colors flex items-center gap-1"
>
<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>
<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>
</
template
>
</el-skeleton>
</div>
<!-- 2. 列表控制栏 -->
...
...
@@ -331,6 +404,8 @@ const commentRefList = ref<InstanceType<typeof Comment>[]>([])
const
questionDetail
=
ref
<
ArticleItemDto
>
({}
as
ArticleItemDto
)
const
commentDialogRef
=
useTemplateRef
<
typeof
CommentDialog
>
(
'commentDialogRef'
)
const
loading
=
computed
(()
=>
!
questionDetail
.
value
.
title
)
const
isAuthor
=
computed
(()
=>
{
return
questionDetail
.
value
.
createUserId
===
userInfo
.
value
.
userId
})
...
...
src/views/videoDetail/components/rewardDialog.vue
View file @
96bad3c0
...
...
@@ -54,12 +54,20 @@
</div>
<!-- 余额提示 -->
<div
class=
"text-center text-12px text-gray-400 mt-4"
>
当前余额: {{ balance }}
</div>
<div
class=
"text-center text-12px text-gray-400 mt-4"
>
当前余额: {{ yabiData.currentValue }}
</div>
</div>
</el-dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
addOrCancelArticleReward
,
getYaBiData
}
from
'@/api'
import
{
addOrCancelArticleReward
}
from
'@/api'
import
{
useYaBiStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
const
yabiStore
=
useYaBiStore
()
const
{
yabiData
}
=
storeToRefs
(
yabiStore
)
const
rewardNum
=
defineModel
<
number
>
(
'rewardNum'
,
{
required
:
true
,
default
:
0
})
interface
RewardOption
{
amount
:
number
...
...
@@ -69,7 +77,6 @@ interface RewardOption {
const
dialogVisible
=
ref
(
false
)
const
selectedAmount
=
ref
(
2
)
const
balance
=
ref
(
0
)
let
articleId
=
-
1
...
...
@@ -82,12 +89,7 @@ const rewardOptions = ref<RewardOption[]>([
const
open
=
(
id
:
number
)
=>
{
articleId
=
id
dialogVisible
.
value
=
true
getYaBiDataFn
()
}
const
getYaBiDataFn
=
async
()
=>
{
const
{
data
}
=
await
getYaBiData
()
balance
.
value
=
data
.
currentValue
yabiStore
.
fetchYaBiData
()
}
// 选择金额
...
...
@@ -100,7 +102,7 @@ const selectAmount = (amount: number) => {
// 确认打赏
const
handleConfirm
=
async
()
=>
{
if
(
balance
.
v
alue
<
selectedAmount
.
value
)
{
if
(
yabiData
.
value
.
currentV
alue
<
selectedAmount
.
value
)
{
ElMessage
.
warning
(
'余额不足,请先充值'
)
return
}
...
...
@@ -112,6 +114,7 @@ const handleConfirm = async () => {
ElMessage
.
success
(
'打赏成功!'
)
dialogVisible
.
value
=
false
rewardNum
.
value
+=
selectedAmount
.
value
yabiStore
.
fetchYaBiData
()
}
defineExpose
({
...
...
src/views/videoDetail/index.vue
View file @
96bad3c0
...
...
@@ -4,202 +4,269 @@
<!-- 整体页面容器:浅灰背景 -->
<!-- 卡片1: 视频播放器区域 -->
<div
class=
"bg-white rounded-lg shadow-sm overflow-hidden"
>
<!-- 标题区 -->
<div
class=
"p-4 pb-3"
>
<h1
class=
"text-xl md:text-2xl font-medium text-gray-900 mb-2 leading-snug flex items-center justify-between"
>
{{
videoDetail
?.
title
}}
<ActionMore
:articleDetail=
"videoDetail"
/>
</h1>
<div
class=
"flex items-center text-14px text-gray-500 gap-2 flex-wrap"
>
<span
class=
"flex items-center"
>
{{
dayjs
(
videoDetail
?.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
·
<span
class=
"flex items-center"
>
{{
formatNumber
(
videoDetail
?.
playCount
)
}}
播放
</span>
·
<span
class=
"flex items-center"
>
{{
videoDetail
?.
region
}}
</span>
</div>
</div>
<el-skeleton
:rows=
"5"
animated
:loading=
"loading"
:throttle=
"
{ leading: 0, trailing: 500 }">
<template
#
template
>
<!-- 标题区 -->
<div
class=
"p-4 pb-3"
>
<div
class=
"flex items-center justify-between"
>
<el-skeleton-item
variant=
"text"
style=
"width: 60%; height: 24px"
/>
<el-skeleton-item
variant=
"button"
style=
"width: 32px; height: 32px"
/>
</div>
<!-- 视频 16/ 9 -->
<div
class=
"w-full bg-black aspect-video"
>
<video
ref=
"videoRef"
:src=
"videoDetail?.videoUrl"
class=
"w-full aspect-video object-contain"
controls
@
play=
"handlePlay"
@
pause=
"handlePause"
></video>
</div>
<div
class=
"bg-white rounded-xl shadow-sm p-4"
>
<div
class=
"flex items-center justify-between flex-wrap gap-4"
>
<!-- 左侧:UP主信息 -->
<div
class=
"flex items-center gap-3"
>
<img
@
click=
"jumpToUserHomePage(
{ userId: videoDetail?.createUserId, isReal: 0 })"
:src="videoDetail?.createUserAvatar"
class="w-12 h-12 rounded-full object-cover cursor-pointer hover:opacity-80 transition-opacity ring-2 ring-gray-100"
/>
<div
class=
"flex flex-col"
>
<h3
class=
"text-base font-semibold text-gray-900 cursor-pointer hover:text-blue-500 transition-colors"
>
{{
videoDetail
?.
createUserName
}}
</h3>
<!-- 时间 / 播放 / 地区 -->
<div
class=
"flex gap-2 mt-3"
>
<el-skeleton-item
variant=
"text"
style=
"width: 160px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 80px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 60px"
/>
</div>
</div>
<!-- 右侧:互动按钮 -->
<div
class=
"flex items-center"
>
<!-- 浏览量 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"handleLike(videoDetail)"
>
<el-icon
size=
"20"
>
<svg-icon
:name=
"videoDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-base"
:class=
"
{ 'text-blue-500': videoDetail?.hasPraised }">
{{
videoDetail
?.
praiseCount
||
0
}}
</span>
</el-button>
<!-- 收藏 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"handleCollect(videoDetail)"
>
<el-icon
size=
"20"
>
<svg-icon
:name=
"videoDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon>
</el-icon>
<span
class=
"text-base"
:class=
"
{ 'text-blue-500': videoDetail?.hasCollect }">
{{
videoDetail
?.
collectionCount
||
0
}}
</span>
</el-button>
<!-- 评论 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"() => commentRef?.scrollToCommentBox?.()"
>
<el-icon
size=
"20"
>
<svg-icon
name=
"comment"
></svg-icon>
</el-icon>
<span
class=
"text-base"
>
{{
videoDetail
?.
replyCount
||
0
}}
</span>
</el-button>
<!-- 打赏 -->
<el-button
text
class=
"reward-button flex items-center gap-2 px-4 py-2 rounded-lg bg-white/40 hover:bg-white/70 backdrop-blur-sm border border-blue-100/30 hover:border-blue-200/50 transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-blue-100/50"
@
click=
"handleReward"
>
<!-- 金币容器 - 带多重动画 -->
<div
class=
"coin-wrapper relative"
>
<!-- 改进版金币图标 - 立体金币设计 -->
<svg
class=
"coin-icon"
viewBox=
"0 0 24 24"
width=
"18"
height=
"18"
>
<defs>
<!-- 金色渐变 -->
<linearGradient
id=
"coinGradient"
x1=
"0%"
y1=
"0%"
x2=
"100%"
y2=
"100%"
>
<stop
offset=
"0%"
style=
"stop-color: #fef3c7"
/>
<stop
offset=
"30%"
style=
"stop-color: #fde68a"
/>
<stop
offset=
"70%"
style=
"stop-color: #fcd34d"
/>
<stop
offset=
"100%"
style=
"stop-color: #f59e0b"
/>
</linearGradient>
<!-- 高光效果 -->
<radialGradient
id=
"highlight"
cx=
"35%"
cy=
"35%"
>
<stop
offset=
"0%"
style=
"stop-color: #ffffff; stop-opacity: 0.9"
/>
<stop
offset=
"50%"
style=
"stop-color: #ffffff; stop-opacity: 0.3"
/>
<stop
offset=
"100%"
style=
"stop-color: #ffffff; stop-opacity: 0"
/>
</radialGradient>
</defs>
<!-- 外圈装饰 -->
<circle
cx=
"12"
cy=
"12"
r=
"10.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.8"
opacity=
"0.4"
/>
<!-- 金币主体 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#coinGradient)"
stroke=
"#d97706"
stroke-width=
"1.5"
/>
<!-- 内圈装饰线 -->
<circle
cx=
"12"
cy=
"12"
r=
"8"
fill=
"none"
stroke=
"#fbbf24"
stroke-width=
"0.5"
opacity=
"0.6"
/>
<circle
cx=
"12"
cy=
"12"
r=
"6.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.3"
opacity=
"0.5"
/>
<!-- 星形图案(代替¥符号) -->
<path
d=
"M12 5 L13 9 L17 9 L14 11.5 L15 15 L12 13 L9 15 L10 11.5 L7 9 L11 9 Z"
fill=
"#d97706"
opacity=
"0.8"
/>
<!-- 高光 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#highlight)"
opacity=
"0.5"
/>
</svg>
<!-- 悬停时的光晕效果 -->
<div
class=
"coin-glow absolute inset-0 rounded-full"
></div>
<!-- 闪光粒子效果 -->
<div
class=
"sparkle sparkle-1"
></div>
<div
class=
"sparkle sparkle-2"
></div>
<div
class=
"sparkle sparkle-3"
></div>
</div>
<!-- 视频区域 16:9 -->
<div
class=
"w-full aspect-video px-4"
>
<el-skeleton-item
variant=
"image"
style=
"width: 100%; height: 100%"
/>
</div>
<span
class=
"ml-2 reward-number font-medium text-gray-700"
>
{{
videoDetail
?.
rewardNum
}}
</span>
<span
class=
"ml-1 reward-text text-sm text-gray-600"
>
打赏
</span>
</el-button>
<!-- UP主 + 操作区 -->
<div
class=
"bg-white rounded-xl shadow-sm p-4"
>
<div
class=
"flex items-center justify-between flex-wrap gap-4"
>
<!-- 左侧 UP主 -->
<div
class=
"flex items-center gap-3"
>
<el-skeleton-item
variant=
"circle"
style=
"width: 48px; height: 48px"
/>
<div>
<el-skeleton-item
variant=
"text"
style=
"width: 120px; height: 16px"
/>
</div>
</div>
<!-- 更多 -->
<button
class=
"p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-all"
<!-- 右侧按钮区 -->
<div
class=
"flex items-center gap-4"
>
<!-- 点赞 -->
<div
class=
"flex items-center gap-2"
>
<el-skeleton-item
variant=
"circle"
style=
"width: 24px; height: 24px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 24px"
/>
</div>
<!-- 收藏 -->
<div
class=
"flex items-center gap-2"
>
<el-skeleton-item
variant=
"circle"
style=
"width: 24px; height: 24px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 24px"
/>
</div>
<!-- 评论 -->
<div
class=
"flex items-center gap-2"
>
<el-skeleton-item
variant=
"circle"
style=
"width: 24px; height: 24px"
/>
<el-skeleton-item
variant=
"text"
style=
"width: 24px"
/>
</div>
<!-- 打赏按钮 -->
<el-skeleton-item
variant=
"button"
style=
"width: 100px; height: 36px"
/>
<!-- 更多 -->
<el-skeleton-item
variant=
"circle"
style=
"width: 32px; height: 32px"
/>
</div>
</div>
</div>
</
template
>
<
template
#
default
>
<!-- 标题区 -->
<div
class=
"p-4 pb-3"
>
<h1
class=
"text-xl md:text-2xl font-medium text-gray-900 mb-2 leading-snug flex items-center justify-between"
>
<i
class=
"i-carbon-overflow-menu-horizontal text-2xl"
></i>
</button>
{{
videoDetail
?.
title
}}
<ActionMore
:articleDetail=
"videoDetail"
/>
</h1>
<div
class=
"flex items-center text-14px text-gray-500 gap-2 flex-wrap"
>
<span
class=
"flex items-center"
>
{{
dayjs
(
videoDetail
?.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
·
<span
class=
"flex items-center"
>
{{
formatNumber
(
videoDetail
?.
playCount
)
}}
播放
</span>
·
<span
class=
"flex items-center"
>
{{
videoDetail
?.
region
}}
</span>
</div>
</div>
</div>
</div>
<!-- 视频 16/ 9 -->
<div
class=
"aspect-video mx-4"
>
<video
ref=
"videoRef"
:src=
"videoDetail?.videoUrl"
class=
"aspect-video object-contain"
controls
@
play=
"handlePlay"
@
pause=
"handlePause"
></video>
</div>
<div
class=
"bg-white rounded-xl shadow-sm p-4"
>
<div
class=
"flex items-center justify-between flex-wrap gap-4"
>
<!-- 左侧:UP主信息 -->
<div
class=
"flex items-center gap-3"
>
<img
@
click=
"jumpToUserHomePage(
{ userId: videoDetail?.createUserId, isReal: 0 })"
:src="videoDetail?.createUserAvatar"
class="w-12 h-12 rounded-full object-cover cursor-pointer hover:opacity-80 transition-opacity ring-2 ring-gray-100"
/>
<div
class=
"flex flex-col"
>
<h3
class=
"text-base font-semibold text-gray-900 cursor-pointer hover:text-blue-500 transition-colors"
>
{{
videoDetail
?.
createUserName
}}
</h3>
</div>
</div>
<!-- 右侧:互动按钮 -->
<div
class=
"flex items-center"
>
<!-- 浏览量 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"handleLike(videoDetail)"
>
<el-icon
size=
"20"
>
<svg-icon
:name=
"videoDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-base"
:class=
"
{ 'text-blue-500': videoDetail?.hasPraised }">
{{
videoDetail
?.
praiseCount
||
0
}}
</span>
</el-button>
<!-- 收藏 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"handleCollect(videoDetail)"
>
<el-icon
size=
"20"
>
<svg-icon
:name=
"videoDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon>
</el-icon>
<span
class=
"text-base"
:class=
"
{ 'text-blue-500': videoDetail?.hasCollect }">
{{
videoDetail
?.
collectionCount
||
0
}}
</span>
</el-button>
<!-- 评论 -->
<el-button
text
class=
"flex items-center gap-2 transition-colors hover:text-blue-500"
@
click=
"() => commentRef?.scrollToCommentBox?.()"
>
<el-icon
size=
"20"
>
<svg-icon
name=
"comment"
></svg-icon>
</el-icon>
<span
class=
"text-base"
>
{{
videoDetail
?.
replyCount
||
0
}}
</span>
</el-button>
<!-- 打赏 -->
<el-button
text
class=
"reward-button flex items-center gap-2 px-4 py-2 rounded-lg bg-white/40 hover:bg-white/70 backdrop-blur-sm border border-blue-100/30 hover:border-blue-200/50 transition-all duration-300 hover:scale-105 hover:shadow-lg hover:shadow-blue-100/50"
@
click=
"handleReward"
>
<!-- 金币容器 - 带多重动画 -->
<div
class=
"coin-wrapper relative"
>
<!-- 改进版金币图标 - 立体金币设计 -->
<svg
class=
"coin-icon"
viewBox=
"0 0 24 24"
width=
"18"
height=
"18"
>
<defs>
<!-- 金色渐变 -->
<linearGradient
id=
"coinGradient"
x1=
"0%"
y1=
"0%"
x2=
"100%"
y2=
"100%"
>
<stop
offset=
"0%"
style=
"stop-color: #fef3c7"
/>
<stop
offset=
"30%"
style=
"stop-color: #fde68a"
/>
<stop
offset=
"70%"
style=
"stop-color: #fcd34d"
/>
<stop
offset=
"100%"
style=
"stop-color: #f59e0b"
/>
</linearGradient>
<!-- 高光效果 -->
<radialGradient
id=
"highlight"
cx=
"35%"
cy=
"35%"
>
<stop
offset=
"0%"
style=
"stop-color: #ffffff; stop-opacity: 0.9"
/>
<stop
offset=
"50%"
style=
"stop-color: #ffffff; stop-opacity: 0.3"
/>
<stop
offset=
"100%"
style=
"stop-color: #ffffff; stop-opacity: 0"
/>
</radialGradient>
</defs>
<!-- 外圈装饰 -->
<circle
cx=
"12"
cy=
"12"
r=
"10.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.8"
opacity=
"0.4"
/>
<!-- 金币主体 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#coinGradient)"
stroke=
"#d97706"
stroke-width=
"1.5"
/>
<!-- 内圈装饰线 -->
<circle
cx=
"12"
cy=
"12"
r=
"8"
fill=
"none"
stroke=
"#fbbf24"
stroke-width=
"0.5"
opacity=
"0.6"
/>
<circle
cx=
"12"
cy=
"12"
r=
"6.5"
fill=
"none"
stroke=
"#f59e0b"
stroke-width=
"0.3"
opacity=
"0.5"
/>
<!-- 星形图案(代替¥符号) -->
<path
d=
"M12 5 L13 9 L17 9 L14 11.5 L15 15 L12 13 L9 15 L10 11.5 L7 9 L11 9 Z"
fill=
"#d97706"
opacity=
"0.8"
/>
<!-- 高光 -->
<circle
cx=
"12"
cy=
"12"
r=
"9.5"
fill=
"url(#highlight)"
opacity=
"0.5"
/>
</svg>
<!-- 悬停时的光晕效果 -->
<div
class=
"coin-glow absolute inset-0 rounded-full"
></div>
<!-- 闪光粒子效果 -->
<div
class=
"sparkle sparkle-1"
></div>
<div
class=
"sparkle sparkle-2"
></div>
<div
class=
"sparkle sparkle-3"
></div>
</div>
<span
class=
"ml-2 reward-number font-medium text-gray-700"
>
{{
videoDetail
?.
rewardNum
}}
</span>
<span
class=
"ml-1 reward-text text-sm text-gray-600"
>
打赏
</span>
</el-button>
<!-- 更多 -->
<button
class=
"p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 rounded-lg transition-all"
>
<i
class=
"i-carbon-overflow-menu-horizontal text-2xl"
></i>
</button>
</div>
</div>
</div>
</
template
>
</el-skeleton>
</div>
<!-- 卡片3: 简介与标签 -->
...
...
@@ -258,6 +325,7 @@ const videoId = Number(route.params.id)
// 视频详情
const
videoDetail
=
ref
({}
as
ArticleItemDto
)
const
loading
=
computed
(()
=>
!
videoDetail
.
value
.
title
)
const
commentRef
=
useTemplateRef
<
InstanceType
<
typeof
Comment
>
|
null
>
(
'commentRef'
)
const
rewardDialogRef
=
useTemplateRef
<
InstanceType
<
typeof
RewardDialog
>
|
null
>
(
'rewardDialogRef'
)
...
...
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