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
cfe3bd46
Commit
cfe3bd46
authored
Dec 16, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 perf: 优化问吧、评论等页面
parent
9662ea89
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
961 additions
and
615 deletions
+961
-615
types.ts
src/api/article/types.ts
+5
-0
index.vue
src/components/common/ArticleContent/index.vue
+8
-2
index.vue
src/components/common/Comment/index.vue
+16
-21
index.vue
src/components/common/CommentDialog/index.vue
+45
-351
index.vue
src/components/common/CommentListDialog/index.vue
+403
-0
onlineTime.vue
src/layoutCulture/components/onlineTime.vue
+0
-4
route.ts
src/router/route.ts
+1
-1
index.ts
src/utils/app/index.ts
+26
-0
index.ts
src/utils/index.ts
+1
-0
index.ts
src/utils/wxUtil/index.ts
+1
-1
index.vue
src/views/homePage/askTab/index.vue
+38
-6
recommendList.vue
src/views/homePage/homeTab/components/recommendList.vue
+21
-28
videoList.vue
src/views/homePage/homeTab/components/videoList.vue
+143
-59
columnList.vue
src/views/homePage/yaTab/components/columnList.vue
+3
-6
interviewList.vue
src/views/homePage/yaTab/components/interviewList.vue
+5
-7
practiceList.vue
src/views/homePage/yaTab/components/practiceList.vue
+5
-7
videoList.vue
src/views/homePage/yaTab/components/videoList.vue
+77
-22
index.vue
src/views/publishLongArticle/index.vue
+59
-15
index.vue
src/views/questionDetail/index.vue
+50
-26
SelfComplaint.vue
src/views/userPage/components/SelfComplaint.vue
+8
-11
selfAnswer.vue
src/views/userPage/components/selfAnswer.vue
+7
-5
selfCollect.vue
src/views/userPage/components/selfCollect.vue
+7
-10
selfComment.vue
src/views/userPage/components/selfComment.vue
+7
-10
selfPraise.vue
src/views/userPage/components/selfPraise.vue
+7
-10
selfPublish.vue
src/views/userPage/components/selfPublish.vue
+7
-9
index.vue
src/views/userPage/index.vue
+9
-2
index.vue
src/views/videoDetail/index.vue
+2
-2
No files found.
src/api/article/types.ts
View file @
cfe3bd46
...
...
@@ -219,6 +219,9 @@ export interface ColumnItemDto {
title
:
string
type
:
ArticleTypeEnum
.
COLUMN
viewCount
:
number
videoDuration
:
string
showName
:
string
showAvatar
:
string
}[]
}
...
...
@@ -331,6 +334,8 @@ export interface CommentItemDto {
childrenPageCurrent
:
number
childrenPageList
:
CommentItemDto
[]
loadingChildren
:
boolean
showComment
:
boolean
isExpand
:
boolean
}
/**
...
...
src/components/common/ArticleContent/index.vue
View file @
cfe3bd46
...
...
@@ -10,7 +10,13 @@
:src=
"articleDetail?.createUserAvatar"
alt=
""
class=
"w-12 h-12 rounded-full object-cover cursor-pointer"
@
click=
"router.push(`/otherUserPage/$
{articleDetail?.createUserId}/0`)"
@
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"
...
...
@@ -105,8 +111,8 @@ import dayjs from 'dayjs'
import
type
{
ArticleItemDto
}
from
'@/api'
import
{
articleTypeListOptions
,
ArticleTypeEnum
}
from
'@/constants'
import
ActionMore
from
'@/components/common/ActionMore/index.vue'
import
{
jumpToUserHomePage
}
from
'@/utils'
const
router
=
useRouter
()
const
{
articleDetail
}
=
defineProps
<
{
articleDetail
:
ArticleItemDto
}
>
()
...
...
src/components/common/Comment/index.vue
View file @
cfe3bd46
...
...
@@ -64,7 +64,7 @@
:src=
"userAvatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
@
click=
"
router.push(`/userPage`
)"
@
click=
"
jumpToUserHomePage(
{ userId: userInfo.userId, isReal: 0 }
)"
/>
<div
class=
"flex-1"
>
<div
ref=
"commentInputRef"
>
...
...
@@ -106,7 +106,7 @@
<div
class=
"p-4 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
@
click=
"
handleUserInfo(item
)"
@
click=
"
jumpToUserHomePage(
{ userId: item.userId, isReal: isReal }
)"
:src="item.avatar"
alt=""
class="w-10 h-10 rounded-full object-cover cursor-pointer"
...
...
@@ -159,7 +159,7 @@
class=
"flex gap-2 p-3 rounded-lg"
>
<img
@
click=
"
handleUserInfo(child
)"
@
click=
"
jumpToUserHomePage(
{ userId: child.userId, isReal: isReal }
)"
:src="child.avatar"
alt=""
class="w-8 h-8 rounded-full object-cover cursor-pointer"
...
...
@@ -215,7 +215,7 @@
</div>
</
template
>
<!-- 只有大于5 才会显示这个 -->
<div
class=
"ml-4"
v-show=
"item.childrenNum > 5"
>
<div
class=
"ml-4"
v-show=
"item.childrenNum > 5
&& !isQuestion
"
>
<!-- 展示 展开回复 -->
<button
v-show=
"!item.showChildrenPage"
...
...
@@ -249,13 +249,13 @@
</div>
</div>
<!-- 展示 回复评论的输入框 -->
<
!-- <transition name="fade" mode="out-in"> --
>
<
transition
name=
"fade"
mode=
"in-out"
>
<div
v-show=
"showCommentBox(item)"
class=
"flex gap-3 mt-4"
>
<img
:src=
"userAvatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover cursor-pointer"
@
click=
"router.push(`/userPage`
)"
@
click=
"jumpToUserHomePage({ userId: userInfo.userId, isReal: isReal }
)"
/>
<div
class=
"flex-1"
>
<el-input
...
...
@@ -283,7 +283,7 @@
</div>
</div>
</div>
<
!-- </transition> --
>
<
/transition
>
</div>
</div>
</div>
...
...
@@ -309,7 +309,7 @@
</div>
</div>
</div>
<Comment
Dialog
ref=
"commen
tDialogRef"
:articleId=
"id"
:pid=
"currentDialogCommentPid"
/>
<Comment
ListDialog
ref=
"commentLis
tDialogRef"
:articleId=
"id"
:pid=
"currentDialogCommentPid"
/>
</div>
</template>
<
script
lang=
"ts"
setup
>
...
...
@@ -326,7 +326,8 @@ import type { CommentItemDto } from '@/api'
import
dayjs
from
'dayjs'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
CommentDialog
from
'../CommentDialog/index.vue'
import
CommentListDialog
from
'../CommentListDialog/index.vue'
import
{
jumpToUserHomePage
}
from
'@/utils'
const
{
id
,
defaultSize
=
10
,
...
...
@@ -354,7 +355,7 @@ const userStore = useUserStore()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
userAvatar
=
computed
(()
=>
(
isReal
?
userInfo
.
value
.
avatar
:
userInfo
.
value
.
hiddenAvatar
))
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
comment
DialogRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commen
tDialogRef'
)
const
comment
ListDialogRef
=
useTemplateRef
<
typeof
CommentListDialog
>
(
'commentLis
tDialogRef'
)
const
commentInputRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentInputRef'
)
const
commentItemRefList
=
ref
<
HTMLElement
[]
>
([])
// 回滚到评论框
...
...
@@ -525,14 +526,10 @@ const getCurrentChildrenList = (item: CommentItemDto) => {
}
}
const
handleUserInfo
=
(
item
:
CommentItemDto
)
=>
{
router
.
push
(
`/otherUserPage/
${
item
.
userId
}
/
${
isReal
}
`
)
}
const
currentDialogCommentPid
=
ref
(
0
)
const
handleOpenCommentDialog
=
(
item
:
CommentItemDto
)
=>
{
currentDialogCommentPid
.
value
=
item
.
id
commentDialogRef
.
value
?.
open
()
comment
List
DialogRef
.
value
?.
open
()
}
defineExpose
({
...
...
@@ -541,14 +538,12 @@ defineExpose({
})
</
script
>
<
style
scoped
lang=
"scss"
>
.fade-enter-from
,
.fade-leave-to
{
.fade-enter-from
{
opacity
:
0
;
transform
:
translateY
(
10px
);
transform
:
translateY
(
-
10px
);
}
.fade-enter-active
,
.fade-leave-active
{
transition
:
all
0.3s
ease
;
.fade-enter-active
{
transition
:
all
0.5s
ease-out
;
}
</
style
>
src/components/common/CommentDialog/index.vue
View file @
cfe3bd46
<
template
>
<el-dialog
v-model=
"visible"
:title=
"dialogTitle"
width=
"600px"
class=
"rounded-xl overflow-hidden"
:show-close=
"false"
append-to-body
destroy-on-close
>
<!-- 自定义头部 -->
<template
#
header=
"
{ close, titleId, titleClass }">
<div
class=
"flex items-center gap-2 py-2 border-b border-gray-100"
>
<el-icon
class=
"cursor-pointer hover:text-blue-500"
@
click=
"close"
:size=
"20"
>
<ArrowLeft
/>
</el-icon>
<span
:id=
"titleId"
:class=
"titleClass"
class=
"text-base font-bold text-gray-800"
>
评论回复
</span
>
</div>
</
template
>
<div
class=
"flex flex-col h-[70vh]"
>
<!-- 中间滚动区域 -->
<div
class=
"flex-1 overflow-y-auto custom-scrollbar p-4"
ref=
"scrollContainer"
>
<!-- 1. 顶部:父级评论展示 -->
<div
v-if=
"parentComment"
class=
"flex gap-3 mb-6"
>
<img
:src=
"parentComment.hiddenAvatar"
class=
"w-10 h-10 rounded-full object-cover border border-gray-100"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center justify-between mb-1"
>
<span
class=
"font-bold text-gray-900 text-sm"
>
{{ parentComment.hiddenName }}
</span>
<!-- 点赞按钮 -->
<div
class=
"flex items-center gap-1 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors"
@
click=
"handleLike(parentComment)"
>
<el-icon
:size=
"16"
>
<svg-icon
:name=
"parentComment.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-xs"
>
{{ parentComment.postPriseCount || 0 }}
</span>
</div>
</div>
<p
class=
"text-gray-800 text-sm leading-relaxed mb-2"
>
{{ parentComment.content }}
</p>
<div
class=
"text-xs text-gray-400 flex items-center gap-3"
>
<span>
{{ dayjs(parentComment.createTime * 1000).format('MM-DD HH:mm') }}
</span>
<!-- <span>IP: {{ parentComment.ipLocation || '未知' }}</span> -->
<!-- <span class="bg-red-50 text-red-500 px-1.5 py-0.5 rounded text-[10px]">热评</span> -->
</div>
</div>
</div>
<!-- 分割线 & 统计 -->
<div
class=
"h-2 bg-gray-50 -mx-4 mb-4"
></div>
<div
class=
"text-sm font-bold text-gray-800 mb-4"
>
{{ total }} 条回复
</div>
<!-- 2. 下方:回复列表 -->
<div
v-loading=
"loading"
class=
"space-y-6"
>
<div
v-for=
"item in list"
:key=
"item.id"
class=
"flex gap-3 relative group"
>
<img
:src=
"item.avatar"
class=
"w-8 h-8 rounded-full object-cover cursor-pointer hover:opacity-80"
@
click=
"handleUserInfo(item)"
/>
<div
class=
"flex-1 border-b border-gray-50 pb-4"
>
<div
class=
"flex items-center justify-between mb-1"
>
<div
class=
"flex items-center gap-2"
>
<span
class=
"font-semibold text-gray-800 text-sm"
>
{{ item.replyUser }}
</span>
<span
v-if=
"item.replyName && item.replyName !== parentComment?.replyUser"
class=
"text-gray-400 text-xs flex items-center"
>
<el-icon
class=
"mx-1"
><CaretRight
/></el-icon>
{{ item.replyName }}
</span>
</div>
<!-- 列表项点赞 -->
<div
class=
"flex items-center gap-4"
>
<div
class=
"flex items-center gap-1 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors"
@
click=
"handleReplyInline(item)"
>
<span
class=
"text-xs"
>
回复
</span>
</div>
<div
class=
"flex items-center gap-1 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors"
@
click=
"handleLike(item)"
>
<el-icon
:size=
"16"
>
<svg-icon
:name=
"item.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-xs"
>
{{ item.postPriseCount || 0 }}
</span>
</div>
</div>
</div>
<p
class=
"text-gray-700 text-sm mb-2 break-all"
>
{{ item.content }}
</p>
<div
class=
"text-xs text-gray-400"
>
<span>
{{ formatDate(item.createTime) }}
</span>
</div>
<el-dialog
v-model=
"visible"
title=
"发表评论"
width=
"500px"
:before-close=
"handleClose"
>
<div
class=
"flex gap-3"
>
<!-- 用户头像 -->
<el-avatar
:size=
"40"
:src=
"userInfo.hiddenAvatar"
/>
<!-- 内嵌回复框 (点击列表回复按钮出现) -->
<div
v-if=
"currentInlineReplyId === item.id"
class=
"mt-3 bg-gray-50 p-3 rounded-lg border border-gray-100 animate-fade-in"
>
<!-- 评论输入框 -->
<el-input
v-model=
"inlineC
ommentContent"
v-model=
"c
ommentContent"
type=
"textarea"
:rows=
"2"
:placeholder=
"`回复 ${item.replyUser}`"
class=
"bg-white mb-2"
resize=
"none"
/>
<div
class=
"flex justify-between items-center"
>
<div
class=
"flex gap-2 text-gray-400 text-lg"
>
<i
class=
"cursor-pointer i-carbon-face-satisfied hover:text-yellow-500"
></i>
<i
class=
"cursor-pointer i-carbon-image hover:text-blue-500"
></i>
</div>
<el-button
type=
"primary"
size=
"small"
class=
"!rounded-full !px-4"
:disabled=
"!inlineCommentContent.trim()"
@
click=
"submitReply(item.id)"
>
发布
</el-button>
</div>
</div>
</div>
</div>
<!-- 加载更多 / 分页 -->
<div
class=
"flex justify-end py-4"
v-if=
"total > 0"
>
<el-pagination
v-model:current-page=
"searchParams.current"
:page-size=
"searchParams.size"
:total=
"total"
layout=
"prev, pager, next"
small
background
@
current-change=
"goToPage"
@
size-change=
"changePageSize"
:rows=
"4"
placeholder=
"写下你的评论..."
maxlength=
"500"
show-word-limit
class=
"flex-1"
/>
</div>
<el-empty
v-if=
"!loading && list.length === 0"
description=
"暂无回复"
:image-size=
"60"
/>
</div>
</div>
<!-- 3. 底部固定回复框 (回复给最上方的父评论) -->
<div
class=
"border-t border-gray-100 p-3 bg-white flex items-center gap-3 z-10 shadow-[0_-2px_10px_rgba(0,0,0,0.02)]"
>
<img
:src=
"currentUserAvatar"
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1 bg-gray-100 rounded-full px-4 py-2 flex items-center cursor-text hover:bg-gray-200 transition-colors"
@
click=
"focusBottomInput"
>
<input
ref=
"bottomInputRef"
v-model=
"bottomCommentContent"
:autosize=
"{ minRows: 1, maxRows: 4 }"
type=
"textarea"
class=
"bg-transparent w-full outline-none text-sm text-gray-700 placeholder-gray-400"
:placeholder=
"`回复 ${parentComment?.replyUser || '...'}`"
@
keyup
.
enter=
"submitReply(parentComment?.id)"
/>
</div>
<template
#
footer
>
<div
class=
"flex justify-end gap-2"
>
<el-button
@
click=
"handleClose"
class=
"rounded-lg"
>
取消
</el-button>
<el-button
type=
"primary"
circle
:disabled=
"!bottomCommentContent.trim()
"
@
click=
"submitReply(parentComment?.id)"
@
click=
"handleSubmit"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200
"
>
发表
</el-button
>
<el-icon><Position
/></el-icon>
</el-button>
</div>
</div>
</
template
>
</el-dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
,
nextTick
}
from
'vue'
import
{
ArrowLeft
,
Star
,
StarFilled
,
CaretRight
,
Position
}
from
'@element-plus/icons-vue'
import
{
ElMessage
}
from
'element-plus'
import
dayjs
from
'dayjs'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
getSecondCommentChildren
,
addComment
,
addOrCancelCommentLike
,
getCommentDetail
,
}
from
'@/api'
import
type
{
CommentItemDto
}
from
'@/api'
import
{
BooleanFlag
}
from
'@/constants'
import
{
usePageSearch
}
from
'@/hooks'
// 假设你有这个hook
// Props
const
{
articleId
,
pid
}
=
defineProps
<
{
articleId
:
number
pid
:
number
}
>
()
import
{
addComment
}
from
'@/api'
const
emit
=
defineEmits
<
{
(
e
:
'
refresh'
):
void
// 通知父组件刷新
(
e
:
'
commentSuccess'
):
void
}
>
()
// Store
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
currentUserAvatar
=
computed
(()
=>
userInfo
.
value
.
hiddenAvatar
)
// State
// 弹窗显示状态
const
visible
=
ref
(
false
)
const
parentComment
=
ref
<
CommentItemDto
|
null
>
(
null
)
const
dialogTitle
=
ref
(
'详情'
)
// Inline Reply State
const
currentInlineReplyId
=
ref
<
number
|
null
>
(
null
)
const
inlineCommentContent
=
ref
(
''
)
const
bottomCommentContent
=
ref
(
''
)
const
bottomInputRef
=
ref
<
HTMLInputElement
>
()
const
scrollContainer
=
ref
<
HTMLElement
>
()
// 评论内容
const
commentContent
=
ref
(
''
)
let
articleId
=
0
// --- Actions ---
const
{
list
,
total
,
search
,
searchParams
,
goToPage
,
changePageSize
,
refresh
,
loading
}
=
usePageSearch
(
getSecondCommentChildren
,
{
defaultParams
:
{
articleId
:
articleId
,
pid
:
0
,
},
immediate
:
false
,
})
const
open
=
async
(
item
:
CommentItemDto
)
=>
{
// const { data } = await getSecondCommentChildren({
// pid: item.id,
// current: 1,
// size: 10,
// articleId: articleId,
// })
// console.log('res', data)
// list.value = data.list
// parentComment.value = item
// // Reset state
// currentInlineReplyId.value = null
// inlineCommentContent.value = ''
// bottomCommentContent.value = ''
console
.
log
(
'pid'
,
pid
)
await
nextTick
()
searchParams
.
value
.
pid
=
pid
search
()
const
{
data
}
=
await
getCommentDetail
(
pid
)
console
.
log
(
'data'
,
data
)
parentComment
.
value
=
data
// 暴露 open 方法
const
open
=
(
id
:
number
)
=>
{
articleId
=
id
visible
.
value
=
true
commentContent
.
value
=
''
}
const
close
=
()
=>
{
// 关闭弹窗
const
handleClose
=
()
=>
{
visible
.
value
=
false
commentContent
.
value
=
''
}
const
formatDate
=
(
time
:
number
)
=>
{
return
dayjs
(
time
*
1000
).
format
(
'MM-DD HH:mm'
)
}
// 点击列表中的“回复”
const
handleReplyInline
=
(
item
:
CommentItemDto
)
=>
{
if
(
currentInlineReplyId
.
value
===
item
.
id
)
{
currentInlineReplyId
.
value
=
null
// Toggle off
}
else
{
currentInlineReplyId
.
value
=
item
.
id
inlineCommentContent
.
value
=
''
// Clear previous
// 提交评论
const
handleSubmit
=
async
()
=>
{
if
(
!
commentContent
.
value
.
trim
())
{
ElMessage
.
warning
(
'请输入评论内容'
)
return
}
}
// 聚焦底部输入框
const
focusBottomInput
=
()
=>
{
bottomInputRef
.
value
?.
focus
()
}
// 提交评论 (共用逻辑)
// targetId: 如果是回复父评论,传 parentComment.id;如果是回复子评论,传 item.id
const
submitReply
=
async
(
targetId
:
number
|
undefined
)
=>
{
if
(
!
targetId
)
return
// 判断使用的是哪个输入框的内容
const
isBottom
=
targetId
===
parentComment
.
value
?.
id
const
content
=
isBottom
?
bottomCommentContent
.
value
:
inlineCommentContent
.
value
if
(
!
content
.
trim
())
return
try
{
// TODO: 这里处理提交逻辑
await
addComment
({
articleId
:
articleId
,
content
:
content
,
pid
:
targetId
,
// 这里的pid逻辑根据您的后端接口来,通常回复子评论也是传该子评论ID作为pid
content
:
commentContent
.
value
,
})
ElMessage
.
success
(
'回复成功'
)
// 清空输入框
if
(
isBottom
)
{
bottomCommentContent
.
value
=
''
}
else
{
inlineCommentContent
.
value
=
''
currentInlineReplyId
.
value
=
null
}
// 刷新列表
refresh
()
// 通知父组件可能需要更新评论数
emit
(
'refresh'
)
}
catch
(
error
)
{
console
.
error
(
error
)
}
}
// 点赞
const
handleLike
=
async
(
item
:
CommentItemDto
)
=>
{
try
{
await
addOrCancelCommentLike
(
item
.
id
)
// 乐观更新 UI
if
(
item
.
hasPraise
===
BooleanFlag
.
YES
)
{
item
.
hasPraise
=
BooleanFlag
.
NO
item
.
postPriseCount
--
}
else
{
item
.
hasPraise
=
BooleanFlag
.
YES
item
.
postPriseCount
++
}
}
catch
(
error
)
{
console
.
error
(
error
)
}
}
const
handleUserInfo
=
(
item
:
CommentItemDto
)
=>
{
// 您的跳转逻辑
// router.push(...)
console
.
log
(
'评论内容:'
,
commentContent
.
value
)
ElMessage
.
success
(
'评论发表成功'
)
handleClose
()
emit
(
'commentSuccess'
)
}
// 暴露方法给父组件
defineExpose
({
open
,
})
</
script
>
<
style
scoped
lang=
"scss"
>
/* 使用 UnoCSS 这里写一些 Element Plus 的样式覆盖 */
:deep
(
.el-dialog__header
)
{
margin-right
:
0
;
padding
:
0
16px
;
}
:deep
(
.el-dialog__body
)
{
padding
:
0
;
}
/* 自定义滚动条 */
.custom-scrollbar
::-webkit-scrollbar
{
width
:
6px
;
}
.custom-scrollbar
::-webkit-scrollbar-thumb
{
background-color
:
#e5e7eb
;
border-radius
:
4px
;
}
.custom-scrollbar
::-webkit-scrollbar-track
{
background-color
:
transparent
;
}
.animate-fade-in
{
animation
:
fadeIn
0.2s
ease-out
;
}
@keyframes
fadeIn
{
from
{
opacity
:
0
;
transform
:
translateY
(
-5px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
<
style
scoped
>
/* 如果需要额外样式可以在这里添加 */
</
style
>
src/components/common/CommentListDialog/index.vue
0 → 100644
View file @
cfe3bd46
<
template
>
<el-dialog
v-model=
"visible"
:title=
"dialogTitle"
width=
"700px"
class=
"rounded-2xl overflow-hidden"
:show-close=
"false"
top=
"5vh"
append-to-body
destroy-on-close
>
<!-- 自定义头部 -->
<template
#
header=
"
{ close, titleId, titleClass }">
<div
class=
"flex items-center gap-3 py-4 px-2 border-b border-gray-100"
>
<el-icon
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"close"
:size=
"24"
>
<ArrowLeft
/>
</el-icon>
<span
:id=
"titleId"
:class=
"titleClass"
class=
"text-lg font-bold text-gray-800"
>
评论回复
</span>
</div>
</
template
>
<div
class=
"flex flex-col h-[80vh]"
>
<!-- 中间滚动区域 -->
<div
class=
"flex-1 overflow-y-auto custom-scrollbar p-6 pt-0"
ref=
"scrollContainer"
>
<!-- 1. 顶部:父级评论展示 -->
<div
v-if=
"parentComment"
class=
"flex gap-4 bg-gray-50 p-5 rounded-xl"
>
<img
:src=
"parentComment.hiddenAvatar"
class=
"w-12 h-12 rounded-full object-cover border-2 border-gray-200 flex-shrink-0"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center justify-between mb-2"
>
<span
class=
"font-bold text-gray-900 text-base"
>
{{ parentComment.hiddenName }}
</span>
<!-- 点赞按钮 -->
<div
class=
"flex items-center gap-1.5 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors px-3 py-1.5 rounded-full hover:bg-blue-50"
@
click=
"handleLike(parentComment)"
>
<el-icon
:size=
"18"
>
<svg-icon
:name=
"parentComment.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-sm font-medium"
>
{{ parentComment.postPriseCount || 0 }}
</span>
</div>
</div>
<p
class=
"text-gray-800 text-base leading-relaxed mb-3"
>
{{ parentComment.content }}
</p>
<div
class=
"text-sm text-gray-400 flex items-center gap-4"
>
<span>
{{ dayjs(parentComment.createTime * 1000).format('MM-DD HH:mm') }}
</span>
</div>
</div>
</div>
<!-- 分割线 & 统计 -->
<div
class=
"h-2.5"
></div>
<div
class=
"text-base font-bold text-gray-800 mb-6 px-1"
>
{{ total }} 条回复
</div>
<!-- 2. 下方:回复列表 -->
<div
v-loading=
"loading"
class=
"space-y-6"
>
<div
v-for=
"item in list"
:key=
"item.id"
class=
"flex gap-4 relative group"
>
<img
:src=
"item.avatar"
class=
"w-10 h-10 rounded-full object-cover cursor-pointer hover:opacity-80 transition-opacity flex-shrink-0"
@
click=
"handleUserInfo(item)"
/>
<div
class=
"flex-1 border-b border-gray-100"
>
<div
class=
"flex items-center justify-between mb-2"
>
<div
class=
"flex items-center gap-2"
>
<span
class=
"font-semibold text-gray-900 text-base"
>
{{ item.replyUser }}
</span>
<span
v-if=
"item.replyName && item.replyName !== parentComment?.replyUser"
class=
"text-gray-400 text-sm flex items-center"
>
<el-icon
class=
"mx-1"
><CaretRight
/></el-icon>
{{ item.replyName }}
</span>
</div>
<!-- 列表项点赞 -->
<div
class=
"flex items-center gap-4"
>
<div
class=
"flex items-center gap-1.5 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors px-3 py-1.5 rounded-full hover:bg-blue-50"
@
click=
"handleReplyInline(item)"
>
<span
class=
"text-sm font-medium"
>
回复
</span>
</div>
<div
class=
"flex items-center gap-1.5 cursor-pointer text-gray-400 hover:text-blue-500 transition-colors px-3 py-1.5 rounded-full hover:bg-blue-50"
@
click=
"handleLike(item)"
>
<el-icon
:size=
"18"
>
<svg-icon
:name=
"item.hasPraise ? 'praise_fill' : 'praise'"
></svg-icon>
</el-icon>
<span
class=
"text-sm font-medium"
>
{{ item.postPriseCount || 0 }}
</span>
</div>
</div>
</div>
<p
class=
"text-gray-700 text-base mb-3 break-all leading-relaxed"
>
{{ item.content }}
</p>
<div
class=
"text-sm text-gray-400"
>
<span>
{{ formatDate(item.createTime) }}
</span>
</div>
<!-- 内嵌回复框 -->
<div
v-if=
"currentInlineReplyId === item.id"
class=
"mt-4 bg-gray-50 p-4 rounded-xl border border-gray-200 animate-fade-in animate-fade-out"
>
<el-input
v-model=
"inlineCommentContent"
type=
"textarea"
:rows=
"3"
:placeholder=
"`回复 ${item.replyUser}`"
class=
"bg-white mb-3 text-base"
resize=
"none"
/>
<div
class=
"flex justify-between items-center"
>
<div
class=
"flex gap-3 text-gray-400 text-xl"
>
<i
class=
"cursor-pointer i-carbon-face-satisfied hover:text-yellow-500 transition-colors"
></i>
<i
class=
"cursor-pointer i-carbon-image hover:text-blue-500 transition-colors"
></i>
</div>
<el-button
type=
"primary"
size=
"default"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
:disabled=
"!inlineCommentContent.trim()"
@
click=
"submitReply(item.id)"
>
发布
</el-button>
</div>
</div>
</div>
</div>
<!-- 加载更多 / 分页 -->
<div
class=
"flex justify-end"
v-if=
"total > 0"
>
<el-pagination
v-model:current-page=
"searchParams.current"
:page-size=
"searchParams.size"
:total=
"total"
layout=
"prev, pager, next,total"
@
current-change=
"goToPage"
@
size-change=
"changePageSize"
/>
</div>
<el-empty
v-if=
"!loading && list.length === 0"
description=
"暂无回复"
:image-size=
"80"
/>
</div>
</div>
<!-- 3. 底部固定回复框 -->
<div
class=
"border-t border-gray-100 p-5 bg-white flex items-center gap-4 z-10 shadow-[0_-4px_16px_rgba(0,0,0,0.04)]"
>
<img
:src=
"currentUserAvatar"
class=
"w-10 h-10 rounded-full object-cover flex-shrink-0"
/>
<div
class=
"flex-1 bg-gray-100 rounded-full px-5 py-3 flex items-center cursor-text hover:bg-gray-200 transition-colors"
@
click=
"focusBottomInput"
>
<input
ref=
"bottomInputRef"
v-model=
"bottomCommentContent"
type=
"text"
class=
"bg-transparent w-full outline-none text-base text-gray-700 placeholder-gray-400"
:placeholder=
"`回复 ${parentComment?.replyUser || '...'}`"
@
keyup
.
enter=
"submitReply(parentComment?.id)"
/>
</div>
<el-button
type=
"primary"
size=
"large"
circle
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
:disabled=
"!bottomCommentContent.trim()"
@
click=
"submitReply(parentComment?.id)"
>
<el-icon
:size=
"20"
><Position
/></el-icon>
</el-button>
</div>
</div>
</el-dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
,
nextTick
}
from
'vue'
import
{
ArrowLeft
,
Star
,
StarFilled
,
CaretRight
,
Position
}
from
'@element-plus/icons-vue'
import
{
ElMessage
}
from
'element-plus'
import
dayjs
from
'dayjs'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
getSecondCommentChildren
,
addComment
,
addOrCancelCommentLike
,
getCommentDetail
,
}
from
'@/api'
import
type
{
CommentItemDto
}
from
'@/api'
import
{
BooleanFlag
}
from
'@/constants'
import
{
usePageSearch
}
from
'@/hooks'
// 假设你有这个hook
// Props
const
{
articleId
,
pid
}
=
defineProps
<
{
articleId
:
number
pid
:
number
}
>
()
const
emit
=
defineEmits
<
{
(
e
:
'refresh'
):
void
// 通知父组件刷新
}
>
()
// Store
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
currentUserAvatar
=
computed
(()
=>
userInfo
.
value
.
hiddenAvatar
)
// State
const
visible
=
ref
(
false
)
const
parentComment
=
ref
<
CommentItemDto
|
null
>
(
null
)
const
dialogTitle
=
ref
(
'详情'
)
// Inline Reply State
const
currentInlineReplyId
=
ref
<
number
|
null
>
(
null
)
const
inlineCommentContent
=
ref
(
''
)
const
bottomCommentContent
=
ref
(
''
)
const
bottomInputRef
=
ref
<
HTMLInputElement
>
()
const
scrollContainer
=
ref
<
HTMLElement
>
()
// --- Actions ---
const
{
list
,
total
,
search
,
searchParams
,
goToPage
,
changePageSize
,
refresh
,
loading
}
=
usePageSearch
(
getSecondCommentChildren
,
{
defaultParams
:
{
articleId
:
articleId
,
pid
:
0
,
},
immediate
:
false
,
})
const
open
=
async
(
item
:
CommentItemDto
)
=>
{
// const { data } = await getSecondCommentChildren({
// pid: item.id,
// current: 1,
// size: 10,
// articleId: articleId,
// })
// console.log('res', data)
// list.value = data.list
// parentComment.value = item
// // Reset state
// currentInlineReplyId.value = null
// inlineCommentContent.value = ''
// bottomCommentContent.value = ''
console
.
log
(
'pid'
,
pid
)
await
nextTick
()
searchParams
.
value
.
pid
=
pid
search
()
const
{
data
}
=
await
getCommentDetail
(
pid
)
console
.
log
(
'data'
,
data
)
parentComment
.
value
=
data
visible
.
value
=
true
}
const
close
=
()
=>
{
visible
.
value
=
false
}
const
formatDate
=
(
time
:
number
)
=>
{
return
dayjs
(
time
*
1000
).
format
(
'MM-DD HH:mm'
)
}
// 点击列表中的“回复”
const
handleReplyInline
=
(
item
:
CommentItemDto
)
=>
{
if
(
currentInlineReplyId
.
value
===
item
.
id
)
{
currentInlineReplyId
.
value
=
null
// Toggle off
}
else
{
currentInlineReplyId
.
value
=
item
.
id
inlineCommentContent
.
value
=
''
// Clear previous
}
}
// 聚焦底部输入框
const
focusBottomInput
=
()
=>
{
bottomInputRef
.
value
?.
focus
()
}
// 提交评论 (共用逻辑)
// targetId: 如果是回复父评论,传 parentComment.id;如果是回复子评论,传 item.id
const
submitReply
=
async
(
targetId
:
number
|
undefined
)
=>
{
if
(
!
targetId
)
return
// 判断使用的是哪个输入框的内容
const
isBottom
=
targetId
===
parentComment
.
value
?.
id
const
content
=
isBottom
?
bottomCommentContent
.
value
:
inlineCommentContent
.
value
if
(
!
content
.
trim
())
return
try
{
await
addComment
({
articleId
:
articleId
,
content
:
content
,
pid
:
targetId
,
// 这里的pid逻辑根据您的后端接口来,通常回复子评论也是传该子评论ID作为pid
})
ElMessage
.
success
(
'回复成功'
)
// 清空输入框
if
(
isBottom
)
{
bottomCommentContent
.
value
=
''
}
else
{
inlineCommentContent
.
value
=
''
currentInlineReplyId
.
value
=
null
}
// 刷新列表
refresh
()
// 通知父组件可能需要更新评论数
emit
(
'refresh'
)
}
catch
(
error
)
{
console
.
error
(
error
)
}
}
// 点赞
const
handleLike
=
async
(
item
:
CommentItemDto
)
=>
{
try
{
await
addOrCancelCommentLike
(
item
.
id
)
// 乐观更新 UI
if
(
item
.
hasPraise
===
BooleanFlag
.
YES
)
{
item
.
hasPraise
=
BooleanFlag
.
NO
item
.
postPriseCount
--
}
else
{
item
.
hasPraise
=
BooleanFlag
.
YES
item
.
postPriseCount
++
}
}
catch
(
error
)
{
console
.
error
(
error
)
}
}
const
handleUserInfo
=
(
item
:
CommentItemDto
)
=>
{
// 您的跳转逻辑
// router.push(...)
}
defineExpose
({
open
,
})
</
script
>
<
style
scoped
lang=
"scss"
>
/* 使用 UnoCSS 这里写一些 Element Plus 的样式覆盖 */
:deep
(
.el-dialog__header
)
{
margin-right
:
0
;
padding
:
0
16px
;
}
:deep
(
.el-dialog__body
)
{
padding
:
0
;
}
/* 自定义滚动条 */
.custom-scrollbar
::-webkit-scrollbar
{
width
:
6px
;
}
.custom-scrollbar
::-webkit-scrollbar-thumb
{
background-color
:
#e5e7eb
;
border-radius
:
4px
;
}
.custom-scrollbar
::-webkit-scrollbar-track
{
background-color
:
transparent
;
}
.animate-fade-in
{
animation
:
fadeIn
0.2s
ease-out
;
}
@keyframes
fadeIn
{
from
{
opacity
:
0
;
transform
:
translateY
(
-5px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
</
style
>
src/layoutCulture/components/onlineTime.vue
View file @
cfe3bd46
...
...
@@ -96,11 +96,7 @@ const widthRate = computed(() => {
// 在线时长格式化 将秒级 格式化为 00:00
const
formatSeconds
=
computed
(()
=>
{
if
(
currentSeconds
.
value
>=
maxSeconds
)
{
return
'30:00'
}
else
{
return
dayjs
.
utc
(
currentSeconds
.
value
*
1000
).
format
(
'mm:ss'
)
}
})
onMounted
(
async
()
=>
{
...
...
src/router/route.ts
View file @
cfe3bd46
...
...
@@ -167,7 +167,7 @@ export const constantsRoute = [
component
:
()
=>
import
(
'@/views/questionDetail/index.vue'
),
},
{
path
:
'publishLongArticle'
,
path
:
'publishLongArticle
/:type
'
,
name
:
'CulturePublishLongArticle'
,
component
:
()
=>
import
(
'@/views/publishLongArticle/index.vue'
),
},
...
...
src/utils/app.ts
→
src/utils/app
/index
.ts
View file @
cfe3bd46
import
type
{
ArticleType
,
BooleanFlag
}
from
'@/constants'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
/**
* 页面改变标题
* @param title
...
...
@@ -59,3 +62,26 @@ export function isCulturePath() {
const
path
=
window
.
location
.
pathname
return
path
.
includes
(
'/culture'
)
}
// 点击头像跳转用户首页
export
function
jumpToUserHomePage
({
userId
,
isReal
}:
{
userId
:
number
;
isReal
:
BooleanFlag
})
{
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
isSelf
=
userInfo
.
value
.
userId
===
userId
if
(
isSelf
)
{
window
.
open
(
`/userPage/selfPublish`
)
}
else
{
window
.
open
(
`/otherUserPage/
${
userId
}
/
${
isReal
}
`
)
}
}
// 根据文章类型跳到对应的文章详情页面
export
function
jumpToArticleDetailPage
({
type
,
id
}:
{
type
:
ArticleType
;
id
:
number
})
{
if
(
type
===
'video'
)
{
window
.
open
(
`/videoDetail/
${
id
}
`
)
}
else
if
(
type
===
'question'
)
{
window
.
open
(
`/questionDetail/
${
id
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
id
}
`
)
}
}
src/utils/index.ts
0 → 100644
View file @
cfe3bd46
export
*
from
'./app'
src/utils/wxUtil/index.ts
View file @
cfe3bd46
...
...
@@ -68,7 +68,7 @@ export function selectDepOrUser(wxOption = {}): Promise<ISelectDepOrUser> {
...
wxOption
,
}
return
new
Promise
((
resolve
,
reject
)
=>
{
w
x
.
invoke
(
'selectEnterpriseContact'
,
option
,
function
(
res
:
ISelectDepOrUser
)
{
w
w
.
invoke
(
'selectEnterpriseContact'
,
option
,
function
(
res
:
ISelectDepOrUser
)
{
if
(
res
.
err_msg
==
'selectEnterpriseContact:ok'
)
{
resolve
(
res
)
}
...
...
src/views/homePage/askTab/index.vue
View file @
cfe3bd46
...
...
@@ -44,9 +44,15 @@
class=
"text-gray-600 text-base leading-relaxed transition-all duration-300"
:class=
"
{ 'line-clamp-3': !item.isExpand }"
>
<!-- 如果有评论的话 就展示 最热的 没有的话 说明没有评论 就展示内容 -->
<template
v-if=
"item.cultureCommentListVo?.hiddenName"
>
{{
item
.
cultureCommentListVo
?.
hiddenName
}}
:
{{
item
.
cultureCommentListVo
?.
content
}}
</
template
>
<
template
v-else
>
{{
item
.
content
}}
</
template
>
</p>
<!-- 展开/收起按钮 靠右边布局 -->
<div
class=
"flex justify-end"
>
...
...
@@ -112,7 +118,12 @@
<!-- 当前最热评论的评论有几条 -->
<el-button
size=
"small"
plain
@
click
.
stop=
"handleComment(item, index)"
>
<el-icon><Edit
/></el-icon>
{{
item
.
cultureCommentListVo
?.
childNum
||
0
}}
条评论
<
template
v-if=
"item.cultureCommentListVo?.childNum"
>
{{
item
.
cultureCommentListVo
?.
childNum
}}
条评论
</
template
>
<
template
v-else
>
<span>
写评论
</span>
</
template
>
</el-button>
<ActionMore
class=
"ml-4"
:articleDetail=
"item"
/>
...
...
@@ -152,7 +163,7 @@
</div>
<Transition
name=
"fade"
>
<Comment
v-
show
=
"item.showComment"
v-
if
=
"item.showComment"
:ref=
"(e) => (commentRefList[index] = e as InstanceType<typeof Comment>)"
:id=
"item.id"
:total=
"item.cultureCommentListVo?.childNum || 0"
...
...
@@ -183,6 +194,13 @@
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
@
current-change=
"
async (e) => {
await goToPage(e)
handleBackTop()
}
"
@
size-change=
"changePageSize"
:page-sizes=
"[15, 30, 45, 60]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
...
...
@@ -207,6 +225,7 @@
</el-tour-step>
<
template
#
indicators
></
template
>
</el-tour>
<CommentDialog
ref=
"commentDialogRef"
@
commentSuccess=
"search"
/>
</div>
</template>
...
...
@@ -223,12 +242,14 @@ import { getArticleList, addOrCanceArticlelCollect, addOrCancelToAnswerList } fr
import
type
{
ArticleItemDto
}
from
'@/api/article/types'
import
{
useQuestionStore
}
from
'@/stores/question'
import
ActionMore
from
'@/components/common/ActionMore/index.vue'
import
CommentDialog
from
'@/components/common/CommentDialog/index.vue'
const
{
fetchUserQestionNum
}
=
useQuestionStore
()
const
route
=
useRoute
()
const
open
=
ref
(
false
)
const
publishBoxRef
=
useTemplateRef
(
'publishBoxRef'
)
const
commentDialogRef
=
useTemplateRef
<
typeof
CommentDialog
>
(
'commentDialogRef'
)
const
activeTab
=
ref
(
'最新'
)
const
tabs
=
[
...
...
@@ -237,7 +258,8 @@ const tabs = [
{
label
:
'关注'
,
value
:
'关注'
},
]
const
{
list
,
total
,
searchParams
,
loading
,
refresh
}
=
usePageSearch
(
getArticleList
,
{
const
{
list
,
total
,
searchParams
,
loading
,
refresh
,
goToPage
,
changePageSize
,
search
}
=
usePageSearch
(
getArticleList
,
{
immediate
:
false
,
defaultParams
:
{
type
:
ArticleTypeEnum
.
QUESTION
,
...
...
@@ -248,7 +270,7 @@ const { list, total, searchParams, loading, refresh } = usePageSearch(getArticle
showComment
:
false
,
isExpand
:
false
,
})),
})
})
const
tabsRef
=
inject
(
TABS_REF_KEY
)
...
...
@@ -261,9 +283,19 @@ const handleCollect = async (item: ArticleItemDto) => {
ElMessage
.
success
(
item
.
hasCollect
?
'收藏成功'
:
'取消收藏'
)
}
const
handleComment
=
(
item
:
ArticleItemDto
,
index
:
number
)
=>
{
commentRefList
.
value
[
index
]?.
search
()
// 一个是直接写这个问题的首评论 一个是直接给最热评论写评论
const
handleComment
=
async
(
item
:
ArticleItemDto
,
index
:
number
)
=>
{
if
(
item
.
cultureCommentListVo
?.
hiddenName
)
{
item
.
showComment
=
!
item
.
showComment
await
nextTick
()
if
(
item
.
showComment
)
{
commentRefList
.
value
[
index
]?.
search
()
}
}
else
{
// 直接让他写评论
commentDialogRef
.
value
?.
open
(
item
.
id
)
}
}
const
handleCommentSuccess
=
(
item
:
ArticleItemDto
)
=>
{
...
...
src/views/homePage/homeTab/components/recommendList.vue
View file @
cfe3bd46
...
...
@@ -6,7 +6,7 @@
v-for=
"item in list"
:key=
"item.id"
class=
"group bg-white rounded-lg p-4 sm:p-6 cursor-pointer transition-all duration-300 hover:shadow-lg hover:shadow-gray-100 hover:-translate-y-1 border border-gray-100 hover:border-gray-200 mb-3 sm:mb-4"
@
click=
"
handleClickItem(item
)"
@
click=
"
jumpToArticleDetailPage(
{ type: item.type, id: item.id }
)"
>
<div
class=
"flex gap-3 justify-between"
>
<!--
<div
...
...
@@ -26,7 +26,7 @@
</h2>
<!-- 内容摘要 -->
<div
class=
"my-2 space-y-1"
>
<div
v-if=
"!item.content?.includes('
</
')"
class
="my-2
space-y-1"
>
<p
class=
"text-gray-600 text-sm sm:text-base leading-relaxed line-clamp-1 break-all"
>
...
...
@@ -38,6 +38,19 @@
<div
class=
"flex flex-wrap items-center gap-2 sm:gap-4 text-gray-500 text-xs sm:text-sm"
>
<!-- 发布人名称和头像 -->
<div
class=
"flex items-center gap-2"
>
<el-avatar
:size=
"24"
:src=
"item.showAvatar"
/>
<span
class=
"text-sm text-gray-500"
>
{{
item
.
showName
}}
</span>
</div>
<!-- 时间 -->
<span
class=
"text-gray-500 font-medium ml-auto sm:ml-0"
>
<span
class=
"hidden sm:inline"
>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</span>
<!-- 分隔符 -->
<div
class=
"hidden sm:block w-1 h-1 bg-gray-300 rounded-full"
></div>
<div
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
>
<el-icon
class=
"text-sm"
><View
/></el-icon>
<span
class=
"font-medium"
>
{{
item
.
viewCount
}}
</span>
...
...
@@ -50,20 +63,6 @@
<el-icon
class=
"text-sm"
><Star
/></el-icon>
<span
class=
"font-medium"
>
{{
item
.
collectionCount
}}
</span>
</div>
<!-- 分隔符 -->
<div
class=
"hidden sm:block w-1 h-1 bg-gray-300 rounded-full"
></div>
<!-- 发布人名称和头像 -->
<div
class=
"flex items-center gap-2"
>
<el-avatar
:size=
"24"
:src=
"item.showAvatar"
/>
<span
class=
"text-sm text-gray-500"
>
{{
item
.
showName
}}
</span>
</div>
<!-- 时间 -->
<span
class=
"text-gray-500 font-medium ml-auto sm:ml-0"
>
<span
class=
"hidden sm:inline"
>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</span>
</div>
</div>
...
...
@@ -106,8 +105,9 @@
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
async (e) =>
{
await goToPage(e)
handleBackTop()
}
"
@size-change="changePageSize"
...
...
@@ -128,10 +128,11 @@
<
script
setup
lang=
"ts"
>
import
{
usePageSearch
}
from
'@/hooks'
import
{
getArticleList
,
type
ArticleItemDto
}
from
'@/api'
import
{
TABS_REF_KEY
,
ArticleTypeEnum
}
from
'@/constants'
import
{
getArticleList
}
from
'@/api'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
useScrollTop
}
from
'@/hooks'
import
dayjs
from
'dayjs'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
{
list
,
total
,
searchParams
,
loading
,
goToPage
,
changePageSize
,
refresh
}
=
usePageSearch
(
getArticleList
,
...
...
@@ -146,14 +147,6 @@ const tabsRef = inject(TABS_REF_KEY)
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
tabsRef
!
)
const
handleClickItem
=
(
item
:
ArticleItemDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
id
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
id
}
`
)
}
}
defineExpose
({
refresh
:
(
sortLogic
?:
number
)
=>
{
console
.
log
(
'sortLogic'
,
sortLogic
)
...
...
src/views/homePage/homeTab/components/videoList.vue
View file @
cfe3bd46
...
...
@@ -6,12 +6,17 @@
<div
class=
"flex items-center justify-between pb-4"
>
<!-- 左侧 Tabs -->
<div
class=
"flex items-center space-x-1"
>
<div
v-for=
"tab in tabs"
:key=
"tab.sortLogic"
:class=
"[
<div
v-for=
"tab in tabs"
:key=
"tab.sortLogic"
:class=
"[
'px-6 py-2 rounded-full text-sm font-medium cursor-pointer transition-all duration-200',
searchParams.sortLogic === tab.sortLogic
? 'bg-orange-400 text-white shadow-md'
: 'text-gray-600 hover:text-orange-500 hover:bg-orange-50',
]"
@
click=
"toggleTab(tab.sortLogic)"
>
]"
@
click=
"toggleTab(tab.sortLogic)"
>
{{
tab
.
label
}}
</div>
</div>
...
...
@@ -24,12 +29,19 @@
<!-- 前三个特殊布局 -->
<div
class=
"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"
>
<!-- 第一个视频 - 占据两列 -->
<div
v-show=
"list.length >= 1"
@
click=
"openDetailPage(`/videoDetail/$
{list[0]?.id}`)"
class="lg:col-span-2 group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-xl transition-all duration-500 cursor-pointer">
<div
v-show=
"list.length >= 1"
@
click=
"jumpToArticleDetailPage(
{ type: ArticleTypeEnum.VIDEO, id: list[0]?.id })"
class="lg:col-span-2 group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-xl transition-all duration-500 cursor-pointer"
>
<div
class=
"relative overflow-hidden mb-2"
>
<img
:src=
"list[0]?.faceUrl"
class=
"w-full h-90 object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"
></div>
<img
:src=
"list[0]?.faceUrl"
class=
"w-full h-90 object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"
></div>
<!-- 标签和数据 -->
<!--
<div
...
...
@@ -37,29 +49,38 @@
>
🔥 推荐
</div>
-->
<div
v-if=
"list[0]?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<div
v-if=
"list[0]?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<div
class=
"absolute bottom-4 right-4 bg-black/80 backdrop-blur-sm text-white px-3 py-1.5 rounded-lg text-sm"
>
class=
"absolute bottom-4 right-4 bg-black/80 backdrop-blur-sm text-white px-3 py-1.5 rounded-lg text-sm"
>
{{
list
[
0
]?.
videoDuration
}}
</div>
<div
class=
"absolute bottom-4 left-4 flex gap-4 text-white"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<View
/>
</el-icon>
<span>
{{
list
[
0
]?.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<ChatDotRound
/>
</el-icon>
<span>
{{
list
[
0
]?.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<Star
/>
</el-icon>
...
...
@@ -80,7 +101,9 @@
</div>
<div
class=
"p-6"
>
<h3
class=
"font-bold text-2xl mb-3 text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-1"
>
<h3
class=
"font-bold text-xl mb-3 text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
list
[
0
]?.
title
}}
</h3>
<h2
class=
"text-gray-600 mb-4 line-clamp-1 leading-relaxed text-xl"
>
...
...
@@ -88,7 +111,11 @@
</h2>
<div
class=
"flex items-center justify-between text-gray-500 text-base"
>
<div
class=
"flex items-center gap-2"
>
<img
:src=
"list[0]?.showAvatar"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/>
<img
:src=
"list[0]?.showAvatar"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/>
<span
class=
"font-medium"
>
{{
list
[
0
]?.
showName
}}
</span>
</div>
<span
class=
"text-base px-2 py-1 rounded-full"
>
{{
...
...
@@ -100,40 +127,54 @@
<!-- 右侧两个视频 -->
<div
class=
"flex flex-col gap-6"
>
<div
v-for=
"(item, index) in list.slice(1, 3)"
:key=
"index"
<div
v-for=
"(item, index) in list.slice(1, 3)"
:key=
"index"
class=
"group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
@
click=
"openDetailPage(`/videoDetail/$
{item?.id}`)">
@
click=
"jumpToArticleDetailPage(
{ type: ArticleTypeEnum.VIDEO, id: item?.id })"
>
<div
class=
"relative overflow-hidden"
>
<img
:src=
"item?.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<img
:src=
"item?.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<div
v-if=
"item?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<div
v-if=
"item?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
item
?.
videoDuration
}}
</div>
<!-- 数据 -->
<div
class=
"absolute bottom-3 left-3 flex gap-3 text-white text-xs"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<View
/>
</el-icon>
<span>
{{
item
?.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<ChatDotRound
/>
</el-icon>
<span>
{{
item
?.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<Star
/>
</el-icon>
...
...
@@ -153,7 +194,9 @@
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
item
?.
title
}}
</h3>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
...
...
@@ -174,11 +217,17 @@
<!-- 剩余视频 - 标准网格 -->
<div
v-show=
"list.length > 3"
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
@
click=
"openDetailPage(`/videoDetail/$
{item.id}`)" v-for="item in list.slice(3)" :key="item.id"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer">
<div
@
click=
"jumpToArticleDetailPage(
{ type: ArticleTypeEnum.VIDEO, id: item.id })"
v-for="item in list.slice(3)"
:key="item.id"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
>
<div
class=
"relative overflow-hidden"
>
<img
:src=
"item.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<img
:src=
"item.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<!-- 标签 -->
...
...
@@ -187,33 +236,42 @@
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
item
.
videoDuration
}}
</div>
<!-- 数据 -->
<div
class=
"absolute bottom-3 left-3 flex gap-3 text-white text-xs"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<View
/>
</el-icon>
<span>
{{
item
.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<ChatDotRound
/>
</el-icon>
<span>
{{
item
.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<Star
/>
</el-icon>
...
...
@@ -233,7 +291,9 @@
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
item
?.
title
}}
</h3>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
...
...
@@ -253,11 +313,17 @@
<!-- 其他页面 - 标准3列网格 -->
<div
v-show=
"searchParams.current !== 1"
>
<div
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
@
click=
"openDetailPage(`/videoDetail/$
{item.id}`)" v-for="item in list" :key="item.id"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer">
<div
@
click=
"jumpToArticleDetailPage(
{ type: ArticleTypeEnum.VIDEO, id: item.id })"
v-for="item in list"
:key="item.id"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
>
<div
class=
"relative overflow-hidden"
>
<img
:src=
"item.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<img
:src=
"item.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<!-- 标签 -->
...
...
@@ -266,33 +332,42 @@
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
item
?.
videoDuration
}}
</div>
<!-- 数据 -->
<div
class=
"absolute bottom-3 left-3 flex gap-3 text-white text-xs"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<View
/>
</el-icon>
<span>
{{
item
.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<ChatDotRound
/>
</el-icon>
<span>
{{
item
.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
>
<Star
/>
</el-icon>
...
...
@@ -312,7 +387,9 @@
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
item
.
title
}}
</h3>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
...
...
@@ -340,14 +417,24 @@
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[9, 24, 36, 48]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
; (handleBackTop(), goToPage(e))
<div
class=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[9, 24, 36, 48]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
async (e) =>
{
await goToPage(e)
handleBackTop()
}
" @size-change="changePageSize" />
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
...
...
@@ -368,6 +455,7 @@ import { ArticleTypeEnum, TABS_REF_KEY } from '@/constants'
import
{
usePageSearch
}
from
'@/hooks'
import
{
getArticleList
}
from
'@/api'
import
dayjs
from
'dayjs'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
tabsRef
=
inject
(
TABS_REF_KEY
)
...
...
@@ -391,10 +479,6 @@ const toggleTab = (sortLogic: number) => {
refresh
()
}
const
openDetailPage
=
(
path
:
string
)
=>
{
window
.
open
(
path
)
}
defineExpose
({
refresh
:
()
=>
{
refresh
()
...
...
src/views/homePage/yaTab/components/columnList.vue
View file @
cfe3bd46
...
...
@@ -37,7 +37,7 @@
v-for=
"i in item.yaColumnVoList"
:key=
"i.articleId"
class=
"group cursor-pointer"
@
click=
"
openArticleDetail(i.articleId
)"
@
click=
"
jumpToArticleDetailPage(
{ type: i.type, id: i.articleId }
)"
>
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<img
...
...
@@ -133,9 +133,10 @@
import
{
View
,
ChatDotRound
,
Star
}
from
'@element-plus/icons-vue'
import
{
getColumnList
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
ArticleTypeEnum
,
TABS_REF_KEY
}
from
'@/constants'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
dayjs
from
'dayjs'
import
{
useRouter
}
from
'vue-router'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
handleBackTop
,
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
...
...
@@ -153,10 +154,6 @@ defineExpose({
refresh
()
},
})
const
openArticleDetail
=
(
id
:
number
)
=>
{
window
.
open
(
`/articleDetail/
${
id
}
`
)
}
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/interviewList.vue
View file @
cfe3bd46
...
...
@@ -35,7 +35,7 @@
v-for=
"i in item.yaColumnVoList"
:key=
"i.articleId"
class=
"group cursor-pointer"
@
click=
"
openArticleDetail(i.articleId
)"
@
click=
"
jumpToArticleDetailPage(
{ type: i.type, id: i.articleId }
)"
>
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<img
...
...
@@ -107,8 +107,9 @@
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
async (e) =>
{
await goToPage(e)
handleBackTop()
}
"
@size-change="changePageSize"
...
...
@@ -134,6 +135,7 @@ import { usePageSearch, useScrollTop } from '@/hooks'
import
{
TABS_REF_KEY
,
ArticleTypeEnum
}
from
'@/constants'
import
{
useRouter
}
from
'vue-router'
import
dayjs
from
'dayjs'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
...
...
@@ -151,10 +153,6 @@ defineExpose({
refresh
()
},
})
const
openArticleDetail
=
(
id
:
number
)
=>
{
window
.
open
(
`/articleDetail/
${
id
}
`
)
}
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/practiceList.vue
View file @
cfe3bd46
...
...
@@ -57,7 +57,7 @@
</div>
<div
class=
"divide-y bg-#fff"
>
<div
@
click=
"
openArticleDetail(item.id
)"
@
click=
"
jumpToArticleDetailPage(
{ type: ArticleTypeEnum.PRACTICE, id: item.id }
)"
v-for="item in list"
:key="item.id"
class="p-4 hover:bg-gray-50 transition-colors cursor-pointer pl-8"
...
...
@@ -141,8 +141,9 @@
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
async (e) =>
{
await goToPage(e)
handleBackTop()
}
"
@size-change="changePageSize"
...
...
@@ -164,6 +165,7 @@ import { TABS_REF_KEY } from '@/constants'
import
{
useTagsStore
}
from
'@/stores/tags'
import
PublishPractice
from
'@/components/common/PublishBox/index.vue'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
tagsStore
=
useTagsStore
()
const
{
tagList
}
=
storeToRefs
(
tagsStore
)
...
...
@@ -215,10 +217,6 @@ defineExpose({
refresh
()
},
})
const
openArticleDetail
=
(
id
:
number
)
=>
{
window
.
open
(
`/articleDetail/
${
id
}
`
)
}
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/videoList.vue
View file @
cfe3bd46
...
...
@@ -31,7 +31,7 @@
</div>
<div
v-loading=
"loadingMore"
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-6"
>
<div
@
click=
"
handleOpenPage(item.articleId)
"
@
click=
"
jumpToArticleDetailPage(
{ type: ArticleTypeEnum.VIDEO, id: item.articleId })
"
v-for="item in listMore"
:key="item.articleId"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
...
...
@@ -79,16 +79,6 @@
<span>
{{
item
.
replyCount
}}
</span>
</div>
</div>
<!-- 播放按钮 -->
<!--
<div
class=
"absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
<div
class=
"bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center shadow-xl transform scale-90 group-hover:scale-100 transition-transform duration-300"
>
<el-icon
size=
"50"
color=
"#333"
><VideoPlay
/></el-icon>
</div>
</div>
-->
</div>
<div
class=
"p-4"
>
...
...
@@ -145,10 +135,77 @@
<div
v-for=
"i in item.yaColumnVoList"
:key=
"i.articleId"
class=
"group cursor-pointer"
@
click=
"handleOpenPage(i.articleId)"
class=
"group cursor-pointer rounded-lg overflow-hidden"
@
click=
"jumpToArticleDetailPage(
{ type: i.type, id: i.articleId })"
>
<div
class=
"relative overflow-hidden"
>
<img
:src=
"i.faceUrl"
class=
"w-full h-44 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<!-- 标签 -->
<!--
<div
class=
"absolute top-3 left-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-2.5 py-1 rounded-full text-xs font-semibold"
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
v-if=
"i.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
i
.
videoDuration
}}
</div>
<!-- 数据 -->
<div
class=
"absolute bottom-3 left-3 flex gap-3 text-white text-xs"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<el-icon
class=
"text-sm"
><View
/></el-icon>
<span>
{{
i
.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
><ChatDotRound
/></el-icon>
<span>
{{
i
.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<el-icon
class=
"text-sm"
><Star
/></el-icon>
<span>
{{
i
.
replyCount
}}
</span>
</div>
</div>
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
i
.
title
}}
</h3>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
<div
class=
"flex items-center gap-2 max-w-55%"
>
<img
:src=
"i.showAvatar"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/>
<el-tooltip
:content=
"i.showName"
placement=
"top"
>
<span
class=
"font-medium"
>
{{
i
.
showName
}}
</span>
</el-tooltip>
</div>
<span>
{{
dayjs
(
i
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</div>
</div>
<!--
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<img
:src=
"i.faceUrl"
class=
"w-full aspect-[5/3] object-cover group-hover:scale-105 transition-transform duration-300"
...
...
@@ -189,7 +246,7 @@
</span>
</div>
<span>
{{
dayjs
(
i
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</div>
</div>
-->
</div>
</div>
<div
v-else
class=
"flex items-center justify-center h-48"
>
...
...
@@ -218,8 +275,9 @@
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
async (e) =>
{
await goToPage(e)
handleBackTop()
}
"
@size-change="changePageSize"
...
...
@@ -242,9 +300,10 @@
import
{
View
,
ChatDotRound
,
Star
}
from
'@element-plus/icons-vue'
import
{
getVideoList
,
getVideoListViewMore
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
TABS_REF_KEY
,
ArticleTypeEnum
}
from
'@/constants'
import
{
useRouter
}
from
'vue-router'
import
dayjs
from
'dayjs'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
...
...
@@ -283,10 +342,6 @@ const changeSort = (sortLogic: number) => {
refreshMore
()
}
const
handleOpenPage
=
(
articleId
:
number
)
=>
{
window
.
open
(
`/videoDetail/
${
articleId
}
`
)
}
defineExpose
({
refresh
:
()
=>
{
// searchParams.value.current = 0
...
...
src/views/publishLongArticle/index.vue
View file @
cfe3bd46
<
template
>
<div
class=
"min-h-screen bg-[#fff]
p-6
font-sans"
>
<div
class=
"min-h-screen bg-[#fff] font-sans"
>
<div
class=
"max-w-7xl mx-auto"
>
<!-- 顶部面包屑或标题(可选) -->
<el-form
:model=
"form"
label-position=
"top"
class=
"grid grid-cols-12 gap-6 items-start"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-position=
"top"
class=
"grid grid-cols-12 gap-6 items-start"
>
<!-- 左侧:沉浸式创作区 (占 9 列) -->
<div
class=
"col-span-12 lg:col-span-9 space-y-6"
>
<div
class=
"bg-white rounded-xl shadow-sm border border-gray-100 p-8 min-h-[80vh]"
>
<!-- 标题输入:模拟大标题风格,去掉边框 -->
<el-form-item
prop=
"title"
class=
"
mb-6 !border-b !border-gray-100 pb-2
"
>
<el-form-item
prop=
"title"
class=
"
!border-b !border-gray-100
"
>
<el-input
v-model=
"form.title"
placeholder=
"请输入文章标题..."
class=
"title-input"
:maxlength=
"100"
show-word-limit
type=
"textarea"
:autosize=
"
{ minRows: 1, maxRows: 2 }"
resize=
"none"
/>
</el-form-item>
<!-- 富文本编辑器 -->
<div
class=
"editor-container"
>
<WangEditor
v-model=
"form.content"
style=
"height: 600px"
/>
<el-form-item
prop=
"content"
class=
"!border-b !border-gray-100"
>
<WangEditor
v-model=
"form.content"
style=
"height: 800px"
/>
</el-form-item>
</div>
</div>
</div>
<!-- 右侧:配置侧边栏 (占 3 列,吸顶) -->
<div
class=
"col-span-12 lg:col-span-3 space-y-4
sticky top-4
"
>
<div
class=
"col-span-12 lg:col-span-3 space-y-4"
>
<!-- 卡片1:基础设置 -->
<div
class=
"bg-white rounded-xl shadow-sm border border-gray-100 p-5"
>
<div
class=
"font-bold text-gray-800 mb-4 flex items-center gap-2"
>
...
...
@@ -39,10 +45,14 @@
<!-- 文章类型 -->
<el-form-item
label=
"文章类型"
prop=
"type"
>
<el-radio-group
v-model=
"form.type"
class=
"w-full grid grid-cols-3 gap-2"
>
<el-radio-button
:value=
"ArticleTypeEnum.POST"
>
帖子
</el-radio-button>
<el-radio-button
:value=
"ArticleTypeEnum.COLUMN"
>
专栏
</el-radio-button>
<el-radio-button
:value=
"ArticleTypeEnum.INTERVIEW"
>
专访
</el-radio-button>
<el-radio-group
v-model=
"form.type"
class=
"w-full grid grid-cols-3 gap-2"
fill=
"#3b82f6"
>
<el-radio-button
:value=
"ArticleTypeEnum.POST"
>
{{
articleTypeListOptions
.
find
((
item
)
=>
item
.
value
===
type
)?.
label
}}
</el-radio-button>
</el-radio-group>
</el-form-item>
...
...
@@ -170,7 +180,15 @@
</div>
<div
class=
"mb-6 flex items-center justify-end w-full"
>
<div
class=
"flex gap-3"
>
<el-button
type=
"primary"
round
class=
"!px-8 w-full!"
@
click=
"handlePublish"
>
<el-button
class=
"rounded-lg"
@
click=
"handleClosed"
>
取消
</el-button>
<el-button
class=
"rounded-lg"
@
click=
"handleSubmit(ReleaseStatusTypeEnum.DRAFT)"
>
存草稿
</el-button>
<el-button
type=
"primary"
@
click=
"handleSubmit(ReleaseStatusTypeEnum.PUBLISH)"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
>
发布
</el-button>
</div>
...
...
@@ -182,8 +200,15 @@
</template>
<
script
setup
lang=
"ts"
>
import
type
{
FormInstance
}
from
'element-plus'
import
WangEditor
from
'@/components/common/WangEditor/index.vue'
import
{
ArticleTypeEnum
,
SendTypeEnum
,
BooleanFlag
,
ReleaseStatusTypeEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
,
SendTypeEnum
,
BooleanFlag
,
ReleaseStatusTypeEnum
,
articleTypeListOptions
,
}
from
'@/constants'
import
UploadFile
from
'@/components/common/UploadFile/index.vue'
import
{
useResetData
}
from
'@/hooks'
import
{
useColumnStore
}
from
'@/stores/column'
...
...
@@ -192,9 +217,13 @@ import { addOrUpdateArticle } from '@/api'
// ... (逻辑部分保持不变,直接复用您的即可)
const
columnStore
=
useColumnStore
()
const
{
columnList
}
=
storeToRefs
(
columnStore
)
const
router
=
useRouter
()
const
route
=
useRoute
()
const
type
=
route
.
params
.
type
as
ArticleTypeEnum
const
formRef
=
useTemplateRef
<
FormInstance
>
(
'formRef'
)
const
[
form
,
resetForm
]
=
useResetData
({
articleType
:
ArticleTypeEnum
.
POST
,
type
:
type
,
title
:
''
,
content
:
''
,
faceUrl
:
''
,
...
...
@@ -208,13 +237,28 @@ const [form, resetForm] = useResetData({
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
})
const
rules
=
{
title
:
[{
required
:
true
,
message
:
'请输入文章标题'
,
trigger
:
'blur'
}],
content
:
[{
required
:
true
,
message
:
'请输入文章内容'
,
trigger
:
'blur'
}],
type
:
[{
required
:
true
,
message
:
'请选择文章类型'
,
trigger
:
'blur'
}],
faceUrl
:
[{
required
:
true
,
message
:
'请上传封面图'
,
trigger
:
'blur'
}],
sendType
:
[{
required
:
true
,
message
:
'请选择发布类型'
,
trigger
:
'blur'
}],
sendTime
:
[{
required
:
true
,
message
:
'请选择发布时间'
,
trigger
:
'blur'
}],
releaseStatus
:
[{
required
:
true
,
message
:
'请选择发布状态'
,
trigger
:
'blur'
}],
}
const
filterTagsFn
=
(
allTags
:
any
[])
=>
{
return
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
form
.
value
.
mainTagId
))
}
const
handlePublish
=
async
()
=>
{
const
handleSubmit
=
async
()
=>
{
try
{
await
formRef
.
value
.
validate
()
const
res
=
await
addOrUpdateArticle
(
form
.
value
)
console
.
log
(
res
)
}
catch
(
error
)
{
console
.
log
(
error
)
}
}
</
script
>
...
...
src/views/questionDetail/index.vue
View file @
cfe3bd46
<
template
>
<div
class=
"min-h-screen pb-10 font-sans text-slate-800 flex justify-center"
>
<div
class=
"w-full
max-w-[1000px]
space-y-4"
>
<div
class=
"min-h-screen pb-10 font-sans text-slate-800 flex justify-center
px-20
"
>
<div
class=
"w-full space-y-4"
>
<!-- 1. 问题卡片 -->
<div
class=
"bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100"
...
...
@@ -22,17 +22,17 @@
</h1>
<!-- 描述:次要信息,深灰 -->
<div
class=
"text-
slate-700 leading-relaxed text-[15px] mb-6
"
>
<
span
:class=
"
{ 'line-clamp-3': !isExpandDesc }
">
<div
class=
"text-
gray-600 text-base leading-relaxed transition-all duration-300
"
>
<
div
:class=
"
{ 'line-clamp-3': !isExpand }" ref="questionContentRef
">
{{
questionDetail
.
content
}}
</
span
>
</
div
>
<button
v-if=
"
questionDetail.content && questionDetail.content.length > 100
"
class=
"text-blue-500 text-sm font-medium hover:text-blue-600 mt-1 flex items-center gap-0.5"
@
click=
"isExpand
Desc = !isExpandDesc
"
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
Desc
?
'收起'
:
'显示全部'
}}
<el-icon
:class=
"
{ 'rotate-180': isExpand
Desc
}" class="transition-transform"
{{
isExpand
?
'收起'
:
'显示全部'
}}
<el-icon
:class=
"
{ 'rotate-180': isExpand }" class="transition-transform"
>
<CaretBottom
/></el-icon>
</button>
...
...
@@ -42,7 +42,8 @@
<div
class=
"flex items-center justify-between mt-4"
>
<div
class=
"flex gap-3"
>
<button
class=
"px-5 py-1.5 border border-blue-500 text-blue-500 hover:bg-blue-50 rounded-[4px] text-sm font-medium transition-colors flex items-center gap-1 cursor-pointer"
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><EditPen
/></el-icon>
写回答
...
...
@@ -81,7 +82,7 @@
<!-- 2. 列表控制栏 -->
<div
class=
"bg-white rounded-
t-
lg border-b border-slate-100 p-6 flex justify-between items-center shadow-sm"
class=
"bg-white rounded-lg border-b border-slate-100 p-6 flex justify-between items-center shadow-sm"
>
<h3
class=
"font-bold text-slate-800 text-base"
>
{{
total
}}
个回答
</h3>
<div
class=
"flex text-sm text-slate-500 bg-slate-50 rounded p-0.5"
>
...
...
@@ -89,6 +90,7 @@
size=
"small"
v-model=
"searchParams.sortType"
@
change=
"(val) => changeSortType(val as number)"
fill=
"#3b82f6"
>
<el-radio-button
label=
"最新"
:value=
"2"
/>
<el-radio-button
label=
"最多评论"
:value=
"1"
/>
...
...
@@ -135,20 +137,20 @@
>
优秀回答
</span
>
-->
</div>
<div
class=
"text-xs text-slate-400 mt-0.5 max-w-md truncate"
>
<
!--
<
div
class=
"text-xs text-slate-400 mt-0.5 max-w-md truncate"
>
{{
answer
.
description
||
'暂无简介'
}}
</div>
</div>
-->
</div>
</div>
<!-- 赞同票数 (微小的灰色文字,增加信息密度) -->
<div
class=
"text-xs text-slate-400 mb-2"
>
{{
answer
.
p
ra
iseCount
||
0
}}
人赞同了该回答
{{
answer
.
p
ostPr
iseCount
||
0
}}
人赞同了该回答
</div>
<!-- 正文 -->
<!-- 正文
换行
-->
<div
class=
"text-slate-800 leading-7 text-[15px] mb-4 rich-text-content"
class=
"text-slate-800 leading-7 text-[15px] mb-4 rich-text-content
break-all
"
v-html=
"answer.content"
></div>
...
...
@@ -167,7 +169,8 @@
>
<el-icon><CaretTop
/></el-icon>
<span
>
{{
answer
.
hasPraise
?
'已赞同'
:
'赞同'
}}
{{
answer
.
praiseCount
||
''
}}
</span
>
{{
answer
.
hasPraise
?
'已赞同'
:
'赞同'
}}
{{
answer
.
postPriseCount
||
''
}}
</span
>
</button>
<!--
<button
...
...
@@ -182,7 +185,7 @@
@
click=
"handleComment(answer, index)"
>
<el-icon
class=
"text-base"
><ChatRound
/></el-icon>
<span
:class=
"
{ 'text-slate-800 font-medium': answer.showComment }"
>
<span>
{{
answer
.
childrenNum
?
`${answer.childrenNum
}
条评论`
:
'添加评论'
}}
<
/span
>
<
/button
>
...
...
@@ -204,7 +207,6 @@
:
immediate
=
"false"
:
isQuestion
=
"true"
:
commentId
=
"answer.id"
@
commentSuccess
=
"() => handleCommentSuccess(answer)"
/>
<
/div
>
<
/Transition
>
...
...
@@ -212,18 +214,18 @@
<
/div
>
<!--
底部加载更多
-->
<
div
class
=
"py-3
flex justify-center bg-#fff
"
>
<
div
class
=
"py-3
px-6 flex justify-end bg-#fff rounded-lg
"
>
<
el
-
pagination
v
-
model
:
current
-
page
=
"searchParams.current"
v
-
model
:
page
-
size
=
"searchParams.size"
:
total
=
"total"
layout
=
"prev, pager, next,total"
background
small
@
current
-
change
=
"goToPage"
/>
<
/div
>
<
/div
>
<
CommentDialog
ref
=
"commentDialogRef"
@
commentSuccess
=
"refresh"
/>
<
/div
>
<
/template
>
<
script
setup
lang
=
"ts"
>
...
...
@@ -234,9 +236,10 @@ import {
addOrCanceArticlelCollect
,
addOrCancelCommentLike
,
}
from
'@/api'
import
type
{
ArticleItemDto
}
from
'@/api
/types
'
import
type
{
ArticleItemDto
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
Comment
from
'@/components/common/Comment/index.vue'
import
CommentDialog
from
'@/components/common/CommentDialog/index.vue'
import
dayjs
from
'dayjs'
const
route
=
useRoute
()
...
...
@@ -244,11 +247,28 @@ const questionId = Number(route.params.id)
const
commentRefList
=
ref
<
InstanceType
<
typeof
Comment
>
[]
>
([])
const
questionDetail
=
ref
<
ArticleItemDto
>
({
}
as
ArticleItemDto
)
const
commentDialogRef
=
useTemplateRef
<
typeof
CommentDialog
>
(
'commentDialogRef'
)
const
questionContentRef
=
useTemplateRef
<
HTMLElement
>
(
'questionContentRef'
)
const
isExpand
=
ref
(
false
)
// 检测当前是否超过三行 要用到具体的dom
const
isOverThreeLine
=
ref
(
false
)
const
checkIsOverThreeLine
=
()
=>
{
if
(
!
questionContentRef
.
value
||
!
questionDetail
.
value
.
content
)
return
false
const
lineHeight
=
parseFloat
(
getComputedStyle
(
questionContentRef
.
value
).
lineHeight
)
const
height
=
questionContentRef
.
value
!
.
scrollHeight
const
maxHeight
=
lineHeight
*
3
console
.
log
(
maxHeight
,
height
)
return
height
>
maxHeight
}
const
getQuestionDetail
=
async
()
=>
{
const
{
data
}
=
await
getArticleDetail
(
questionId
)
questionDetail
.
value
=
data
console
.
log
(
questionDetail
.
value
)
await
nextTick
()
isOverThreeLine
.
value
=
checkIsOverThreeLine
()
}
const
{
list
,
total
,
searchParams
,
goToPage
,
refresh
}
=
usePageSearch
(
getCommentList
,
{
...
...
@@ -276,7 +296,7 @@ const handleLikeArticle = async () => {
questionDetail
.
value
.
praiseCount
=
questionDetail
.
value
.
hasPraised
?
questionDetail
.
value
.
praiseCount
+
1
:
questionDetail
.
value
.
praiseCount
-
1
ElMessage
.
success
(
`${questionDetail.value.hasPraised ? '点赞
成功' : '取消点赞成功
'
}
`
)
ElMessage
.
success
(
`${questionDetail.value.hasPraised ? '点赞
该问题' : '取消点赞该问题
'
}
`
)
}
const
handleCollectArticle
=
async
()
=>
{
...
...
@@ -290,9 +310,9 @@ const handleCollectArticle = async () => {
const
handleLikeAnswer
=
async
(
answer
:
any
)
=>
{
await
addOrCancelCommentLike
(
answer
.
id
)
ElMessage
.
success
(
`${answer.hasPraise ? '点赞成功' : '取消点赞成功'
}
`
)
answer
.
hasPraise
=
!
answer
.
hasPraise
answer
.
praiseCount
=
answer
.
hasPraise
?
answer
.
praiseCount
+
1
:
answer
.
praiseCount
-
1
answer
.
postPriseCount
=
answer
.
hasPraise
?
answer
.
postPriseCount
+
1
:
answer
.
postPriseCount
-
1
ElMessage
.
success
(
`${answer.hasPraise ? '点赞该回答' : '取消点赞该回答'
}
`
)
}
const
handleComment
=
(
answer
:
any
,
index
:
number
)
=>
{
...
...
@@ -300,6 +320,10 @@ const handleComment = (answer: any, index: number) => {
answer
.
showComment
=
!
answer
.
showComment
}
const
openCommentDialog
=
()
=>
{
commentDialogRef
.
value
?.
open
(
questionDetail
.
value
.
id
)
}
onMounted
(()
=>
{
getQuestionDetail
()
}
)
...
...
src/views/userPage/components/SelfComplaint.vue
View file @
cfe3bd46
...
...
@@ -48,7 +48,11 @@
>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
<span
class=
"flex items-center px-2"
>
举报帖子
</span>
<el-link
type=
"primary"
:underline=
"false"
@
click=
"handleToDetail(item)"
>
<el-link
type=
"primary"
:underline=
"false"
@
click=
"jumpToArticleDetailPage(
{ type: item.type, id: item.articleId })"
>
{{
item
.
title
}}
</el-link>
</span>
...
...
@@ -118,9 +122,10 @@ import { getComplaintList, auditComplaint } from '@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
auditTypeListOptions
}
from
'@/constants/options'
import
dayjs
from
'dayjs'
import
{
AuditStatusEnum
,
ArticleTypeEnum
}
from
'@/constants'
import
type
{
AuditComplaintDto
,
ComplaintListItemDto
}
from
'@/api'
import
{
AuditStatusEnum
}
from
'@/constants'
import
type
{
AuditComplaintDto
}
from
'@/api'
import
type
{
TabPaneName
}
from
'element-plus'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
toggleTab
=
(
key
:
TabPaneName
)
=>
{
searchParams
.
value
.
status
=
key
as
AuditStatusEnum
...
...
@@ -154,12 +159,4 @@ const handleAudit = async (data: AuditComplaintDto) => {
refresh
()
}
const
handleToDetail
=
(
item
:
ComplaintListItemDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
articleId
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
articleId
}
`
)
}
}
</
script
>
src/views/userPage/components/selfAnswer.vue
View file @
cfe3bd46
...
...
@@ -36,7 +36,12 @@
</div>
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type=
"primary"
link
@
click=
"openArticleDetail(item.id)"
>
去回复
</el-button>
<el-button
type=
"primary"
link
@
click=
"jumpToArticleDetailPage(
{ type: 'question', id: item.id })"
>去回复
</el-button
>
</div>
</div>
</div>
...
...
@@ -67,6 +72,7 @@ import { Document, Refresh } from '@element-plus/icons-vue'
import
{
answerQuestionPage
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
dayjs
from
'dayjs'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
{
list
,
loading
,
searchParams
,
total
,
refresh
,
goToPage
,
changePageSize
}
=
usePageSearch
(
answerQuestionPage
,
...
...
@@ -77,8 +83,4 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize }
onActivated
(()
=>
{
refresh
()
})
const
openArticleDetail
=
(
id
:
number
)
=>
{
window
.
open
(
`/articleDetail/
${
id
}
`
)
}
</
script
>
src/views/userPage/components/selfCollect.vue
View file @
cfe3bd46
...
...
@@ -47,7 +47,12 @@
</div>
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type=
"primary"
link
@
click=
"handleView(item)"
>
查看
</el-button>
<el-button
type=
"primary"
link
@
click=
"jumpToArticleDetailPage(
{ type: item.type, id: item.id })"
>查看
</el-button
>
</div>
</div>
</div>
...
...
@@ -85,8 +90,8 @@ import {
import
dayjs
from
'dayjs'
import
{
ArticleTypeEnum
}
from
'@/constants/enums'
import
type
{
TabPaneName
}
from
'element-plus'
import
type
{
SelfCollectDetailDto
}
from
'@/api'
import
{
IS_REAL_KEY
}
from
'@/constants/symbolKey'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
isReal
=
inject
(
IS_REAL_KEY
)
...
...
@@ -111,14 +116,6 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize }
},
)
const
handleView
=
(
item
:
SelfCollectDetailDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
id
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
id
}
`
)
}
}
onActivated
(()
=>
{
searchParams
.
value
.
type
=
filterArticleType
.
value
[
0
]
!
.
value
refresh
()
...
...
src/views/userPage/components/selfComment.vue
View file @
cfe3bd46
...
...
@@ -61,7 +61,12 @@
<!-- Meta Info -->
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type=
"primary"
link
@
click=
"handleView(item)"
>
查看
</el-button>
<el-button
type=
"primary"
link
@
click=
"jumpToArticleDetailPage(
{ type: item.type, id: item.articleId })"
>查看
</el-button
>
<el-button
type=
"danger"
link
@
click=
"handleDelete(item.id)"
>
删除
</el-button>
</div>
</div>
...
...
@@ -99,9 +104,9 @@ import {
}
from
'@/constants/options'
import
dayjs
from
'dayjs'
import
{
CommentTypeEnum
,
ArticleTypeEnum
}
from
'@/constants/enums'
import
type
{
SelfCommentItemDto
}
from
'@/api'
import
type
{
TabPaneName
}
from
'element-plus'
import
{
IS_REAL_KEY
}
from
'@/constants/symbolKey'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
route
=
useRoute
()
const
isReal
=
inject
(
IS_REAL_KEY
)
...
...
@@ -140,14 +145,6 @@ const handleDelete = async (id: number) => {
refresh
()
}
const
handleView
=
(
item
:
SelfCommentItemDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
articleId
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
articleId
}
`
)
}
}
onActivated
(()
=>
{
if
(
route
.
query
.
type
)
{
searchParams
.
value
.
type
=
route
.
query
.
type
as
ArticleTypeEnum
...
...
src/views/userPage/components/selfPraise.vue
View file @
cfe3bd46
...
...
@@ -46,7 +46,12 @@
</div>
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type=
"primary"
link
@
click=
"handleView(item)"
>
查看
</el-button>
<el-button
type=
"primary"
link
@
click=
"jumpToArticleDetailPage(
{ type: item.type, id: item.id })"
>查看
</el-button
>
</div>
</div>
</div>
...
...
@@ -83,8 +88,8 @@ import {
}
from
'@/constants/options'
import
dayjs
from
'dayjs'
import
{
ArticleTypeEnum
}
from
'@/constants/enums'
import
type
{
SelfPraiseDetailDto
}
from
'@/api'
import
{
IS_REAL_KEY
}
from
'@/constants/symbolKey'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
isReal
=
inject
(
IS_REAL_KEY
)
...
...
@@ -114,14 +119,6 @@ onActivated(() => {
refresh
()
})
const
handleView
=
(
item
:
SelfPraiseDetailDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
id
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
id
}
`
)
}
}
defineExpose
({
refresh
:
()
=>
{
searchParams
.
value
.
type
=
filterArticleType
.
value
[
0
]
!
.
value
...
...
src/views/userPage/components/selfPublish.vue
View file @
cfe3bd46
...
...
@@ -47,7 +47,12 @@
</div>
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type=
"primary"
link
@
click=
"handleView(item)"
>
查看
</el-button>
<el-button
type=
"primary"
link
@
click=
"jumpToArticleDetailPage(
{ type: item.type, id: item.id })"
>查看
</el-button
>
<el-button
type=
"danger"
link
@
click=
"handleDelete(item.id)"
>
删除
</el-button>
</div>
</div>
...
...
@@ -84,9 +89,9 @@ import {
}
from
'@/constants/options'
import
dayjs
from
'dayjs'
import
{
ArticleTypeEnum
}
from
'@/constants/enums'
import
type
{
SelfPublishDetailDto
}
from
'@/api'
import
type
{
TabPaneName
}
from
'element-plus'
import
{
IS_REAL_KEY
}
from
'@/constants/symbolKey'
import
{
jumpToArticleDetailPage
}
from
'@/utils'
const
isReal
=
inject
(
IS_REAL_KEY
)
const
filterArticleType
=
computed
(()
=>
{
...
...
@@ -115,13 +120,6 @@ onActivated(() => {
refresh
()
})
const
handleView
=
(
item
:
SelfPublishDetailDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
window
.
open
(
`/videoDetail/
${
item
.
id
}
`
)
}
else
{
window
.
open
(
`/articleDetail/
${
item
.
id
}
`
)
}
}
const
handleDelete
=
async
(
articleId
:
number
)
=>
{
await
ElMessageBox
.
confirm
(
'确定删除该吗?'
,
'提示'
,
{
confirmButtonText
:
'确定'
,
...
...
src/views/userPage/index.vue
View file @
cfe3bd46
...
...
@@ -28,8 +28,15 @@
@
click=
"handleBackUser"
>
返回个人账号
</el-button
>
<!-- v-if="userInfo.isAdmin" 暂时不加权限 -->
<el-button
type=
"primary"
plain
size=
"small"
@
click=
"handleAdmin"
>
后台管理
</el-button>
<!-- 暂时不加权限 -->
<el-button
v-if=
"userInfo.isAdmin || userInfo.isOfficialAccount"
type=
"primary"
plain
size=
"small"
@
click=
"handleAdmin"
>
后台管理
</el-button
>
</div>
</div>
...
...
src/views/videoDetail/index.vue
View file @
cfe3bd46
...
...
@@ -39,7 +39,7 @@
<!-- 左侧:UP主信息 -->
<div
class=
"flex items-center gap-3"
>
<img
@
click=
"
router.push(`/otherUserPage/$
{videoDetail?.createUserId}/0`
)"
@
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"
/>
...
...
@@ -243,9 +243,9 @@ import type { ArticleItemDto } from '@/api/article/types'
import
Comment
from
'@/components/common/Comment/index.vue'
import
RewardDialog
from
'./components/rewardDialog.vue'
import
ActionMore
from
'@/components/common/ActionMore/index.vue'
import
{
jumpToUserHomePage
}
from
'@/utils'
const
route
=
useRoute
()
const
router
=
useRouter
()
const
videoId
=
route
.
params
.
id
as
string
// 视频详情
...
...
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