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
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
260 additions
and
23 deletions
+260
-23
index.vue
src/components/common/ArticleContent/index.vue
+99
-8
index.vue
src/views/questionDetail/index.vue
+77
-2
rewardDialog.vue
src/views/videoDetail/components/rewardDialog.vue
+13
-10
index.vue
src/views/videoDetail/index.vue
+71
-3
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"
>
<el-skeleton
:rows=
"5"
animated
:loading=
"loading"
:throttle=
"
{ leading: 0, trailing: 1500 }">
<template
#
template
>
<!-- 发布者信息 -->
<div
class=
"p-6 border-b border-gray-100 pb-0"
>
<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
...
...
@@ -14,7 +96,9 @@
jumpToUserHomePage(
{
userId: articleDetail?.createUserId,
isReal:
articleDetail.type === 'practice' || articleDetail.type === 'interview' ? 1 : 0,
articleDetail.type === 'practice' || articleDetail.type === 'interview'
? 1
: 0,
})
"
/>
...
...
@@ -44,7 +128,9 @@
v-if=
"isAuthor"
type=
"primary"
:underline=
"false"
@
click=
"router.push(`/publishLongArticle/$
{articleDetail.type}?id=${articleDetail.id}`)"
@
click=
"
router.push(`/publishLongArticle/$
{articleDetail.type}?id=${articleDetail.id}`)
"
class="text-sm"
>
编辑
...
...
@@ -69,7 +155,7 @@
</div>
</div>
<div
class=
"p
-6"
>
<div
class=
"mt
-6"
>
<h1
class=
"text-2xl font-bold text-gray-900 mb-4 leading-tight"
>
{{
articleDetail
?.
title
}}
</h1>
...
...
@@ -78,7 +164,7 @@
<video
:src=
"articleDetail.articleVideoUrl"
controls
class=
"w-80%
aspect-video bg-black"
class=
"w-100%!
aspect-video bg-black"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
...
...
@@ -107,7 +193,7 @@
<video
:src=
"articleDetail.videoUrl"
controls
class=
"w-80%
aspect-video bg-black"
class=
"w-100%!
aspect-video bg-black"
:poster=
"`$
{articleDetail.videoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
...
...
@@ -121,7 +207,7 @@
<video
:src=
"articleDetail.articleVideoUrl"
controls
class=
"w-80%
aspect-video bg-black"
class=
"w-100%!
aspect-video bg-black"
:poster=
"`$
{articleDetail.articleVideoUrl}?x-oss-process=video/snapshot,t_1000,f_jpg`"
/>
</div>
...
...
@@ -136,6 +222,8 @@
</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,6 +5,76 @@
<div
class=
"bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100"
>
<el-skeleton
:rows=
"5"
animated
:loading=
"loading"
:throttle=
"
{ leading: 0, trailing: 1500 }"
>
<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>
<!-- 编辑按钮 -->
<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>
...
...
@@ -107,7 +177,9 @@
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>
<svg-icon
:name=
"questionDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
:class=
"
{ 'text-blue-500': questionDetail?.hasPraised }">
{{
questionDetail
?.
praiseCount
||
0
...
...
@@ -128,7 +200,8 @@
</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,6 +4,69 @@
<!-- 整体页面容器:浅灰背景 -->
<!-- 卡片1: 视频播放器区域 -->
<div
class=
"bg-white rounded-lg shadow-sm overflow-hidden"
>
<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>
<!-- 时间 / 播放 / 地区 -->
<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>
<!-- 视频区域 16:9 -->
<div
class=
"w-full aspect-video px-4"
>
<el-skeleton-item
variant=
"image"
style=
"width: 100%; height: 100%"
/>
</div>
<!-- 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>
<!-- 右侧按钮区 -->
<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
...
...
@@ -17,7 +80,9 @@
{{
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"
>
{{
formatNumber
(
videoDetail
?.
playCount
)
}}
播放
</span>
·
<span
class=
"flex items-center"
>
{{
videoDetail
?.
region
}}
...
...
@@ -26,11 +91,11 @@
</div>
<!-- 视频 16/ 9 -->
<div
class=
"w-full bg-black aspect-video
"
>
<div
class=
"aspect-video mx-4
"
>
<video
ref=
"videoRef"
:src=
"videoDetail?.videoUrl"
class=
"w-full
aspect-video object-contain"
class=
"
aspect-video object-contain"
controls
@
play=
"handlePlay"
@
pause=
"handlePause"
...
...
@@ -200,6 +265,8 @@
</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