Commit 96bad3c0 by lijiabin

【需求 20331】 feat: 文章详情页(帖子等相关、视频、问吧)加入骨架屏

parent e6d3866d
<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
......
......@@ -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
})
......
......@@ -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.value < selectedAmount.value) {
if (yabiData.value.currentValue < selectedAmount.value) {
ElMessage.warning('余额不足,请先充值')
return
}
......@@ -112,6 +114,7 @@ const handleConfirm = async () => {
ElMessage.success('打赏成功!')
dialogVisible.value = false
rewardNum.value += selectedAmount.value
yabiStore.fetchYaBiData()
}
defineExpose({
......
......@@ -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')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment