Commit 861e50b6 by lijiabin

【需求 25171】feat: 发布/编辑页新增预览功能,问吧问题卡片抽成共用组件

parent 82d62658
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
alt="" alt=""
class="w-12 h-12 rounded-full object-cover cursor-pointer" class="w-12 h-12 rounded-full object-cover cursor-pointer"
@click=" @click="
!isPreview &&
jumpToUserHomePage({ jumpToUserHomePage({
userId: articleDetail.createUserId, userId: articleDetail.createUserId,
type: articleDetail.type, type: articleDetail.type,
...@@ -163,7 +164,7 @@ ...@@ -163,7 +164,7 @@
{{ articleType }} {{ articleType }}
</span> </span>
<ActionMore :articleDetail="articleDetail" /> <ActionMore v-if="!isPreview" :articleDetail="articleDetail" />
</div> </div>
</div> </div>
</div> </div>
...@@ -305,9 +306,10 @@ const userStore = useUserStore() ...@@ -305,9 +306,10 @@ const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
const router = useRouter() const router = useRouter()
const { jumpToUserHomePage } = useNavigation() const { jumpToUserHomePage } = useNavigation()
const { articleDetail, isAudit } = defineProps<{ const { articleDetail, isAudit, isPreview = false } = defineProps<{
articleDetail: ArticleItemDto articleDetail: ArticleItemDto
isAudit: boolean // 是否是审核页面 isAudit: boolean // 是否是审核页面
isPreview?: boolean // 是否是发布页的预览:数据是前端拼的,头像不跳转、不展示举报
}>() }>()
const sendMessageDialogRef = ref<InstanceType<typeof SendMessageDialog> | null>(null) const sendMessageDialogRef = ref<InstanceType<typeof SendMessageDialog> | null>(null)
......
<template>
<div
class="bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100 relative"
>
<slot />
<el-skeleton :rows="5" animated :loading="loading" :throttle="{ leading: 0, trailing: 300 }">
<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>
<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="
!isPreview &&
jumpToUserHomePage({
userId: questionDetail.createUserId,
type: ArticleTypeEnum.QUESTION,
})
"
>
{{ questionDetail.createUserName }}
</el-avatar>
<div class="flex flex-col flex-1 min-w-0">
<span class="text-sm text-slate-900">
{{ questionDetail.createUserName }}
</span>
<span class="text-sm text-slate-500 flex items-center gap-1">
{{ dayjs(questionDetail.createTime * 1000).format('YYYY-MM-DD HH:mm') }} ·
<span
v-if="questionDetail?.region"
class="text-sm text-slate-500 flex items-center gap-1"
>
{{ questionDetail.region }}
·
</span>
{{ questionDetail.viewCount }}阅读
</span>
</div>
<button
v-if="!isAuthor && questionDetail.createUserId"
type="button"
class="shrink-0 inline-flex items-center px-3 py-1.5 text-xs font-semibold text-blue-600 bg-blue-50 rounded-lg border border-blue-100 hover:bg-blue-100 hover:border-blue-200/80 active:scale-[0.98] transition-all cursor-pointer"
@click="emit('message')"
>
私信
</button>
</div>
<!-- 标题:主要信息,黑重粗 -->
<h1 class="text-2xl font-bold text-slate-900 mb-3 leading-snug tracking-tight">
{{ questionDetail.title }}
</h1>
<!-- 描述:次要信息,深灰 -->
<div class="transition-all duration-300">
<div
:class="{ 'line-clamp-3': !isExpand }"
ref="questionContentRef"
class="html-content"
v-html="parseEmoji(questionDetail.content)"
v-image-preview
v-parse-comment
></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.imgUrlList?.length"
class="mt-3 flex gap-2 flex-wrap items-center justify-start"
>
<el-image
v-for="(item, i) in questionDetail.imgUrlList"
:key="item"
:src="item"
fit="cover"
class="rounded-lg w-24 h-24 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list="questionDetail.imgUrlList"
:initial-index="i"
:preview-teleported="true"
/>
</div>
<!-- 底部操作栏:预览时没有这些操作 -->
<div v-if="!isPreview" 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="emit('write')"
>
<el-icon><IEpEditPen /></el-icon>
写回答
</button>
</div>
<!-- 右侧数据 -->
<div class="flex items-center gap-1 text-slate-500 text-base select-none">
<el-button
text
class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasPraised }"
@click="emit('like')"
>
<el-icon size="17">
<svg-icon :name="questionDetail?.hasPraised ? 'praise_fill' : 'praise'"></svg-icon>
</el-icon>
<span class="text-15px">{{ questionDetail?.praiseCount || 0 }}</span>
</el-button>
<el-button
text
class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasCollect }"
@click="emit('collect')"
>
<el-icon size="17">
<svg-icon
:name="questionDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon>
</el-icon>
<span class="text-15px">{{ questionDetail?.collectionCount || 0 }}</span>
</el-button>
<el-button
text
class="!ml-0 flex items-center gap-2 transition-colors hover:text-blue-500"
@click="emit('share')"
>
<el-icon size="17">
<svg-icon name="share" size="15"></svg-icon>
</el-icon>
<span class="text-15px">分享</span>
</el-button>
</div>
</div>
</template>
</el-skeleton>
<!-- 正文里的图片预览 -->
<el-image-viewer
v-if="showPreview"
:url-list="srcList"
show-progress
:teleported="true"
:initial-index="currentPreviewIndex"
@close="showPreview = false"
/>
</div>
</template>
<script setup lang="ts">
/**
* 问吧详情页顶部的问题卡片(标签、发布人、标题、正文、配图、点赞收藏分享)。
* questionDetail 页和发布页的预览共用这一个组件,保证预览跟真实页面长得一样;
* 预览时传 isPreview,头像不跳转、不展示底部操作栏。
* 点击这些操作只往外抛事件,具体怎么处理由页面决定。
*/
import dayjs from 'dayjs'
import type { ArticleItemDto } from '@/api'
import { ArticleTypeEnum } from '@/constants'
import { useImagePreview, useNavigation } from '@/hooks'
import { parseEmoji } from '@/utils/emoji'
const {
questionDetail,
isAuthor = false,
isPreview = false,
} = defineProps<{
questionDetail: ArticleItemDto
isAuthor?: boolean
isPreview?: boolean
}>()
const emit = defineEmits<{
write: []
like: []
collect: []
share: []
message: []
}>()
const router = useRouter()
const { jumpToUserHomePage } = useNavigation()
const { showPreview, currentPreviewIndex, srcList, vImagePreview } = useImagePreview()
const loading = computed(() => !questionDetail.title)
const questionContentRef = useTemplateRef<HTMLElement>('questionContentRef')
const isExpand = ref(false)
// 检测当前是否超过三行 要用到具体的dom
const isOverThreeLine = computed(() => {
if (!questionContentRef.value || !questionDetail.content) return false
const lineHeight = parseFloat(getComputedStyle(questionContentRef.value).lineHeight)
const height = questionContentRef.value!.scrollHeight
const maxHeight = lineHeight * 3
return height > maxHeight
})
</script>
<template> <template>
<div class="min-h-screen bg-gradient-to-br pb-10 relative"> <div class="min-h-screen bg-gradient-to-br pb-10 relative">
<BackButton /> <BackButton />
<!-- 主编辑区域 --> <!-- 主编辑区域:预览时用 v-show 藏起来而不是销毁,返回编辑时编辑器里的内容和光标状态都还在 -->
<div class="bg-white rounded-lg shadow-lg border border-gray-100/50 overflow-hidden"> <div
v-show="!previewing"
class="bg-white rounded-lg shadow-lg border border-gray-100/50 overflow-hidden"
>
<div class="px-10 py-5"> <div class="px-10 py-5">
<!-- 标题输入 --> <!-- 标题输入 -->
<el-form-item class="mb-8"> <el-form-item class="mb-8">
...@@ -41,11 +44,21 @@ ...@@ -41,11 +44,21 @@
</div> </div>
<!-- 底部固定按钮栏 --> <!-- 底部固定按钮栏 -->
<div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg"> <div
v-show="!previewing"
class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg"
>
<div class="max-w-[1200px] mx-auto px-4 py-3"> <div class="max-w-[1200px] mx-auto px-4 py-3">
<div class="flex justify-center gap-4"> <div class="flex justify-center gap-4">
<el-button @click="handleClosed" class="rounded-lg min-w[120px]"> 取消 </el-button> <el-button @click="handleClosed" class="rounded-lg min-w[120px]"> 取消 </el-button>
<el-button <el-button
:disabled="!sharedForm.content"
@click="previewing = true"
class="rounded-lg min-w[120px]"
>
预览
</el-button>
<el-button
type="primary" type="primary"
:disabled="!canPublish" :disabled="!canPublish"
@click="openDrawer" @click="openDrawer"
...@@ -89,6 +102,7 @@ ...@@ -89,6 +102,7 @@
<template #footer> <template #footer>
<div class="flex gap-3 justify-end"> <div class="flex gap-3 justify-end">
<el-button @click="handleDrawerClose" class="rounded-lg">取消</el-button> <el-button @click="handleDrawerClose" class="rounded-lg">取消</el-button>
<el-button @click="handlePreviewFromDrawer" class="rounded-lg">预览</el-button>
<el-button <el-button
:loading="loading" :loading="loading"
@click="handleSubmit(ReleaseStatusTypeEnum.DRAFT)" @click="handleSubmit(ReleaseStatusTypeEnum.DRAFT)"
...@@ -107,6 +121,19 @@ ...@@ -107,6 +121,19 @@
</div> </div>
</template> </template>
</el-drawer> </el-drawer>
<!-- 预览:直接用详情页的 ArticleContent 渲染,所见即所得,不用先保存也不需要接口 -->
<div v-if="previewing" class="space-y-4">
<!-- 问吧详情页的样子跟其它图文类完全不同,用它自己的问题卡片;其它类型用 ArticleContent -->
<QuestionCard v-if="type === ArticleTypeEnum.QUESTION" :questionDetail="previewDetail" isPreview />
<ArticleContent v-else :articleDetail="previewDetail" :isAudit="true" :isPreview="true" />
<div class="flex justify-end gap-3">
<el-button @click="previewing = false">返回编辑</el-button>
<el-button type="primary" :disabled="!canPublish" @click="openDrawer">
{{ isEdit ? '编辑' : '发布' }}
</el-button>
</div>
</div>
</div> </div>
</template> </template>
...@@ -132,6 +159,10 @@ import type { ArticleItemDto } from '@/api' ...@@ -132,6 +159,10 @@ import type { ArticleItemDto } from '@/api'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { push } from 'notivue' import { push } from 'notivue'
import BackButton from '@/components/common/BackButton/index.vue' import BackButton from '@/components/common/BackButton/index.vue'
import ArticleContent from '@/components/common/ArticleContent/index.vue'
import QuestionCard from '@/components/common/QuestionCard/index.vue'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
import type { ArticleTypeFormExpose } from './types' import type { ArticleTypeFormExpose } from './types'
import PostForm from './components/PostForm.vue' import PostForm from './components/PostForm.vue'
import ColumnForm from './components/ColumnForm.vue' import ColumnForm from './components/ColumnForm.vue'
...@@ -183,6 +214,39 @@ const handleDrawerClose = () => { ...@@ -183,6 +214,39 @@ const handleDrawerClose = () => {
drawerVisible.value = false drawerVisible.value = false
} }
// 预览:用当前编辑器里的标题/内容和自己的头像昵称拼一份假的文章详情,交给 ArticleContent 渲染。
// 标签、浏览量这些要后端数据的部分预览里没有,也不需要
const previewing = ref(false)
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
// 抽屉里也能预览:关掉抽屉进预览,抽屉里已填的内容不会丢(子表单没销毁),预览里点发布会再打开
const handlePreviewFromDrawer = () => {
drawerVisible.value = false
previewing.value = true
}
const previewDetail = computed(() => {
// 实践/问吧的配图在抽屉子表单里,拿得到就带上
const imgUrl = subFormRef.value?.getSubmitData().imgUrl as string | undefined
const isReal = [
ArticleTypeEnum.PRACTICE,
ArticleTypeEnum.INTERVIEW,
ArticleTypeEnum.QUESTION,
].includes(type)
return {
type,
title: sharedForm.value.title,
content: sharedForm.value.content,
imgUrl,
imgUrlList: imgUrl?.split(',').filter(Boolean) ?? [],
createTime: Math.floor(Date.now() / 1000),
viewCount: 0,
createUserName: isReal ? userInfo.value.name : userInfo.value.hiddenName,
createUserAvatar: isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar,
tagNameList: [],
tagIdList: [],
} as unknown as ArticleItemDto
})
// 取消按钮 // 取消按钮
const handleClosed = () => { const handleClosed = () => {
resetSharedForm() resetSharedForm()
...@@ -228,6 +292,7 @@ const editorKey = ref(0) ...@@ -228,6 +292,7 @@ const editorKey = ref(0)
const isEdit = computed(() => !!route.query.id) const isEdit = computed(() => !!route.query.id)
onActivated(async () => { onActivated(async () => {
previewing.value = false
editorKey.value++ editorKey.value++
resetSharedForm() resetSharedForm()
subFormRef.value?.reset() subFormRef.value?.reset()
......
...@@ -2,246 +2,17 @@ ...@@ -2,246 +2,17 @@
<div class="min-h-screen pb-10 text-slate-800 flex justify-center sm:px-20"> <div class="min-h-screen pb-10 text-slate-800 flex justify-center sm:px-20">
<div class="w-full space-y-4"> <div class="w-full space-y-4">
<!-- 1. 问题卡片 --> <!-- 1. 问题卡片 -->
<div <QuestionCard
class="bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100 relative" :questionDetail="questionDetail"
:isAuthor="isAuthor"
@write="openCommentDialog"
@like="handleLikeArticle"
@collect="handleCollectArticle"
@share="handleShareQuestion"
@message="openSendMessage"
> >
<BackButton /> <BackButton />
<el-skeleton </QuestionCard>
:rows="5"
animated
:loading="loading"
:throttle="{ leading: 0, trailing: 300 }"
>
<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>
<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,
type: ArticleTypeEnum.QUESTION,
})
"
>
{{ questionDetail.createUserName }}
</el-avatar>
<div class="flex flex-col flex-1 min-w-0">
<span class="text-sm text-slate-900">
{{ questionDetail.createUserName }}
</span>
<span class="text-sm text-slate-500 flex items-center gap-1">
{{ dayjs(questionDetail.createTime * 1000).format('YYYY-MM-DD HH:mm') }} ·
<span
v-if="questionDetail?.region"
class="text-sm text-slate-500 flex items-center gap-1"
>
{{ questionDetail.region }}
·
</span>
{{ questionDetail.viewCount }}阅读
</span>
</div>
<button
v-if="!isAuthor && questionDetail.createUserId"
type="button"
class="shrink-0 inline-flex items-center px-3 py-1.5 text-xs font-semibold text-blue-600 bg-blue-50 rounded-lg border border-blue-100 hover:bg-blue-100 hover:border-blue-200/80 active:scale-[0.98] transition-all cursor-pointer"
@click="
sendMessageDialogRef?.open({
receiverId: String(questionDetail.createUserId),
isReal: BooleanFlag.YES,
})
"
>
私信
</button>
</div>
<!-- 标题:主要信息,黑重粗 -->
<h1 class="text-2xl font-bold text-slate-900 mb-3 leading-snug tracking-tight">
{{ questionDetail.title }}
</h1>
<!-- 描述:次要信息,深灰 -->
<div class="transition-all duration-300">
<div
:class="{ 'line-clamp-3': !isExpand }"
ref="questionContentRef"
class="html-content"
v-html="parseEmoji(questionDetail.content)"
v-image-preview
v-parse-comment
></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.imgUrlList?.length"
class="mt-3 flex gap-2 flex-wrap items-center justify-start"
>
<el-image
v-for="(item, i) in questionDetail.imgUrlList"
:key="item"
:src="item"
fit="cover"
class="rounded-lg w-24 h-24 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list="questionDetail.imgUrlList"
: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-1 text-slate-500 text-base select-none">
<el-button
text
class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasPraised }"
@click="handleLikeArticle"
>
<el-icon size="17">
<svg-icon
:name="questionDetail?.hasPraised ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span class="text-15px">{{ questionDetail?.praiseCount || 0 }}</span>
</el-button>
<el-button
text
class="!ml-0 flex items-center gap-1 transition-colors hover:text-blue-500"
:class="{ '!text-blue-500': questionDetail?.hasCollect }"
@click="handleCollectArticle"
>
<el-icon size="17">
<svg-icon
:name="questionDetail?.hasCollect ? 'collection_fill' : 'collection'"
></svg-icon>
</el-icon>
<span class="text-15px">{{ questionDetail?.collectionCount || 0 }}</span>
</el-button>
<el-button
text
class="!ml-0 flex items-center gap-2 transition-colors hover:text-blue-500"
@click="handleShareQuestion"
>
<el-icon size="17">
<svg-icon name="share" size="15"></svg-icon>
</el-icon>
<span class="text-15px">分享</span>
</el-button>
</div>
</div>
</template>
</el-skeleton>
</div>
<!-- 2. 列表控制栏 --> <!-- 2. 列表控制栏 -->
<div <div
...@@ -452,14 +223,6 @@ ...@@ -452,14 +223,6 @@
</div> </div>
<CommentDialog ref="commentDialogRef" @commentSuccess="refresh" /> <CommentDialog ref="commentDialogRef" @commentSuccess="refresh" />
<SendMessageDialog ref="sendMessageDialogRef" /> <SendMessageDialog ref="sendMessageDialogRef" />
<el-image-viewer
v-if="showPreview"
:url-list="srcList"
show-progress
:teleported="true"
:initial-index="currentPreviewIndex"
@close="showPreview = false"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
...@@ -480,10 +243,11 @@ import Comment from '@/components/common/Comment/index.vue' ...@@ -480,10 +243,11 @@ import Comment from '@/components/common/Comment/index.vue'
import CommentDialog from '@/components/common/CommentDialog/index.vue' import CommentDialog from '@/components/common/CommentDialog/index.vue'
import SendMessageDialog from '@/components/common/SendMessageDialog/index.vue' import SendMessageDialog from '@/components/common/SendMessageDialog/index.vue'
import BackButton from '@/components/common/BackButton/index.vue' import BackButton from '@/components/common/BackButton/index.vue'
import QuestionCard from '@/components/common/QuestionCard/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 { useImagePreview, useMessageBox, useNavigation, useScrollTop } from '@/hooks' import { useMessageBox, useNavigation, useScrollTop } from '@/hooks'
import { parseEmoji } from '@/utils/emoji' import { parseEmoji } from '@/utils/emoji'
import { isWxShareCancel, wxShare } from '@/utils/wxUtil' import { isWxShareCancel, wxShare } from '@/utils/wxUtil'
import { ArticleTypeEnum, BooleanFlag } from '@/constants' import { ArticleTypeEnum, BooleanFlag } from '@/constants'
...@@ -495,7 +259,6 @@ const { userInfo } = storeToRefs(userStore) ...@@ -495,7 +259,6 @@ const { userInfo } = storeToRefs(userStore)
const { jumpToUserHomePage } = useNavigation() const { jumpToUserHomePage } = useNavigation()
const { confirm } = useMessageBox() const { confirm } = useMessageBox()
const route = useRoute() const route = useRoute()
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>[]>([])
...@@ -506,31 +269,17 @@ const commentDialogRef = useTemplateRef<typeof CommentDialog>('commentDialogRef' ...@@ -506,31 +269,17 @@ const commentDialogRef = useTemplateRef<typeof CommentDialog>('commentDialogRef'
const sendMessageDialogRef = useTemplateRef<InstanceType<typeof SendMessageDialog> | null>( const sendMessageDialogRef = useTemplateRef<InstanceType<typeof SendMessageDialog> | null>(
'sendMessageDialogRef', 'sendMessageDialogRef',
) )
const { showPreview, currentPreviewIndex, srcList, vImagePreview } = useImagePreview()
// 回滚到子评论框 // 回滚到子评论框
const { handleBackTop: handleBackTopChildren } = useScrollTop(answerRefList) const { handleBackTop: handleBackTopChildren } = useScrollTop(answerRefList)
// 翻页时滚回到回答列表顶部 // 翻页时滚回到回答列表顶部
const { handleBackTop: handleBackTopAnswerList } = useScrollTop(answerListRef) const { handleBackTop: handleBackTopAnswerList } = useScrollTop(answerListRef)
const loading = computed(() => !questionDetail.value.title)
const isSelfAnswer = (answer: CommentItemDto) => const isSelfAnswer = (answer: CommentItemDto) =>
String(answer.userId || '') === String(userInfo.value.userId || '') String(answer.userId || '') === String(userInfo.value.userId || '')
const isAuthor = computed(() => { const isAuthor = computed(() => {
return questionDetail.value.createUserId === userInfo.value.userId return questionDetail.value.createUserId === userInfo.value.userId
}) })
const questionContentRef = useTemplateRef<HTMLElement>('questionContentRef')
const isExpand = ref(false)
// 检测当前是否超过三行 要用到具体的dom
const isOverThreeLine = computed(() => {
if (!questionContentRef.value || !questionDetail.value.content) return false
const lineHeight = parseFloat(getComputedStyle(questionContentRef.value).lineHeight)
const height = questionContentRef.value!.scrollHeight
const maxHeight = lineHeight * 3
return height > maxHeight
})
const getQuestionDetail = async () => { const getQuestionDetail = async () => {
const { data } = await getArticleDetail(questionId) const { data } = await getArticleDetail(questionId)
questionDetail.value = { questionDetail.value = {
...@@ -688,6 +437,13 @@ const openCommentDialog = () => { ...@@ -688,6 +437,13 @@ const openCommentDialog = () => {
commentDialogRef.value?.open(questionDetail.value.id) commentDialogRef.value?.open(questionDetail.value.id)
} }
const openSendMessage = () => {
sendMessageDialogRef.value?.open({
receiverId: String(questionDetail.value.createUserId),
isReal: BooleanFlag.YES,
})
}
// ==================== 评论定位(消息通知带 commentId 跳进来) ==================== // ==================== 评论定位(消息通知带 commentId 跳进来) ====================
// 问吧是三层:回答 → 回答下的评论 → 评论下的回复。回答这层由页面自己定位, // 问吧是三层:回答 → 回答下的评论 → 评论下的回复。回答这层由页面自己定位,
// 评论/回复那两层交给该回答内嵌的 Comment 组件(focusTarget),滚动只发生一次 // 评论/回复那两层交给该回答内嵌的 Comment 组件(focusTarget),滚动只发生一次
......
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