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
4a88ffa9
Commit
4a88ffa9
authored
Nov 20, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 wip: 继续完善页面
parent
21438b37
Show whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
1159 additions
and
887 deletions
+1159
-887
index.ts
src/api/article/index.ts
+85
-13
types.ts
src/api/article/types.ts
+102
-6
index.ts
src/api/column/index.ts
+3
-3
types.ts
src/api/column/types.ts
+1
-1
index.ts
src/api/common/index.ts
+20
-5
index.ts
src/api/interview/index.ts
+3
-3
types.ts
src/api/interview/types.ts
+1
-1
types.ts
src/api/user/types.ts
+1
-1
praise.svg
src/assets/svg/praise.svg
+2
-0
index.vue
src/components/common/UploadFile/index.vue
+5
-3
useScrollTop.tsx
src/hooks/useScrollTop.tsx
+1
-1
colnumForm.tsx
src/layoutCulture/components/colnumForm.tsx
+5
-4
interviewForm.tsx
src/layoutCulture/components/interviewForm.tsx
+4
-4
postForm.tsx
src/layoutCulture/components/postForm.tsx
+1
-1
index.ts
src/router/index.ts
+1
-1
column.ts
src/stores/column.ts
+4
-4
interview.ts
src/stores/interview.ts
+4
-4
recommendList.vue
src/views/homePage/homeTab/components/recommendList.vue
+12
-8
videoList.vue
src/views/homePage/homeTab/components/videoList.vue
+61
-51
index.vue
src/views/homePage/homeTab/index.vue
+10
-2
index.vue
src/views/homePage/index.vue
+37
-15
columnList.vue
src/views/homePage/yaTab/components/columnList.vue
+75
-57
interviewList.vue
src/views/homePage/yaTab/components/interviewList.vue
+72
-52
index.vue
src/views/homePage/yaTab/index.vue
+11
-44
actionButtons.vue
src/views/postDetail/components/actionButtons.vue
+34
-21
index.vue
src/views/postDetail/index.vue
+192
-363
index.vue
src/views/publishVideo/index.vue
+6
-5
index.vue
src/views/userPage/index.vue
+5
-2
index.vue
src/views/videoDetail/index.vue
+401
-212
No files found.
src/api/article/index.ts
View file @
4a88ffa9
...
...
@@ -3,11 +3,15 @@ import type {
AddOrUpdateArticleDto
,
ArticleItemDto
,
ArticleSearchParams
,
InterviewOptionDto
,
ColumnOptionDto
,
AddCommentDto
,
CommentItemDto
,
CommentSearchParams
,
InterviewItemDto
,
ColumnItemDto
,
}
from
'./types'
import
type
{
BackendServicePageResult
}
from
'@/utils/request/types'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
type
{
BackendServicePageResult
,
PageSearchParams
}
from
'@/utils/request/types'
// 文章相关的接口(帖子 视频 实践等)
...
...
@@ -44,32 +48,100 @@ export const getArticleDetail = (articleId: number | string) => {
}
/**
*
收藏或者取消收藏
*
获取专访列表选项 --不分页 用户新增时候选择
*/
export
const
addOrCancelCollect
=
(
data
:
{
articleId
:
number
|
string
;
type
:
ArticleTypeEnum
})
=>
{
export
const
getInterviewOptions
=
()
=>
{
return
service
.
request
<
InterviewOptionDto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=interview'
,
method
:
'POST'
,
})
}
/**
* 获取首页专访列表list —— 分页
*/
export
const
getInterviewList
=
(
data
:
PageSearchParams
)
=>
{
return
service
.
request
<
BackendServicePageResult
<
InterviewItemDto
>>
({
url
:
'/api/yaCulture/listByPage'
,
method
:
'POST'
,
data
:
{
...
data
,
type
:
'interview'
,
},
})
}
/**
* 获取专栏列表选项-- 不分页 用户新增时候选择
*/
export
const
getColumnOptions
=
()
=>
{
return
service
.
request
<
ColumnOptionDto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=column'
,
method
:
'POST'
,
})
}
/**
* 获取首页专栏列表list —— 分页
*/
export
const
getColumnList
=
(
data
:
PageSearchParams
)
=>
{
return
service
.
request
<
BackendServicePageResult
<
ColumnItemDto
>>
({
url
:
'/api/yaCulture/listByPage'
,
method
:
'POST'
,
data
:
{
...
data
,
type
:
'column'
,
},
})
}
/**
* 点赞或者取消点赞文章
*/
export
const
addOrCanceArticlelLike
=
(
articleId
:
number
|
string
)
=>
{
return
service
.
request
<
boolean
>
({
url
:
`/api/cultureCollect/addOrCancelCollect`
,
url
:
`/api/cultureArticle/likeArticle?articleId=
${
articleId
}
`
,
method
:
'POST'
,
})
}
/**
* 收藏或者取消收藏文章
*/
export
const
addOrCanceArticlelCollect
=
(
articleId
:
number
|
string
)
=>
{
return
service
.
request
<
boolean
>
({
url
:
`/api/cultureArticle/collectArticle?articleId=
${
articleId
}
`
,
method
:
'POST'
,
})
}
/**
* 获取评论列表
*/
export
const
getCommentList
=
(
data
:
CommentSearchParams
)
=>
{
return
service
.
request
<
BackendServicePageResult
<
CommentItemDto
>>
({
url
:
`/api/cultureComment/getComment`
,
method
:
'POST'
,
data
,
})
}
/**
*
获取专访列表--不分页
*
新增评论
*/
export
const
getInterviewList
=
(
)
=>
{
return
service
.
request
<
InterviewItemDto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=interview'
,
export
const
addComment
=
(
data
:
AddCommentDto
)
=>
{
return
service
.
request
<
boolean
>
({
url
:
`/api/cultureComment/addComment`
,
method
:
'POST'
,
data
,
})
}
/**
*
获取专栏列表-- 不分页
*
点赞评论
*/
export
const
getColumnList
=
(
)
=>
{
return
service
.
request
<
ColumnItemDto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=column'
,
export
const
addOrCancelCommentLike
=
(
commentId
:
number
|
string
)
=>
{
return
service
.
request
<
boolean
>
({
url
:
`/api/cultureComment/likeComment?commentId=
${
commentId
}
`
,
method
:
'POST'
,
})
}
src/api/article/types.ts
View file @
4a88ffa9
...
...
@@ -6,6 +6,7 @@ import type { PageSearchParams } from '@/utils/request/types'
*/
export
interface
ArticleSearchParams
extends
PageSearchParams
{
type
?:
ArticleTypeEnum
sortLogic
?:
number
}
/**
...
...
@@ -41,7 +42,7 @@ interface AddOrUpdateColumnBase {
faceUrl
?:
string
imgUrl
?:
string
// 关联的专栏栏目
relateColumn
:
number
relateColumn
Id
:
number
mainTagId
:
string
isRelateColleague
:
BooleanFlag
sendTime
?:
string
...
...
@@ -71,7 +72,7 @@ export interface AddOrUpdateInterviewBase {
faceUrl
?:
string
imgUrl
?:
string
// 关联的专访栏目
relateColumn
:
number
relateColumn
Id
:
number
mainTagId
:
string
sendTime
?:
string
}
...
...
@@ -95,7 +96,6 @@ export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase {
* 文章详情
*/
// 更严格的类型定义
export
interface
ArticleItemDto
{
id
:
number
title
:
string
...
...
@@ -114,10 +114,17 @@ export interface ArticleItemDto {
praiseCount
:
number
collectionCount
:
number
replyCount
:
number
hasPraised
:
BooleanFlag
hasPraised
:
boolean
hasCollect
:
boolean
imgUrl
:
string
createUserAvatar
:
string
createUserName
:
string
}
export
interface
ColumnItemDto
{
/**
* 专栏选项
*/
export
interface
ColumnOptionDto
{
color
:
string
createTime
:
number
createUserId
:
number
...
...
@@ -129,7 +136,34 @@ export interface ColumnItemDto {
type
:
'column'
}
export
interface
InterviewItemDto
{
/**
* 专栏列表Item
*/
export
interface
ColumnItemDto
{
title
:
string
color
:
string
sort
:
number
yaColumnVoList
:
{
articleId
:
number
collectCount
:
number
content
:
string
createTime
:
number
description
:
string
faceUrl
:
string
hasPraised
:
boolean
isRecommend
:
number
praiseCount
:
number
replyCount
:
number
title
:
string
type
:
ArticleTypeEnum
.
COLUMN
viewCount
:
number
}[]
}
/**
* 专访选项
*/
export
interface
InterviewOptionDto
{
color
:
string
createTime
:
number
createUserId
:
number
...
...
@@ -140,3 +174,65 @@ export interface InterviewItemDto {
title
:
string
type
:
'column'
}
/**
* 专访列表Item
*/
export
interface
InterviewItemDto
{
title
:
string
color
:
string
sort
:
number
yaColumnVoList
:
{
articleId
:
number
collectCount
:
number
content
:
string
createTime
:
number
description
:
string
faceUrl
:
string
hasPraised
:
boolean
isRecommend
:
number
praiseCount
:
number
replyCount
:
number
title
:
string
type
:
ArticleTypeEnum
.
INTERVIEW
viewCount
:
number
}[]
}
/**
* 评论列表
*/
export
interface
CommentSearchParams
extends
PageSearchParams
{
articleId
:
number
|
string
}
/**
* 新增评论
*/
export
interface
AddCommentDto
{
articleId
:
number
|
string
content
:
string
pId
?:
number
|
string
}
/**
* 评论信息
*/
export
interface
CommentItemDto
{
articleId
:
number
avatar
:
string
children
:
CommentItemDto
[]
content
:
string
createTime
:
number
hasPraise
:
BooleanFlag
hiddenAvatar
:
string
hiddenName
:
string
id
:
number
isFeatured
:
number
isTop
:
number
pid
:
number
postPriseCount
:
number
region
:
string
regionHide
:
number
replyUser
:
string
userId
:
number
}
src/api/column/index.ts
View file @
4a88ffa9
// 专栏列表
import
service
from
'@/utils/request/index'
import
type
{
Column
Item
Dto
}
from
'./types'
import
type
{
Column
Option
Dto
}
from
'./types'
/**
* 获取专栏列表
*/
export
const
getColumn
List
=
()
=>
{
return
service
.
request
<
Column
Item
Dto
[]
>
({
export
const
getColumn
Options
=
()
=>
{
return
service
.
request
<
Column
Option
Dto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=column'
,
method
:
'POST'
,
})
...
...
src/api/column/types.ts
View file @
4a88ffa9
import
{
BooleanFlag
}
from
'@/constants'
export
interface
Column
Item
Dto
{
export
interface
Column
Option
Dto
{
color
:
string
createTime
:
number
createUserId
:
number
...
...
src/api/common/index.ts
View file @
4a88ffa9
...
...
@@ -4,13 +4,28 @@ import type { FielItemDto } from './types'
/**
* 获取常规的接口
*/
// export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
// const formData = new FormData()
// formData.append('file', file)
// return service.request<FielItemDto>({
// url: '/mobiles/file-upload/singleUpload',
// method: 'POST',
// data: formData,
// onUploadProgress: (progressEvent) => {
// const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1))
// onProgress?.(percentCompleted)
// },
// })
// }
/**
* 暂时调用oa正式接口
*/
import
axios
from
'axios'
export
const
uploadFile
=
(
file
:
File
,
onProgress
?:
(
progress
:
number
)
=>
void
)
=>
{
const
formData
=
new
FormData
()
formData
.
append
(
'file'
,
file
)
return
service
.
request
<
FielItemDto
>
({
url
:
'/mobiles/file-upload/singleUpload'
,
method
:
'POST'
,
data
:
formData
,
formData
.
append
(
'fileList'
,
file
)
return
axios
.
post
(
'http://47.112.96.71:8082/mobiles/uploadFile'
,
formData
,
{
onUploadProgress
:
(
progressEvent
)
=>
{
const
percentCompleted
=
Math
.
round
((
progressEvent
.
loaded
*
100
)
/
(
progressEvent
.
total
||
1
))
onProgress
?.(
percentCompleted
)
...
...
src/api/interview/index.ts
View file @
4a88ffa9
// 专栏列表
import
service
from
'@/utils/request/index'
import
type
{
Column
Item
Dto
}
from
'./types'
import
type
{
Column
Option
Dto
}
from
'./types'
/**
* 获取专栏列表
*/
export
const
getInterview
List
=
()
=>
{
return
service
.
request
<
Column
Item
Dto
[]
>
({
export
const
getInterview
Options
=
()
=>
{
return
service
.
request
<
Column
Option
Dto
[]
>
({
url
:
'/api/cultureColumn/listNoPage?type=interview'
,
method
:
'POST'
,
})
...
...
src/api/interview/types.ts
View file @
4a88ffa9
import
{
BooleanFlag
}
from
'@/constants'
export
interface
Interview
Item
Dto
{
export
interface
Interview
Option
Dto
{
color
:
string
createTime
:
number
createUserId
:
number
...
...
src/api/user/types.ts
View file @
4a88ffa9
...
...
@@ -58,7 +58,7 @@ export interface AuditListItemDto {
isRelateColleague
:
boolean
playCount
:
number
praiseCount
:
number
relateColumn
:
string
relateColumn
Id
:
string
releaseStatus
:
AuditStatusEnum
replyCount
:
number
showAvatar
:
string
...
...
src/assets/svg/praise.svg
0 → 100644
View file @
4a88ffa9
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
t=
"1763611281052"
class=
"icon"
viewBox=
"0 0 1024 1024"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
p-id=
"7362"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
width=
"256"
height=
"256"
><path
d=
"M190.193225 471.411583c14.446014 0 26.139334-11.718903 26.139334-26.13831 0-14.44499-11.69332-26.164916-26.139334-26.164916-0.271176 0-0.490164 0.149403-0.73678 0.149403l-62.496379 0.146333c-1.425466-0.195451-2.90005-0.295735-4.373611-0.295735-19.677155 0-35.621289 16.141632-35.621289 36.114522L86.622358 888.550075c0 19.949354 15.96767 35.597753 35.670407 35.597753 1.916653 0 3.808746 0.292666 5.649674 0l61.022819 0.022513c0.099261 0 0.148379 0.048095 0.24764 0.048095 0.097214 0 0.146333-0.048095 0.24457-0.048095l0.73678 0 0-0.148379c13.413498-0.540306 24.174586-11.422144 24.174586-24.960485 0-13.55983-10.760065-24.441669-24.174586-24.981974l0-0.393973-50.949392 0 1.450025-402.275993L190.193225 471.409536z"
fill=
"#606266"
p-id=
"7363"
></path><path
d=
"M926.52241 433.948343c-19.283182-31.445176-47.339168-44.172035-81.289398-45.546336-1.77032-0.246617-3.536546-0.39295-5.380544-0.39295l-205.447139-0.688685c13.462616-39.059598 22.698978-85.58933 22.698978-129.317251 0-28.349675-3.193739-55.962569-9.041934-82.542948l-0.490164 0.049119c-10.638291-46.578852-51.736315-81.31498-100.966553-81.31498-57.264215 0-95.466282 48.15065-95.466282 106.126063 0 3.241834-0.294712 6.387477 0 9.532097-2.996241 108.386546-91.240027 195.548698-196.23636 207.513194l0 54.881958-0.785899 222.227314 0 229.744521 10.709923 0 500.025271 0.222057 8.746198-0.243547c19.35686 0.049119 30.239721-4.817726 47.803749-16.116049 16.682961-10.761088 29.236881-25.50079 37.490869-42.156122 2.260483-3.341095 4.028757-7.075139 5.106298-11.20111l77.018118-344.324116c1.056052-4.053316 1.348718-8.181333 1.056052-12.160971C943.643346 476.446249 938.781618 453.944769 926.52241 433.948343zM893.82573 486.837924l-82.983993 367.783411-0.099261-0.049119c-2.555196 6.141884-6.879688 11.596106-12.872169 15.427364-4.177136 2.727111-8.773827 4.351098-13.414521 4.964058-1.49812-0.195451-3.046383 0-4.620227 0l-477.028511-0.540306-0.171915-407.408897c89.323375-40.266076 154.841577-79.670527 188.596356-173.661202 0.072655 0.024559 0.124843 0.049119 0.195451 0.072655 2.99931-9.137101 6.313799-20.73423 8.697079-33.164331 5.551436-29.185716 5.258771-58.123792 5.258771-58.123792-4.937452-37.98001 25.940812-52.965306 44.364417-52.965306 25.304316 0.860601 50.263777 33.656541 50.263777 52.326762 0 0 5.600555 27.563776 5.649674 57.190537 0.048095 37.366026-4.6673 56.847729-4.6673 56.847729l-0.466628 0c-5.872754 30.879288-16.214287 60.138682-30.464849 86.964654l0.36839 0.342808c-2.358721 4.815679-3.709485 10.220782-3.709485 15.943111 0 19.922748 19.088754 21.742187 38.765909 21.742187l238.761895 0.270153c0 0 14.666024 0.465604 14.690584 0.465604l0 0.100284c12.132318-0.638543 24.221658 5.207605 31.100322 16.409738 5.504364 9.016351 6.437619 19.6045 3.486404 28.988218L893.82573 486.837924z"
fill=
"#606266"
p-id=
"7364"
></path><path
d=
"M264.827039 924.31872c0.319272 0.024559 0.441045 0.024559 0.295735-0.024559 0.243547-0.048095 0.367367-0.074701-0.295735-0.074701s-0.539282 0.026606-0.271176 0.074701C264.43409 924.343279 264.532327 924.343279 264.827039 924.31872z"
fill=
"#606266"
p-id=
"7365"
></path></svg>
\ No newline at end of file
src/components/common/UploadFile/index.vue
View file @
4a88ffa9
...
...
@@ -125,7 +125,9 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 上传文件
const
{
data
}
=
await
uploadFileApi
(
uploadFile
.
raw
)
console
.
log
(
'data'
,
data
)
const
url
=
data
.
fileUrl
||
data
.
data
[
0
].
filePath
const
name
=
data
.
fileName
||
data
.
data
[
0
].
finalName
// ✅ 上传完成后重新查找索引(第二次查找)
fileIndex
=
fileList
.
value
.
findIndex
((
file
)
=>
file
.
uid
===
uid
)
...
...
@@ -133,8 +135,8 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 更新文件信息
fileList
.
value
[
fileIndex
]
=
{
...
fileList
.
value
[
fileIndex
],
url
:
data
.
fileUrl
,
name
:
data
.
fileName
,
url
,
name
,
status
:
'success'
,
}
ElMessage
.
success
(
'上传成功'
)
...
...
src/hooks/useScrollTop.tsx
View file @
4a88ffa9
...
...
@@ -2,7 +2,7 @@ import type { SetupContext, MaybeRef } from 'vue'
type
Events
=
{
handleBackTop
():
void
}
function
ScrollTopComp
(
_
:
Record
<
string
,
never
>
,
{
emit
}:
SetupContext
<
Events
>
)
{
function
ScrollTopComp
(
_
:
any
,
{
emit
}:
SetupContext
<
Events
>
)
{
return
(
<
button
class=
"back-top-btn group cursor-pointer flex items-center gap-3 px-4 py-2.5 bg-gradient-to-r from-blue-50 to-indigo-50 hover:from-blue-100 hover:to-indigo-100 border border-blue-200/50 rounded-full transition-all duration-300 hover:shadow-lg hover:-translate-y-1 active:scale-95 shadow-sm"
...
...
src/layoutCulture/components/colnumForm.tsx
View file @
4a88ffa9
...
...
@@ -12,6 +12,7 @@ import type { AddOrUpdateColumnForm, AddOrUpdateColumnDto } from '@/api/article/
export
default
defineComponent
((
_
,
{
expose
})
=>
{
const
columnStore
=
useColumnStore
()
const
{
columnList
}
=
storeToRefs
(
columnStore
)
console
.
log
(
columnList
.
value
)
const
[
form
,
resetForm
]
=
useResetData
<
AddOrUpdateColumnForm
>
({
title
:
''
,
content
:
''
,
...
...
@@ -23,7 +24,7 @@ export default defineComponent((_, { expose }) => {
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
isRelateColleague
:
BooleanFlag
.
NO
,
relateColumn
:
0
,
relateColumn
Id
:
0
,
type
:
ArticleTypeEnum
.
COLUMN
,
})
const
formRef
=
ref
<
InstanceType
<
typeof
ElForm
>>
()
...
...
@@ -34,7 +35,7 @@ export default defineComponent((_, { expose }) => {
mainTagId
:
[{
required
:
true
,
message
:
'请选择主标签'
,
trigger
:
'blur'
}],
sendType
:
[{
required
:
true
,
message
:
'请选择发布类型'
,
trigger
:
'blur'
}],
sendTime
:
[{
required
:
true
,
message
:
'请选择发布时间'
,
trigger
:
'blur'
}],
relateColumn
:
[
relateColumn
Id
:
[
{
required
:
true
,
message
:
'请选择专栏栏目'
,
trigger
:
'blur'
,
type
:
'number'
,
min
:
1
},
],
}
...
...
@@ -111,8 +112,8 @@ export default defineComponent((_, { expose }) => {
{
/* @ts-ignore */
}
<
UploadFile
v
-
model=
{
form
.
value
.
imgUrl
}
/>
</
el
-
form
-
item
>
<
el
-
form
-
item
label=
"专栏栏目选择"
prop=
"relateColumn"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
relateColumn
}
>
<
el
-
form
-
item
label=
"专栏栏目选择"
prop=
"relateColumn
Id
"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
relateColumn
Id
}
>
{
columnList
.
value
.
map
((
item
)
=>
(
<
el
-
radio
value=
{
item
.
id
}
>
{
item
.
title
}
</
el
-
radio
>
))
}
...
...
src/layoutCulture/components/interviewForm.tsx
View file @
4a88ffa9
...
...
@@ -22,7 +22,7 @@ export default defineComponent((_, { expose }) => {
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
type
:
ArticleTypeEnum
.
INTERVIEW
,
relateColumn
:
0
,
relateColumn
Id
:
0
,
})
const
formRef
=
ref
<
InstanceType
<
typeof
ElForm
>>
()
const
rules
=
{
...
...
@@ -32,7 +32,7 @@ export default defineComponent((_, { expose }) => {
mainTagId
:
[{
required
:
true
,
message
:
'请选择主标签'
,
trigger
:
'blur'
}],
sendType
:
[{
required
:
true
,
message
:
'请选择发布类型'
,
trigger
:
'blur'
}],
sendTime
:
[{
required
:
true
,
message
:
'请选择发布时间'
,
trigger
:
'blur'
}],
relateColumn
:
[
relateColumn
Id
:
[
{
required
:
true
,
message
:
'请选择专访栏目'
,
trigger
:
'blur'
,
type
:
'number'
,
min
:
1
},
],
}
...
...
@@ -109,8 +109,8 @@ export default defineComponent((_, { expose }) => {
{
/* @ts-ignore */
}
<
UploadFile
v
-
model=
{
form
.
value
.
imgUrl
}
/>
</
el
-
form
-
item
>
<
el
-
form
-
item
label=
"专访栏目选择"
prop=
"relateColumn"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
relateColumn
}
>
<
el
-
form
-
item
label=
"专访栏目选择"
prop=
"relateColumn
Id
"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
relateColumn
Id
}
>
{
columnList
.
value
.
map
((
item
)
=>
(
<
el
-
radio
value=
{
item
.
id
}
>
{
item
.
title
}
</
el
-
radio
>
))
}
...
...
src/layoutCulture/components/postForm.tsx
View file @
4a88ffa9
...
...
@@ -27,7 +27,7 @@ export default defineComponent(
return
{
...
form
.
value
,
releaseStatus
,
img
Url
:
form
.
value
.
imgUrl
?.
split
(
','
).
filter
(
Boolean
)[
0
]
||
''
,
face
Url
:
form
.
value
.
imgUrl
?.
split
(
','
).
filter
(
Boolean
)[
0
]
||
''
,
}
}
...
...
src/router/index.ts
View file @
4a88ffa9
...
...
@@ -34,7 +34,7 @@ const routes = [
],
},
{
path
:
'videoDetail/:'
,
path
:
'videoDetail/:
id
'
,
name
:
'CultureVideoDetail'
,
component
:
()
=>
import
(
'@/views/videoDetail/index.vue'
),
},
...
...
src/stores/column.ts
View file @
4a88ffa9
import
{
defineStore
}
from
'pinia'
import
{
getColumn
List
}
from
'@/api'
import
type
{
Column
Item
Dto
}
from
'@/api/article/types'
import
{
getColumn
Options
}
from
'@/api'
import
type
{
Column
Option
Dto
}
from
'@/api/article/types'
/**
* 关于专栏的不分页数据
*/
export
const
useColumnStore
=
defineStore
(
'column'
,
()
=>
{
const
columnList
=
ref
<
Column
Item
Dto
[]
>
([])
const
columnList
=
ref
<
Column
Option
Dto
[]
>
([])
let
isLoading
=
false
...
...
@@ -14,7 +14,7 @@ export const useColumnStore = defineStore('column', () => {
if
(
isLoading
)
return
isLoading
=
true
try
{
const
{
data
}
=
await
getColumn
List
()
const
{
data
}
=
await
getColumn
Options
()
columnList
.
value
=
data
console
.
log
(
columnList
.
value
,
'columnList'
)
}
catch
(
error
)
{
...
...
src/stores/interview.ts
View file @
4a88ffa9
import
{
defineStore
}
from
'pinia'
import
{
getInterview
List
}
from
'@/api'
import
type
{
Interview
Item
Dto
}
from
'@/api/article/types'
import
{
getInterview
Options
}
from
'@/api'
import
type
{
Interview
Option
Dto
}
from
'@/api/article/types'
/**
* 关于专访的相关数据 --不分页
*/
export
const
useInterviewStore
=
defineStore
(
'interview'
,
()
=>
{
const
interviewList
=
ref
<
Interview
Item
Dto
[]
>
([])
const
interviewList
=
ref
<
Interview
Option
Dto
[]
>
([])
let
isLoading
=
false
...
...
@@ -14,7 +14,7 @@ export const useInterviewStore = defineStore('interview', () => {
if
(
interviewList
.
value
.
length
>
0
)
return
isLoading
=
true
try
{
const
{
data
}
=
await
getInterview
List
()
const
{
data
}
=
await
getInterview
Options
()
interviewList
.
value
=
data
console
.
log
(
interviewList
.
value
,
'interviewList'
)
}
catch
(
error
)
{
...
...
src/views/homePage/homeTab/components/recommendList.vue
View file @
4a88ffa9
<
template
>
<div
ref=
"listRef"
>
<div
v-loading=
"loading"
v-if=
"list.length
> 0
"
>
<div
v-loading=
"loading"
v-if=
"list.length"
>
<div
class=
"space-y-3 sm:space-y-4"
>
<div
v-for=
"item in list"
...
...
@@ -10,7 +10,7 @@
>
<div
class=
"flex gap-3 justify-between"
>
<!-- 内容区域 -->
<div
class=
"flex-1 min-w-0 flex flex-col justify-between"
>
<div
class=
"flex-1 min-w-0 flex flex-col justify-between
h-24
"
>
<!-- 标题 -->
<h2
class=
"text-xl font-semibold text-gray-900 line-clamp-1 group-hover:text-blue-600 transition-colors duration-200 leading-tight"
...
...
@@ -20,7 +20,7 @@
<!-- 内容摘要 -->
<div
class=
"my-2 space-y-1"
>
<p
class=
"text-gray-600 text-sm sm:text-base leading-relaxed line-clamp-
2
"
>
<p
class=
"text-gray-600 text-sm sm:text-base leading-relaxed line-clamp-
1
"
>
{{
item
.
content
}}
</p>
</div>
...
...
@@ -55,7 +55,7 @@
</div>
<!-- 图片区域 -->
<div
v-
if
=
"item.faceUrl"
class=
"relative flex-shrink-0 w-36 h-24"
>
<div
v-
show
=
"item.faceUrl"
class=
"relative flex-shrink-0 w-36 h-24"
>
<img
:src=
"item.faceUrl"
alt=
"文章配图"
...
...
@@ -116,16 +116,16 @@
<
script
setup
lang=
"ts"
name=
"RecommendList"
>
import
{
usePageSearch
}
from
'@/hooks'
import
{
getArticleList
}
from
'@/api'
import
{
ArticleTypeEnum
,
TABS_REF_KEY
}
from
'@/constants'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
useScrollTop
}
from
'@/hooks'
import
dayjs
from
'dayjs'
const
router
=
useRouter
()
const
{
list
,
total
,
searchParams
,
loading
,
goToPage
,
changePageSize
,
re
set
}
=
usePageSearch
(
const
{
list
,
total
,
searchParams
,
loading
,
goToPage
,
changePageSize
,
re
fresh
}
=
usePageSearch
(
getArticleList
,
{
// defaultParams: { type: ArticleTypeEnum.POST
},
defaultParams
:
{
sortLogic
:
0
},
defaultCurrent
:
1
,
defaultSize
:
5
,
},
...
...
@@ -135,6 +135,10 @@ const tabsRef = inject(TABS_REF_KEY)
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
tabsRef
!
)
defineExpose
({
refresh
:
reset
,
refresh
:
(
sortLogic
:
number
)
=>
{
console
.
log
(
'sortLogic'
,
sortLogic
)
searchParams
.
value
.
sortLogic
=
sortLogic
refresh
()
},
})
</
script
>
src/views/homePage/homeTab/components/videoList.vue
View file @
4a88ffa9
<
template
>
<div
class=
"
min-h-screen
bg-gray-50/30"
>
<div
class=
"bg-gray-50/30"
>
<!-- tabs -->
<div
class=
"shadow-sm"
>
<div
class=
"max-w-7xl mx-auto px-4"
>
...
...
@@ -24,7 +24,7 @@
</div>
</div>
<!-- 第一页的特殊布局 -->
<template
v-if=
"
currentPage === 1
"
>
<template
v-if=
"
searchParams.current === 0
"
>
<!-- 前三个特殊布局 -->
<div
class=
"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"
>
<!-- 第一个视频 - 占据两列 -->
...
...
@@ -34,7 +34,6 @@
<div
class=
"relative overflow-hidden"
>
<img
src=
"https://picsum.photos/800/400?random=1"
alt=
"视频封面"
class=
"w-full h-72 object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div
...
...
@@ -113,7 +112,6 @@
<div
class=
"relative overflow-hidden"
>
<img
:src=
"`https://picsum.photos/400/200?random=$
{n + 1}`"
alt="视频封面"
class="w-full h-36 object-cover group-hover:scale-105 transition-transform duration-500"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"
></div>
...
...
@@ -168,14 +166,13 @@
<!-- 剩余视频 - 标准网格 -->
<div
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
v-for=
"item in
12
"
:key=
"item"
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=
"`https://picsum.photos/300/200?random=$
{item + 10}`"
alt="视频封面"
: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>
...
...
@@ -184,16 +181,14 @@
<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
%
2
===
0
?
'🔬 科技'
:
'🎨 设计'
}}
{{
item
.
tagNameList
[
0
]
}}
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
String
(
Math
.
floor
(
Math
.
random
()
*
60
)).
padStart
(
2
,
'0'
)
}}
:
{{
String
(
Math
.
floor
(
Math
.
random
()
*
60
)).
padStart
(
2
,
'0'
)
}}
15:18
</div>
<!-- 数据 -->
...
...
@@ -202,13 +197,13 @@
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<SvgIcon
name=
"icon_play"
size=
"12"
/>
<span>
{{
(
Math
.
random
()
*
10
).
toFixed
(
1
)
}}
万
</span>
<span>
{{
item
.
viewCount
}}
万
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<SvgIcon
name=
"icon_comment"
size=
"12"
/>
<span>
{{
Math
.
floor
(
Math
.
random
()
*
1000
)
}}
</span>
<span>
{{
item
.
replyCount
}}
</span>
</div>
</div>
...
...
@@ -228,23 +223,23 @@
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-2"
>
{{
[
'设计师必看的创意灵感来源'
,
'前端开发实战教程'
,
'UI设计从入门到精通'
][
item
%
3
]
}}
{{
item
.
title
}}
</h3>
<p
class=
"text-gray-600 text-sm mb-3 line-clamp-2 leading-relaxed"
>
这是一个非常有趣的视频教程,包含了丰富的内容和实用的技巧,适合初学者和进阶用户学习...
{{
item
.
content
}}
</p>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-6 h-6 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center text-white text-xs font-bold"
>
{{
String
(
item
).
slice
(
-
1
)
}}
{{
item
.
createUserName
}}
</div>
<span
class=
"font-medium"
>
UP主
{{
item
}}
</span>
<span
class=
"font-medium"
>
UP主
{{
item
.
createUserName
}}
</span>
</div>
<span
class=
"bg-gray-100 px-2 py-1 rounded-full"
>
{{
Math
.
floor
(
Math
.
random
()
*
30
)
+
1
}}
天前
</span
>
<span
class=
"bg-gray-100 px-2 py-1 rounded-full"
>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span
>
</div>
</div>
</div>
...
...
@@ -255,14 +250,14 @@
<
template
v-else
>
<div
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
v-for=
"item in 12"
:key=
"item"
@
click=
"router.push(`/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
class=
"relative overflow-hidden"
>
<img
:src=
"`https://picsum.photos/300/200?random=$
{item + 10}`"
alt="视频封面"
: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>
...
...
@@ -271,16 +266,14 @@
<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
%
2
===
0
?
'🔬 科技'
:
'🎨 设计'
}}
{{
item
.
tagNameList
[
0
]
}}
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
{{
String
(
Math
.
floor
(
Math
.
random
()
*
60
)).
padStart
(
2
,
'0'
)
}}
:
{{
String
(
Math
.
floor
(
Math
.
random
()
*
60
)).
padStart
(
2
,
'0'
)
}}
15:18
</div>
<!-- 数据 -->
...
...
@@ -288,25 +281,30 @@
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<
SvgIcon
name=
"icon_play"
size=
"12"
/
>
<span>
{{
(
Math
.
random
()
*
10
).
toFixed
(
1
)
}}
万
</span>
<
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"
>
<SvgIcon
name=
"icon_comment"
size=
"12"
/>
<span>
{{
Math
.
floor
(
Math
.
random
()
*
1000
)
}}
</span>
<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"
>
<el-icon
class=
"text-sm"
><Star
/></el-icon>
<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=
"
w-12 h-12
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"
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"
>
<
SvgIcon
name=
"icon_play"
size=
"20"
color=
"#374151"
/
>
<
el-icon
size=
"50"
color=
"#333"
><VideoPlay
/></el-icon
>
</div>
</div>
</div>
...
...
@@ -315,23 +313,21 @@
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-2"
>
{{
[
'设计师必看的创意灵感来源'
,
'前端开发实战教程'
,
'UI设计从入门到精通'
][
item
%
3
]
}}
{{
item
.
title
}}
</h3>
<p
class=
"text-gray-600 text-sm mb-3 line-clamp-2 leading-relaxed"
>
这是一个非常有趣的视频教程,包含了丰富的内容和实用的技巧,适合初学者和进阶用户学习...
{{
item
.
content
}}
</p>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-6 h-6 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center text-white text-xs font-bold"
>
{{
String
(
item
).
slice
(
-
1
)
}}
{{
item
.
createUserName
}}
</div>
<span
class=
"font-medium"
>
UP主
{{
item
}}
</span>
<span
class=
"font-medium"
>
UP主
{{
item
.
createUserName
}}
</span>
</div>
<span
class=
"bg-gray-100 px-2 py-1 rounded-full"
>
{{
Math
.
floor
(
Math
.
random
()
*
30
)
+
1
}}
天前
</span
>
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</div>
</div>
</div>
...
...
@@ -353,11 +349,11 @@
class=
"pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"
currentPage
"
v-model:page-size=
"
pageS
ize"
:page-sizes=
"[1
5, 30, 45, 60
]"
v-model:current-page=
"
searchParams.current
"
v-model:page-size=
"
searchParams.s
ize"
:page-sizes=
"[1
2, 24, 36, 48
]"
layout=
"prev, pager, next, jumper, total"
:total=
"
400
"
:total=
"
total
"
class=
"custom-pagination"
/>
</div>
...
...
@@ -371,14 +367,22 @@
<
script
setup
lang=
"ts"
name=
"RecommendList"
>
import
{
useRouter
}
from
'vue-router'
import
{
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
ArticleTypeEnum
,
TABS_REF_KEY
}
from
'@/constants'
import
{
usePageSearch
}
from
'@/hooks'
import
{
getArticleList
}
from
'@/api'
import
dayjs
from
'dayjs'
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
tabsRef
!
)
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
15
)
const
{
list
,
total
,
searchParams
,
loading
,
goToPage
,
changePageSize
,
refresh
}
=
usePageSearch
(
getArticleList
,
{
defaultParams
:
{
type
:
ArticleTypeEnum
.
VIDEO
},
defaultCurrent
:
1
,
defaultSize
:
12
,
},
)
// Tabs 配置
const
router
=
useRouter
()
const
tabs
=
[
...
...
@@ -392,6 +396,12 @@ const activeTab = ref('latest')
const
goVideoDetail
=
(
n
:
number
)
=>
{
router
.
push
(
`/videoDetail?id=
${
n
}
`
)
}
defineExpose
({
refresh
:
(
sortLogic
:
number
)
=>
{
searchParams
.
value
.
sortLogic
=
sortLogic
refresh
()
},
})
</
script
>
<
style
>
...
...
src/views/homePage/homeTab/index.vue
View file @
4a88ffa9
...
...
@@ -44,12 +44,20 @@ const activeTabComponentRef =
useTemplateRef
<
InstanceType
<
typeof
RecommendList
>>
(
'activeTabComponentRef'
)
const
handleRefresh
=
()
=>
{
activeTabComponentRef
.
value
?.
refresh
?.()
if
(
activeTab
.
value
===
'推荐'
)
{
activeTabComponentRef
.
value
?.
refresh
?.(
0
)
}
else
if
(
activeTab
.
value
===
'最新'
)
{
activeTabComponentRef
.
value
?.
refresh
?.(
1
)
}
else
if
(
activeTab
.
value
===
'视频'
)
{
activeTabComponentRef
.
value
?.
refresh
?.(
2
)
}
}
const
handleTabChange
=
(
tab
:
string
)
=>
{
if
(
tab
===
'最新'
)
{
activeTabComponentRef
.
value
?.
refresh
?.()
activeTabComponentRef
.
value
?.
refresh
?.(
1
)
}
else
if
(
tab
===
'推荐'
)
{
activeTabComponentRef
.
value
?.
refresh
?.(
0
)
}
}
</
script
>
...
...
src/views/homePage/index.vue
View file @
4a88ffa9
...
...
@@ -10,22 +10,25 @@
<div
class=
"flex gap-3"
>
<div
class=
"left flex-1 basis-full xl:basis-3/4 transition-all duration-500"
>
<div
ref=
"tabsRef"
class=
"tabs-container h-7
0px flex relative md:h-60px rounded-lg mb-3
"
>
<div
ref=
"tabsRef"
class=
"tabs-container h-7
5px flex relative rounded-lg mb-3 shadow-md
"
>
<div
v-for=
"tab in tabs"
:key=
"tab.path"
class=
"flex-1 flex items-center justify-center cursor-pointer relative transition-all duration-300
hover:bg-white/10 gap-2
group"
class=
"flex-1 flex items-center justify-center cursor-pointer relative transition-all duration-300 group"
@
click=
"toggleTab(tab)"
>
<div
class=
"flex items-center gap-2 px-12 py-2.5 rounded-xl transition-all duration-300"
:class=
"
{
'bg-#fffdfd shadow-[inset_0_2px_4px_0_rgb(0,0,0,0.1)]': activeTab === tab.name,
'hover:bg-white/60': activeTab !== tab.name,
}"
>
<svg-icon
:name=
"tab.svg"
class=
"h-60px w-auto md:h-50px sm:h-40px"
size=
"40"
/>
<div
class=
"text-18px font-500 text-gray-800 md:text-16px sm:text-14px"
>
{{
tab
.
name
}}
</div>
<!-- 下划线 -->
<div
class=
"absolute bottom-0 left-0 right-0 h-4px bg-[linear-gradient(to_right,#60a5fa_0%,#c084fc_100%)] transform scale-x-0 transition-transform duration-300 origin-center"
:class=
"
{ 'scale-x-50': activeTab === tab.name }"
>
</div>
</div>
</div>
</div>
...
...
@@ -162,13 +165,16 @@
<!-- 任务中心 -->
<div
class=
"task-container common-box rounded-lg bg-#FFF0E5"
>
<div
class=
"flex items-
center justify-between
mb-2"
>
<div>
<div
class=
"flex items-
start justify-start
mb-2"
>
<div
class=
"w-full"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-1 h-4 bg-gradient-to-b from-pink-500 to-rose-500 rounded-full"
></div>
<h1
class=
"text-sm sm:text-base font-bold"
>
任务中心
</h1>
</div>
<h2
class=
"text-xs sm:text-sm mb-1 text-gray-600 ml-3"
>
<h2
class=
"w-full text-xs sm:text-sm mt-1 text-gray-600 flex items-center justify-between"
>
<div>
<span
v-for=
"item in taskTypeList"
:key=
"item.value"
...
...
@@ -177,6 +183,10 @@
@click="currentTask = item.value"
>
{{
item
.
label
}}
</span>
</div>
<span
class=
"text-#999 cursor-pointer"
@
click=
"router.push(`/userPage?key=task`)"
>
查看更多
</span>
</h2>
</div>
</div>
...
...
@@ -210,13 +220,23 @@
<
/div
>
<
/div
>
<
/div
>
<
el
-
button
class
=
"bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none hover:-translate-y-1 transition-all duration-200 text-xs sm:text-sm rounded-full"
type
=
"primary"
<
button
class
=
"w-72px h-32px shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none text-xs sm:text-sm rounded-full"
:
class
=
"[
item.currentCount === item.limitCount
? 'bg-#FFC5A1'
: 'bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] hover:-translate-y-1 transition-all duration-200 cursor-pointer',
]"
@
click
=
"handleTask(item)"
>
<
span
class
=
"text-black text-xs sm:text-sm"
>
去完成
<
/span
>
<
/el-button
>
<
span
class
=
"text-black text-sm"
:
style
=
"{
color: item.currentCount === item.limitCount ? '#999' : '#000',
}
"
>
{{
item
.
currentCount
===
item
.
limitCount
?
'已完成'
:
'去完成'
}}
<
/spa
n
>
<
/button
>
<
/div
>
<!--
分割线
-->
<
el
-
divider
style
=
"margin: 0"
/>
...
...
@@ -332,6 +352,8 @@ const onDailySign = async () => {
}
const
handleTask
=
(
item
:
TaskItemDto
)
=>
{
if
(
item
.
currentCount
===
item
.
limitCount
)
return
// if (item.svgName === 'svgName')
{
handleBackTop
()
triggerAnimation
()
...
...
src/views/homePage/yaTab/components/columnList.vue
View file @
4a88ffa9
<
template
>
<div>
<div
v-loading=
"loading"
v-if=
"list.length"
>
<div
class=
"w-full max-w-6xl mx-auto"
>
<div
v-for=
"item in columnL
ist"
:key=
"item.id
"
v-for=
"(item, index) in l
ist"
:key=
"index
"
class=
"bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
:style=
"
{ '--dynamic-color': item.color }"
>
...
...
@@ -14,103 +16,119 @@
<span
class=
"w-1 h-5 mr-2 bg-#444"
></span>
{{
item
.
title
}}
</h3>
<div
class=
"flex items-center cursor-pointer hover:text-[var(--dynamic-color)]"
>
<span
class=
"mr-1 text-14px color-#606266 hover:text-[var(--dynamic-color)]"
>
查看更多
</span
>
<div
class=
"flex items-center cursor-pointer"
>
<span
class=
"mr-1 text-14px color-#606266"
>
查看更多
</span>
<el-icon><ArrowRight
/></el-icon>
</div>
</div>
<div
class=
"p-4"
>
<div
class=
"grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div
v-for=
"item in section2Items"
:key=
"item.id"
class=
"group cursor-pointer"
>
<div
v-if=
"item.yaColumnVoList.length"
class=
"grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div
v-for=
"i in item.yaColumnVoList"
:key=
"i.articleId"
class=
"group cursor-pointer"
@
click=
"router.push(`/postDetail/$
{i.articleId}`)"
>
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<img
src=
"@/assets/img/culture/ask.png"
:alt=
"item.title"
class=
"w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
:src=
"i.faceUrl"
class=
"w-full aspect-[5/3] object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div
class=
"absolute top-2 left-2"
>
<el-tag
size=
"small"
class=
"bg-orange-500 text-white border-none"
>
{{
item
.
tag
}}
1111111
</el-tag>
</div>
</div>
<h3
class=
"text-sm font-medium text-gray-800 mb-2 transition-colors"
>
{{
item
.
title
}}
{{
i
.
title
}}
</h3>
<p
class=
"text-xs text-gray-500 mb-3 line-clamp-2"
>
{{
item
.
description
}}
{{
i
.
content
}}
</p>
<div
class=
"flex items-center justify-between text-xs text-gray-400"
>
<div
class=
"flex items-center space-x-4"
>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><View
/></el-icon>
{{
item
.
views
}}
{{
i
.
viewCount
}}
</span>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><ChatDotRound
/></el-icon>
{{
item
.
comments
}}
{{
i
.
replyCount
}}
</span>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><Star
/></el-icon>
{{
item
.
likes
}}
{{
i
.
collectCount
}}
</span>
</div>
<span>
{{
item
.
date
}}
</span>
<span>
{{
dayjs
(
i
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</div>
</div>
</div>
<div
v-else
class=
"flex items-center justify-center h-48"
>
<el-empty
description=
"暂无数据"
/>
</div>
</div>
</div>
</div>
<div
class=
"bottom-pagination backdrop-blur-8 border-t border-gray-200"
>
<div
class=
"max-w-7xl mx-auto py-6"
>
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[15, 30, 45, 60]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
}
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<template
v-else
>
<div
class=
"flex items-center justify-center h-full"
>
<el-empty
description=
"暂无数据"
/>
</div>
</
template
>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ArrowRight
,
View
,
ChatDotRound
,
Star
}
from
'@element-plus/icons-vue'
import
{
useColumnStore
}
from
'@/stores/column'
import
{
storeToRefs
}
from
'pinia'
import
{
getColumnList
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
dayjs
from
'dayjs'
import
{
useRouter
}
from
'vue-router'
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
handleBackTop
,
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
const
columnStore
=
useColumnStore
()
const
{
columnList
}
=
storeToRefs
(
columnStore
)
const
section2Items
=
ref
([
{
id
:
4
,
title
:
'宿主,你的负面清单该更新啦!'
,
description
:
'宿主你好呀!正在文化一本分与平常心的知识,我们一起来学习吧的知识。'
,
image
:
'https://via.placeholder.com/300x200/DDA0DD/FFFFFF?text=Image4'
,
tag
:
'推荐'
,
views
:
'300'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 12:01'
,
},
{
id
:
5
,
title
:
'不打破惯性思维,永远无法创新'
,
description
:
'当我们的思路被惯性思维束缚了,想要得到一些不一样的结果就会变得很困难。但是,我们可以通过一些方法来打破惯性思维。'
,
image
:
'https://via.placeholder.com/300x200/F0E68C/FFFFFF?text=Image5'
,
tag
:
'推荐'
,
views
:
'280'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 12:10'
,
},
const
{
list
,
total
,
searchParams
,
goToPage
,
changePageSize
,
loading
}
=
usePageSearch
(
getColumnList
,
{
id
:
6
,
title
:
'文化共创|你提意见"我听你"'
,
description
:
'巧妙设手段第三名,出让各有所需满意的额外为为你们带来更多一些精彩。'
,
image
:
'https://via.placeholder.com/300x200/FFA07A/FFFFFF?text=Image6'
,
tag
:
'推荐'
,
views
:
'280'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 14:28'
,
defaultSize
:
3
,
},
]
)
)
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/interviewList.vue
View file @
4a88ffa9
<
template
>
<div>
<div
v-loading=
"loading"
v-if=
"list.length"
>
<div
class=
"w-full max-w-6xl mx-auto"
>
<div
v-for=
"item in interviewL
ist"
:key=
"item.id
"
v-for=
"(item, index) in l
ist"
:key=
"index
"
class=
"bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
:style=
"
{ '--dynamic-color': item.color }"
>
...
...
@@ -23,94 +25,112 @@
</div>
<div
class=
"p-4"
>
<div
class=
"grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div
v-for=
"item in section2Items"
:key=
"item.id"
class=
"group cursor-pointer"
>
<div
v-if=
"item.yaColumnVoList.length"
class=
"grid grid-cols-1 md:grid-cols-3 gap-4"
>
<div
v-for=
"i in item.yaColumnVoList"
:key=
"i.articleId"
class=
"group cursor-pointer"
@
click=
"router.push(`/postDetail/$
{i.articleId}`)"
>
<div
class=
"relative mb-3 overflow-hidden rounded-lg"
>
<img
src=
"@/assets/img/culture/ask.png"
:alt=
"item.title"
:src=
"i.faceUrl"
class=
"w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div
class=
"absolute top-2 left-2"
>
<el-tag
size=
"small"
class=
"bg-orange-500 text-white border-none"
>
{{
item
.
tag
}}
1111111
</el-tag>
</div>
</div>
<h3
class=
"text-sm font-medium text-gray-800 mb-2 transition-colors"
>
{{
item
.
title
}}
{{
i
.
title
}}
</h3>
<p
class=
"text-xs text-gray-500 mb-3 line-clamp-2"
>
{{
item
.
description
}}
{{
i
.
content
}}
</p>
<div
class=
"flex items-center justify-between text-xs text-gray-400"
>
<div
class=
"flex items-center space-x-4"
>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><View
/></el-icon>
{{
item
.
views
}}
{{
i
.
viewCount
}}
</span>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><ChatDotRound
/></el-icon>
{{
item
.
comments
}}
{{
i
.
replyCount
}}
</span>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><Star
/></el-icon>
{{
item
.
likes
}}
{{
i
.
collectCount
}}
</span>
</div>
<span>
{{
item
.
date
}}
</span>
<span>
{{
dayjs
(
i
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</div>
</div>
</div>
<div
v-else
class=
"flex items-center justify-center h-48"
>
<el-empty
description=
"暂无数据"
/>
</div>
</div>
</div>
</div>
<div
class=
"bottom-pagination backdrop-blur-8 border-t border-gray-200"
>
<div
class=
"max-w-7xl mx-auto py-6"
>
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[15, 30, 45, 60]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
}
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<template
v-else
>
<div
class=
"flex items-center justify-center h-full"
>
<el-empty
description=
"暂无数据"
/>
</div>
</
template
>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ArrowRight
,
View
,
ChatDotRound
,
Star
}
from
'@element-plus/icons-vue'
import
{
useInterviewStore
}
from
'@/stores/interview'
import
{
storeToRefs
}
from
'pinia'
import
{
getInterviewList
}
from
'@/api'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
useRouter
}
from
'vue-router'
import
dayjs
from
'dayjs'
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
handleBackTop
,
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
const
interviewStore
=
useInterviewStore
()
const
{
interviewList
}
=
storeToRefs
(
interviewStore
)
const
section2Items
=
ref
([
{
id
:
4
,
title
:
'宿主,你的负面清单该更新啦!'
,
description
:
'宿主你好呀!正在文化一本分与平常心的知识,我们一起来学习吧的知识。'
,
image
:
'https://via.placeholder.com/300x200/DDA0DD/FFFFFF?text=Image4'
,
tag
:
'推荐'
,
views
:
'300'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 12:01'
,
},
{
id
:
5
,
title
:
'不打破惯性思维,永远无法创新'
,
description
:
'当我们的思路被惯性思维束缚了,想要得到一些不一样的结果就会变得很困难。但是,我们可以通过一些方法来打破惯性思维。'
,
image
:
'https://via.placeholder.com/300x200/F0E68C/FFFFFF?text=Image5'
,
tag
:
'推荐'
,
views
:
'280'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 12:10'
,
},
const
{
list
,
total
,
searchParams
,
goToPage
,
changePageSize
,
loading
}
=
usePageSearch
(
getInterviewList
,
{
id
:
6
,
title
:
'文化共创|你提意见"我听你"'
,
description
:
'巧妙设手段第三名,出让各有所需满意的额外为为你们带来更多一些精彩。'
,
image
:
'https://via.placeholder.com/300x200/FFA07A/FFFFFF?text=Image6'
,
tag
:
'推荐'
,
views
:
'280'
,
comments
:
'0'
,
likes
:
'24'
,
date
:
'2025-04-13 14:28'
,
defaultSize
:
3
,
},
]
)
)
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/index.vue
View file @
4a88ffa9
<
template
>
<div
class=
"min-h-screen"
>
<div>
<!-- 头部Tabs -->
<div
class=
"header h-40px items-center justify-between"
>
<div
class=
"left flex gap-3 flex items-center"
>
...
...
@@ -11,35 +11,9 @@
</div>
</div>
<el-divider
style=
"margin: 10px 0 20px 0"
/>
<ColumnList
v-if=
"activeTab === '专栏'"
/>
<InterviewList
v-if=
"activeTab === '专访'"
/>
<PracticeList
v-if=
"activeTab === '实践'"
/>
<!-- 底部分页 -->
<div
class=
"bottom-pagination backdrop-blur-8 border-t border-gray-200"
>
<div
class=
"max-w-7xl mx-auto px-8 py-6"
>
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"currentPage"
v-model:page-size=
"pageSize"
:page-sizes=
"[15, 30, 45, 60]"
layout=
"prev, pager, next, jumper, total"
:total=
"400"
class=
"custom-pagination"
/>
</div>
</div>
</div>
</div>
</div>
<keep-alive>
<component
:is=
"curretComponent"
/>
</keep-alive>
</div>
</
template
>
...
...
@@ -50,14 +24,14 @@ import { Refresh } from '@element-plus/icons-vue'
import
ColumnList
from
'./components/columnList.vue'
import
InterviewList
from
'./components/interviewList.vue'
import
PracticeList
from
'./components/practiceList.vue'
import
{
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
const
curretComponent
=
computed
(()
=>
{
return
{
专栏
:
ColumnList
,
专访
:
InterviewList
,
实践
:
PracticeList
,
}[
activeTab
.
value
]
})
const
activeTab
=
ref
(
'专栏'
)
const
tabs
=
[
...
...
@@ -66,12 +40,5 @@ const tabs = [
{
label
:
'专访'
,
value
:
'专访'
},
{
label
:
'视频'
,
value
:
'视频'
},
]
const
handleBackTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
'smooth'
,
})
}
</
script
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/postDetail/components/actionButtons.vue
View file @
4a88ffa9
...
...
@@ -12,7 +12,7 @@
>
<div
class=
"flex flex-col items-center justify-center w-56px h-56px transition-all duration-200 group"
@
click=
"handleClick(item)"
@
click=
"handleClick(item
as StatItem
)"
>
<el-icon
class=
"group-hover:text-blue-500! cursor-pointer"
...
...
@@ -39,11 +39,10 @@ import { Star, ChatLineSquare, Pointer } from '@element-plus/icons-vue'
import
type
{
ArticleItemDto
}
from
'@/api'
import
type
{
Component
}
from
'vue'
import
{
useScrollTop
}
from
'@/hooks'
import
{
addOrCance
lCollect
}
from
'@/api'
import
{
addOrCance
ArticlelCollect
,
addOrCanceArticlelLike
}
from
'@/api'
import
{
COMMENT_REF_KEY
}
from
'@/constants'
const
{
articleDetail
}
=
defineProps
<
{
articleDetail
:
ArticleItemDto
}
>
()
const
modelValue
=
defineModel
<
ArticleItemDto
>
(
'modelValue'
,
{
required
:
true
})
const
commentRef
=
inject
(
COMMENT_REF_KEY
)
...
...
@@ -55,38 +54,52 @@ interface StatItem {
count
:
number
label
:
string
active
?:
boolean
actionFn
?:
()
=>
Promise
<
boolean
>
actionFn
?:
()
=>
Promise
<
void
>
}
const
stats
=
computed
(()
=>
{
return
[
{
icon
:
Pointer
,
count
:
articleDetail
?.
praiseCount
,
count
:
modelValue
.
value
?.
praiseCount
??
0
,
label
:
'点赞'
,
active
:
articleDetail
?.
viewCount
,
active
:
modelValue
.
value
?.
hasPraised
,
api
:
addOrCanceArticlelLike
,
async
actionFn
()
{
await
addOrCanceArticlelLike
(
modelValue
.
value
.
id
)
// 前端直接本地修改
const
isAdd
=
!
modelValue
.
value
.
hasPraised
modelValue
.
value
.
hasPraised
=
isAdd
modelValue
.
value
.
praiseCount
=
isAdd
?
modelValue
.
value
.
praiseCount
+
1
:
modelValue
.
value
.
praiseCount
-
1
ElMessage
.
success
(
isAdd
?
'点赞成功'
:
'取消点赞成功'
)
},
},
{
icon
:
Star
,
count
:
articleDetail
?.
collectionCount
,
count
:
modelValue
.
value
?.
collectionCount
??
0
,
label
:
'收藏'
,
active
:
articleDetail
?.
isRecommend
,
api
:
addOrCancelCollect
,
async
actionFn
(
item
:
StatItem
)
{
const
res
=
await
addOrCancelCollect
({
articleId
:
articleDetail
.
id
,
type
:
articleDetail
.
type
,
})
if
(
res
)
{
item
.
active
=
!
item
.
active
}
active
:
modelValue
.
value
?.
hasCollect
,
api
:
addOrCanceArticlelCollect
,
async
actionFn
()
{
await
addOrCanceArticlelCollect
(
modelValue
.
value
.
id
)
// 前端直接本地修改
const
isAdd
=
!
modelValue
.
value
.
hasCollect
modelValue
.
value
.
hasCollect
=
isAdd
modelValue
.
value
.
collectionCount
=
isAdd
?
modelValue
.
value
.
collectionCount
+
1
:
modelValue
.
value
.
collectionCount
-
1
ElMessage
.
success
(
isAdd
?
'收藏成功'
:
'取消收藏成功'
)
},
},
{
icon
:
ChatLineSquare
,
count
:
articleDetail
?.
replyCount
,
count
:
modelValue
.
value
?.
replyCount
??
0
,
label
:
'评论'
,
active
:
articleDetail
?.
isRecommend
,
// active: modelValue.value?.replyCount > 0
,
actionFn
:
handleBackTop
,
},
]
...
...
src/views/postDetail/index.vue
View file @
4a88ffa9
<
template
>
<div
class=
"min-h-screen px-20"
>
<!-- 主内容区 -->
<ActionButtons
:articleDetai
l=
"articleDetail"
></ActionButtons>
<ActionButtons
v-mode
l=
"articleDetail"
></ActionButtons>
<div
class=
"lg:col-span-3"
>
<!-- 帖子主体 -->
<div
class=
"bg-white
/90
backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 overflow-hidden"
class=
"bg-white backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 overflow-hidden"
>
<!-- 发布者信息 -->
<div
class=
"p-6 border-b border-gray-100"
>
<div
class=
"flex items-center gap-4"
>
<div
class=
"relative"
>
<img
src=
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face
"
:src=
"articleDetail?.createUserAvatar
"
alt=
""
class=
"w-12 h-12 rounded-full object-cover"
/>
...
...
@@ -24,7 +24,7 @@
</div>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2"
>
<h3
class=
"font-semibold text-gray-800"
>
前端小李
</h3>
<h3
class=
"font-semibold text-gray-800"
>
{{
articleDetail
?.
createUserName
}}
</h3>
<span
class=
"px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
>
...
...
@@ -36,11 +36,11 @@
·
{{
articleDetail
?.
viewCount
||
0
}}
阅读
</p>
</div>
<button
<
!--
<
button
class=
"px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
>
+ 关注
</button>
</button>
-->
</div>
</div>
...
...
@@ -50,57 +50,41 @@
{{
articleDetail
?.
title
}}
</h1>
<!-- 标签 -->
<div
class=
"flex flex-wrap gap-2 mb-6"
>
<span
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
# Vue3
</span>
<span
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
# UnoCSS
</span>
<span
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
# 前端开发
</span>
<span
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
# 最佳实践
</span>
</div>
<!-- 文章内容 -->
<div
class=
"prose prose-lg max-w-none"
>
<div
class=
"text-gray-700 leading-relaxed space-y-4"
>
{{
articleDetail
?.
description
}}
{{
articleDetail
?.
content
}}
</div>
<!-- 图片内容 -->
<div
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<div
v-if=
"articleDetail.imgUrl"
class=
"grid grid-cols-1 md:grid-cols-2 gap-4 mt-6"
>
<img
src=
"https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=500&h=300&fit=crop"
alt=
""
class=
"rounded-xl object-cover w-full h-64 hover:scale-105 transition-transform cursor-pointer"
/>
<img
src=
"https://images.unsplash.com/photo-1627398242454-45a1465c2479?w=500&h=300&fit=crop"
v-for=
"item in articleDetail.imgUrl.split(',')"
:key=
"item"
:src=
"item"
alt=
""
class=
"rounded-xl object-cover w-full h-64 hover:scale-105 transition-transform cursor-pointer"
/>
</div>
</div>
<!-- 标签 -->
<div
class=
"flex flex-wrap gap-2 mt-6"
>
<span
v-for=
"item in articleDetail?.tagNameList"
:key=
"item"
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
#
{{
item
}}
</span>
</div>
</div>
</div>
<!-- 评论区 -->
<div
ref=
"commentRef"
class=
"mt-6 bg-white
/90
backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 overflow-hidden"
class=
"mt-6 bg-white backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 overflow-hidden"
>
<!-- 评论筛选 -->
<div
class=
"p-4 border-b border-gray-100"
>
...
...
@@ -156,7 +140,7 @@
<button
class=
"cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled=
"!comment.trim()"
@
click=
"handleComment"
@
click=
"handleComment
()
"
>
发表
</button>
...
...
@@ -166,81 +150,53 @@
</div>
<!-- 评论列表 -->
<div
class=
"divide-y divide-gray-100"
>
<
!-- 评论1 --
>
<div
class=
"divide-y divide-gray-100"
v-if=
"list.length"
>
<
div
v-for=
"item in list"
:key=
"item.id"
>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<img
:src=
"item.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
前端大佬
</span>
<span
class=
"font-semibold text-gray-800"
>
{{
item
.
replyUser
}}
</span>
<span
class=
"px-2 py-0.5 text-xs bg-gradient-to-r from-purple-100 to-blue-100 text-purple-600 rounded-full"
>
技术专家
</span>
<span
class=
"px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>
置顶
</span>
</div>
<p
class=
"text-gray-700 mb-3"
>
写得很好!Vue 3 + UnoCSS 确实是一个很棒的组合,我在项目中也有类似的实践。特别是
Composition API 的使用,让代码组织变得更加清晰。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
1小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
24
</span>
</button>
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(1)"
<span
class=
"px-2 py-0.5 text-xs bg-red-100 text-red-600 rounded-full"
>
置顶
</span
>
回复
</button>
</div>
</div>
<!-- 回复列表 -->
<div
class=
"mt-3 ml-4 space-y-3"
>
<div
class=
"flex gap-2 p-3 bg-gray-50 rounded-lg"
>
<img
src=
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-1"
>
<span
class=
"font-medium text-sm text-gray-800"
>
前端小李
</span>
<span
class=
"text-xs text-gray-500"
>
30分钟前
</span>
</div>
<p
class=
"text-sm text-gray-700"
>
谢谢认可!确实这个组合在开发效率上提升很大。
<!-- 换行 -->
<p
class=
"text-gray-700 mb-3 break-all"
>
{{
item
.
content
}}
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
1小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
<!--
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
-->
<div
class=
"flex items-center gap-1 cursor-pointer"
@
click=
"handleLickComment(item)"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
24
</span>
</button>
<el-icon
:size=
"16"
:style=
"
{ color: item.hasPraise ? '#409eff' : '#606266' }"
>
<Pointer
/>
</el-icon>
<span>
{{
item
.
postPriseCount
}}
</span>
</div>
<!--
</button>
-->
<button
@
click=
"handleReply(1)"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(item)"
>
回复
</button>
</div>
</div>
</div>
</div>
<div
v-show=
"currentId === 1"
class=
"flex gap-3"
>
<!--
<div
v-show=
"currentId === item.id"
class=
"flex gap-3"
>
<img
:src=
"userInfo?.avatar"
alt=
""
...
...
@@ -265,74 +221,52 @@
<button
class=
"cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled=
"!comment.trim()"
@
click=
"handleComment"
@
click=
"handleComment
(item.id)
"
>
发表
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<el-divider
/>
<!-- 评论2 -->
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
-->
<!-- 回复列表 -->
<div
class=
"mt-3 ml-4 space-y-3"
>
<div
class=
"flex gap-2 p-3 bg-gray-50 rounded-lg"
>
<img
src=
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face
"
alt=
"
"
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
v-if=
"item.children.length"
class=
"mt-3 ml-4 space-y-3"
>
<div
v-for=
"child in item.children"
:key=
"child.id
"
class=
"flex gap-2 p-3 bg-gray-50 rounded-lg
"
>
<img
:src=
"child.avatar"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-1"
>
<span
class=
"font-medium text-sm text-gray-800"
>
前端小李
</span>
<span
class=
"text-xs text-gray-500"
>
30分钟前
</span>
<span
class=
"font-medium text-sm text-gray-800"
>
{{
child
.
replyUser
}}
</span>
<span
class=
"text-xs text-gray-500"
>
{{
dayjs
(
child
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</div>
<p
class=
"text-sm text-gray-700"
>
谢谢认可!确实这个组合在开发效率上提升很大。
{{
child
.
content
}}
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
1小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
<span>
{{
dayjs
(
child
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
<div
class=
"flex items-center gap-1 cursor-pointer"
@
click=
"handleLickComment(child)"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
24
</span>
</button>
<el-icon
:size=
"16"
:style=
"
{ color: child.hasPraise ? '#409eff' : '#606266' }"
>
<Pointer
/>
</el-icon>
<span>
{{
child
.
postPriseCount
}}
</span>
</div>
<button
@
click=
"handleReply(1
)"
@
click=
"handleReply(child
)"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
>
回复
...
...
@@ -341,7 +275,8 @@
</div>
</div>
</div>
<div
v-show=
"currentId === 1"
class=
"flex gap-3"
>
</div>
<div
v-show=
"showCommentBox(item)"
class=
"flex gap-3"
>
<img
:src=
"userInfo?.avatar"
alt=
""
...
...
@@ -366,7 +301,7 @@
<button
class=
"cursor-pointer disabled:opacity-50 px-6 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
:disabled=
"!comment.trim()"
@
click=
"handleComment"
@
click=
"handleComment
(item.id)
"
>
发表
</button>
...
...
@@ -376,214 +311,20 @@
</div>
</div>
</div>
<el-divider
/>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(2)"
>
回复
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
<!-- 底部分页 -->
<!-- 靠右侧 -->
<div
class=
"flex justify-end"
>
<div
class=
"w-fit"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:total=
"total"
@
current-change=
"goToPage"
@
size-change=
"changePageSize"
layout=
"prev, pager, next, total"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-semibold text-gray-800"
>
Vue开发者
</span>
<span
class=
"px-2 py-0.5 text-xs bg-orange-100 text-orange-600 rounded-full"
>
热门
</span
>
</div>
<p
class=
"text-gray-700 mb-3"
>
能分享一下具体的项目配置吗?我在集成 UnoCSS 的时候遇到了一些问题。
</p>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2小时前
</span>
<button
class=
"flex items-center gap-1 hover:text-red-500 transition-colors"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
18
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
@@ -594,11 +335,21 @@
<
script
lang=
"ts"
setup
>
import
dayjs
from
'dayjs'
import
{
getArticleDetail
,
type
ArticleItemDto
}
from
'@/api'
import
{
getArticleDetail
,
type
ArticleItemDto
,
addComment
,
getCommentList
,
addOrCancelCommentLike
,
}
from
'@/api'
import
ActionButtons
from
'./components/actionButtons.vue'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
COMMENT_REF_KEY
}
from
'@/constants'
import
{
BooleanFlag
,
COMMENT_REF_KEY
}
from
'@/constants'
import
{
usePageSearch
}
from
'@/hooks/usePageSearch'
import
{
Pointer
}
from
'@element-plus/icons-vue'
import
type
{
CommentItemDto
}
from
'@/api/article/types'
const
userStore
=
useUserStore
()
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
{
userInfo
}
=
storeToRefs
(
userStore
)
...
...
@@ -611,22 +362,100 @@ const comment = ref('')
const
currentId
=
ref
(
-
1
)
const
fetchArticleDetail
=
async
()
=>
{
const
{
data
}
=
await
getArticleDetail
(
id
)
articleDetail
.
value
=
data
const
{
list
,
searchParams
,
goToPage
,
changePageSize
,
total
,
reset
}
=
usePageSearch
(
getCommentList
,
{
defaultParams
:
{
articleId
:
id
,
},
},
)
const
initPage
=
()
=>
{
Promise
.
allSettled
([
getArticleDetail
(
id
)]).
then
(([
r1
])
=>
{
if
(
r1
.
status
===
'fulfilled'
)
{
articleDetail
.
value
=
r1
.
value
.
data
}
})
}
const
handleLickComment
=
async
(
item
:
CommentItemDto
)
=>
{
await
addOrCancelCommentLike
(
item
.
id
)
if
(
item
.
hasPraise
===
BooleanFlag
.
YES
)
{
ElMessage
.
success
(
'取消点赞成功'
)
item
.
postPriseCount
--
item
.
hasPraise
=
BooleanFlag
.
NO
}
else
{
ElMessage
.
success
(
'点赞成功'
)
item
.
postPriseCount
++
item
.
hasPraise
=
BooleanFlag
.
YES
}
}
const
handleReply
=
(
id
:
number
)
=>
{
const
currentParentCommentId
=
ref
(
0
)
const
currentSonCommentId
=
ref
(
0
)
const
handleReply
=
(
item
:
CommentItemDto
)
=>
{
if
(
item
.
pid
)
{
// 点击的是子评论
if
(
currentSonCommentId
.
value
)
{
// 置为空
if
(
currentSonCommentId
.
value
!==
item
.
id
)
{
currentSonCommentId
.
value
=
item
.
id
currentParentCommentId
.
value
=
item
.
pid
}
else
{
currentSonCommentId
.
value
=
0
currentParentCommentId
.
value
=
0
}
}
else
{
currentSonCommentId
.
value
=
item
.
id
currentParentCommentId
.
value
=
item
.
pid
}
}
else
{
// 点击的是父评论
if
(
currentParentCommentId
.
value
)
{
// 置为空
if
(
currentParentCommentId
.
value
!==
item
.
id
)
{
currentParentCommentId
.
value
=
item
.
id
}
else
{
currentParentCommentId
.
value
=
0
}
}
else
{
currentParentCommentId
.
value
=
item
.
id
currentSonCommentId
.
value
=
0
}
}
comment
.
value
=
''
currentId
.
value
=
id
console
.
log
(
'parent'
,
currentParentCommentId
.
value
,
'son'
,
currentSonCommentId
.
value
)
}
const
showCommentBox
=
(
item
:
CommentItemDto
)
=>
{
if
(
currentParentCommentId
.
value
&&
currentSonCommentId
.
value
)
{
// 说明在评论子评论
return
(
item
.
id
===
currentParentCommentId
.
value
&&
item
.
children
?.
some
((
i
)
=>
i
.
id
===
currentSonCommentId
.
value
)
)
}
else
if
(
currentParentCommentId
.
value
)
{
// 说明在评论父评论
return
item
.
id
===
currentParentCommentId
.
value
}
}
const
handleComment
=
async
()
=>
{
const
handleComment
=
async
(
pid
?:
number
)
=>
{
console
.
log
(
comment
.
value
)
const
res
=
await
addComment
({
articleId
:
id
,
content
:
comment
.
value
,
...(
pid
?
{
pid
}
:
{}),
})
console
.
log
(
res
)
ElMessage
.
success
(
'发表评论成功'
)
reset
()
comment
.
value
=
''
}
provide
(
COMMENT_REF_KEY
,
commentRef
)
onMounted
(
async
()
=>
{
fetchArticleDetail
()
initPage
()
})
</
script
>
src/views/publishVideo/index.vue
View file @
4a88ffa9
...
...
@@ -85,10 +85,10 @@
</el-form-item>
</div>
<div
class=
"mb-8"
>
<el-form-item
prop=
"
description
"
>
<el-form-item
prop=
"
content
"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
视频简介
</label>
<el-input
v-model=
"form.
description
"
v-model=
"form.
content
"
type=
"textarea"
:rows=
"5"
placeholder=
"请写上您希望介绍的内容,让更多的人了解您的作品吧!"
...
...
@@ -201,7 +201,7 @@
<
script
setup
lang=
"ts"
>
import
UploadVideo
from
'@/components/common/UploadVideo/index.vue'
import
{
useResetData
}
from
'@/hooks'
import
{
ArticleTypeEnum
,
SendTypeEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
,
ReleaseStatusTypeEnum
,
SendTypeEnum
}
from
'@/constants'
import
{
addOrUpdateArticle
}
from
'@/api'
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
...
...
@@ -213,11 +213,12 @@ const [form] = useResetData({
videoUrl
:
''
,
title
:
'视频标题'
,
type
:
ArticleTypeEnum
.
VIDEO
,
description
:
''
,
content
:
''
,
mainTagId
:
''
,
tagList
:
[],
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
})
const
filterTagsFn
=
(
tags
:
TagItemDto
[])
=>
{
...
...
@@ -227,7 +228,7 @@ const filterTagsFn = (tags: TagItemDto[]) => {
const
rules
=
{
videoUrl
:
[{
required
:
true
,
message
:
'请上传视频'
,
trigger
:
'change'
}],
title
:
[{
required
:
true
,
message
:
'请输入视频标题'
,
trigger
:
'blur'
}],
description
:
[{
required
:
true
,
message
:
'请输入视频简介'
,
trigger
:
'blur'
}],
content
:
[{
required
:
true
,
message
:
'请输入视频简介'
,
trigger
:
'blur'
}],
mainTagId
:
[{
required
:
true
,
message
:
'请选择标签'
,
trigger
:
'change'
}],
}
...
...
src/views/userPage/index.vue
View file @
4a88ffa9
...
...
@@ -167,9 +167,12 @@ import { hasOfficialAccount } from '@/api'
const
editUserInfoRef
=
useTemplateRef
<
InstanceType
<
typeof
EditUserInfo
>>
(
'editUserInfoRef'
)
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
console
.
log
(
userInfo
.
value
)
const
route
=
useRoute
()
const
key
=
route
.
query
.
key
as
string
console
.
log
(
key
)
// 当前激活的菜单
const
activeMenu
=
ref
(
'posts'
)
const
activeMenu
=
ref
(
key
||
'posts'
)
// 当前激活的标签页
const
activeTab
=
ref
(
'published'
)
...
...
src/views/videoDetail/index.vue
View file @
4a88ffa9
<
template
>
<div
class=
"min-h-screen
bg-gray-5
0"
>
<!--
主容器
-->
<div
class=
"
max-w-6xl mx-auto px-4 py-6
"
>
<div
class=
"min-h-screen
px-2
0"
>
<!--
左侧主内容区
-->
<div
class=
"
lg:col-span-3
"
>
<!-- 视频播放器 -->
<div
class=
"relative w-full aspect-video bg-black rounded-lg overflow-hidden mb-4"
>
<div
class=
"absolute inset-0"
>
<!-- 模拟视频内容 -->
<div
class=
"w-full h-full bg-gradient-to-br from-gray-800 to-black relative"
>
<!-- 视频标题叠加 -->
<div
class=
"absolute inset-0 flex items-center justify-center"
>
<div
class=
"text-center"
>
<h1
class=
"text-4xl md:text-6xl font-bold text-yellow-400 mb-4 drop-shadow-lg"
>
《黑神话:钟馗》
</h1>
<p
class=
"text-xl text-white drop-shadow"
>
里的钟馗,有着怎样的身世?
</p>
<div
class=
"bg-black rounded-2xl overflow-hidden shadow-lg"
>
<video
ref=
"videoRef"
:src=
"videoDetail?.videoUrl"
class=
"w-full aspect-video"
controls
@
play=
"handlePlay"
@
pause=
"handlePause"
></video>
</div>
<!-- 视频信息卡片 -->
<div
class=
"mt-4 bg-white backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 p-6"
>
<!-- 标题和标签 -->
<h1
class=
"text-2xl font-bold text-gray-900 mb-4"
>
{{
videoDetail
?.
title
}}
</h1>
<div
class=
"flex flex-wrap gap-2 mb-6"
>
<span
v-for=
"tag in videoDetail?.tags"
:key=
"tag"
class=
"px-3 py-1 text-sm bg-gradient-to-r from-cyan-100 to-blue-100 text-cyan-600 rounded-full hover:shadow-md transition-all cursor-pointer"
>
#
{{
tag
}}
</span>
</div>
<!-- 播放控制栏
-->
<!-- 视频数据统计
-->
<div
class=
"absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-4
"
class=
"flex items-center gap-6 text-sm text-gray-600 mb-6 pb-6 border-b border-gray-100
"
>
<div
class=
"flex items-center justify-between text-white"
>
<div
class=
"flex items-center space-x-4"
>
<button
class=
"hover:text-blue-400 transition-colors"
>
⏮️
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
▶️
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
⏭️
</button>
<span
class=
"text-sm"
>
00:00 / 10:35
</span>
</div>
<div
class=
"flex items-center space-x-3"
>
<button
class=
"hover:text-blue-400 transition-colors"
>
自动
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
倍速
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
字幕
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
🔊
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
⚙️
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
📺
</button>
<button
class=
"hover:text-blue-400 transition-colors"
>
⛶
</button>
</div>
<div
class=
"flex items-center gap-2"
>
<i
class=
"i-carbon-view text-lg"
></i>
<span>
{{
formatNumber
(
videoDetail
?.
viewCount
)
}}
播放
</span>
</div>
<div
class=
"flex items-center gap-2"
>
<i
class=
"i-carbon-thumbs-up text-lg"
></i>
<span>
{{
formatNumber
(
videoDetail
?.
likeCount
)
}}
</span>
</div>
<div
class=
"flex items-center gap-2"
>
<i
class=
"i-carbon-star text-lg"
></i>
<span>
{{
formatNumber
(
videoDetail
?.
collectCount
)
}}
收藏
</span>
</div>
<div
class=
"flex items-center gap-2"
>
<i
class=
"i-carbon-share text-lg"
></i>
<span>
{{
formatNumber
(
videoDetail
?.
shareCount
)
}}
分享
</span>
</div>
<span
class=
"text-gray-400"
>
{{
dayjs
(
videoDetail
?.
publishTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</div>
<!-- 视频信息区域 -->
<div
class=
"bg-white rounded-lg p-6 mb-4 shadow-sm"
>
<!-- 标题和基本信息 -->
<div
class=
"mb-4"
>
<h1
class=
"text-xl md:text-2xl font-bold text-gray-900 mb-3 leading-tight"
>
《黑神话:钟馗》里的钟馗,有着怎样的身世?
</h1>
<div
class=
"flex flex-wrap items-center gap-4 text-sm text-gray-600 mb-4"
>
<div
class=
"flex items-center gap-1"
>
<span>
▶️
</span>
<span>
63.2万
</span>
</div>
<div
class=
"flex items-center gap-1"
>
<span>
💬
</span>
<span>
589
</span>
</div>
<div
class=
"text-gray-500"
>
2025-08-20 12:24:18
</div>
<div
class=
"flex items-center gap-1 text-blue-500"
>
<span>
#文化标签
</span>
</div>
</div>
<!-- UP主信息和操作按钮 -->
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-4"
>
<img
:src=
"videoDetail?.authorAvatar"
alt=
""
class=
"w-12 h-12 rounded-full object-cover cursor-pointer hover:opacity-80 transition-opacity"
/>
<div>
<h3
class=
"font-semibold text-gray-800 hover:text-blue-500 cursor-pointer transition-colors"
>
{{
videoDetail
?.
authorName
}}
</h3>
<p
class=
"text-sm text-gray-500"
>
{{
formatNumber
(
videoDetail
?.
fansCount
)
}}
粉丝
</p>
</div>
<!-- 互动按钮区域 -->
<div
class=
"flex flex-wrap items-center justify-between gap-4 mb-4"
>
<div
class=
"flex flex-wrap items-center gap-3"
>
<button
class=
"flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full transition-colors"
class=
"px-6 py-2 bg-gradient-to-r from-pink-500 to-red-500 text-white rounded-full text-sm hover:shadow-lg transition-all"
@
click=
"handleFollow"
>
<span
>
👍
</span>
<span
class=
"font-medium"
>
1.1万
</span>
<span
v-if=
"!videoDetail?.hasFollowed"
>
+ 关注
</span>
<span
v-else
>
已关注
</span>
</button>
</div>
<div
class=
"flex items-center gap-3"
>
<button
class=
"flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full transition-colors border-2 border-blue-500"
class=
"flex items-center gap-2 px-4 py-2 rounded-full transition-all"
:class=
"
videoDetail?.hasLiked
? 'bg-blue-50 text-blue-500'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
"
@
click=
"handleLike"
>
<div
class=
"w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center"
>
<span
class=
"text-white text-sm"
>
💰
</span>
</div>
<span
class=
"font-medium text-blue-500"
>
1121
</span>
<i
class=
"i-carbon-thumbs-up text-lg"
></i>
<span>
{{
videoDetail
?.
hasLiked
?
'已赞'
:
'点赞'
}}
</span>
</button>
<button
class=
"flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full transition-colors"
class=
"flex items-center gap-2 px-4 py-2 rounded-full transition-all"
:class=
"
videoDetail?.hasCollected
? 'bg-yellow-50 text-yellow-600'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
"
@
click=
"handleCollect"
>
<
span>
⭐
</span
>
<span
class=
"font-medium"
>
4341
</span>
<
i
class=
"i-carbon-star text-lg"
></i
>
<span
>
{{
videoDetail
?.
hasCollected
?
'已收藏'
:
'收藏'
}}
</span>
</button>
<button
class=
"flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-full transition-colors"
class=
"flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-600 rounded-full hover:bg-gray-200 transition-all"
@
click=
"handleShare"
>
<
span>
📤
</span
>
<span
class=
"font-medium"
>
488
</span>
<
i
class=
"i-carbon-share text-lg"
></i
>
<span
>
分享
</span>
</button>
</div>
</div>
<!-- UP主信息 -->
<div
class=
"flex items-center justify-between p-4 bg-gray-50 rounded-lg mb-4"
>
<div
class=
"flex items-center gap-3"
>
<div
class=
"w-12 h-12 bg-gradient-to-r from-pink-400 to-purple-500 rounded-full flex items-center justify-center"
>
<span
class=
"text-white font-bold"
>
UP
</span>
<!-- 视频简介 -->
<div
v-if=
"videoDetail?.description"
class=
"mt-6 pt-6 border-t border-gray-100"
>
<div
class=
"text-gray-700 leading-relaxed whitespace-pre-wrap"
>
{{
videoDetail
?.
description
}}
</div>
<div>
<h3
class=
"font-medium text-gray-900"
>
UP主名称
</h3>
<p
class=
"text-sm text-gray-600"
>
粉丝 12.3万 · 获赞 45.6万
</p>
</div>
</div>
<!-- 评论区 -->
<div
ref=
"commentRef"
class=
"mt-6 bg-white backdrop-blur-sm rounded-2xl shadow-sm border border-white/50 overflow-hidden"
>
<!-- 评论头部 -->
<div
class=
"p-6 border-b border-gray-100"
>
<div
class=
"flex items-center justify-between"
>
<span
class=
"text-xl font-bold text-gray-800"
>
评论
{{
formatNumber
(
videoDetail
?.
commentCount
)
}}
</span>
<div
class=
"flex items-center gap-2"
>
<button
class=
"px-6 py-2 bg-pink-500 hover:bg-pink-600 text-white rounded-full transition-colors"
v-for=
"filter in commentFilters"
:key=
"filter.value"
class=
"px-4 py-1.5 text-sm rounded-full transition-all"
:class=
"
activeFilter === filter.value
? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-md'
: 'hover:bg-gray-100 text-gray-600'
"
@
click=
"activeFilter = filter.value"
>
+ 关注
{{
filter
.
label
}}
</button>
</div>
<!-- 评论区 -->
<div
class=
"bg-white rounded-lg p-6 shadow-sm"
>
<div
class=
"flex items-center justify-between mb-6"
>
<h3
class=
"text-lg font-bold flex items-center gap-2"
>
<span>
评论
</span>
<span
class=
"text-blue-500"
>
1171
</span>
</h3>
<div
class=
"flex gap-1"
>
<button
class=
"px-3 py-1 text-sm bg-pink-100 text-pink-600 rounded transition-colors"
>
最热
</button>
<button
class=
"px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors"
>
最新
</div>
</div>
<!-- 发表评论 -->
<div
class=
"p-6 border-b border-gray-100"
>
<div
class=
"flex gap-4"
>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<el-input
v-model=
"commentContent"
type=
"textarea"
placeholder=
"发一条友善的评论"
:rows=
"3"
:maxlength=
"500"
show-word-limit
></el-input>
<div
class=
"flex justify-between items-center mt-3"
>
<div
class=
"flex items-center gap-3"
>
<button
class=
"text-gray-500 hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-face-satisfied text-xl"
></i>
</button>
<button
class=
"px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors"
>
精选
<button
class=
"text-gray-500 hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-image text-xl"
></i>
</button>
</div>
<button
class=
"px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors"
class=
"px-8 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full text-sm hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed"
:disabled=
"!commentContent.trim()"
@
click=
"publishComment()"
>
置顶
发布
</button>
</div>
</div>
<!-- 评论输入框 -->
<div
class=
"flex gap-3 mb-6"
>
<div
class=
"w-10 h-10 bg-teal-400 rounded-full flex items-center justify-center flex-shrink-0"
>
<span
class=
"text-white"
>
👤
</span>
</div>
<div
class=
"flex-1"
>
<input
type=
"text"
placeholder=
"这里是评论区,不是无人区:)"
class=
"w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
/>
</div>
</div>
<!-- 评论列表 -->
<div
class=
"space-y-6"
>
<!-- 评论项 1 -->
<div
class=
"flex gap-3"
>
<div
class=
"divide-y divide-gray-100"
>
<div
class=
"w-10 h-10 bg-red-400 rounded-full flex items-center justify-center flex-shrink-0"
v-for=
"comment in commentList"
:key=
"comment.id"
class=
"p-6 hover:bg-gray-50/50 transition-colors"
>
<span
class=
"text-white"
>
👤
</span
>
<
/div
>
<div
class=
"flex gap-4"
>
<
img
:src=
"comment.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/
>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-medium text-gray-900"
>
秋名
</span>
<span
class=
"bg-red-500 text-white text-xs px-2 py-0.5 rounded"
>
UP主
</span>
</div>
<p
class=
"text-gray-800 mb-2"
>
正值抗日反法西斯胜利80周年之际
<br
/>
《黑神话 钟馗》发布可谓正当其时!🔥
</p>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2025-08-20 12:41
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
>
<span>
👍
</span>
<span>
887
</span>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
👎
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
<span
class=
"font-semibold text-gray-800"
>
{{
comment
.
username
}}
</span>
<span
v-if=
"comment.isAuthor"
class=
"px-2 py-0.5 text-xs bg-gradient-to-r from-pink-100 to-red-100 text-pink-600 rounded"
>
UP主
</span>
<span
v-if=
"comment.isPinned"
class=
"px-2 py-0.5 text-xs bg-yellow-100 text-yellow-600 rounded"
>
置顶
</span>
</div>
<!-- 评论项 2 -->
<div
class=
"flex gap-3"
>
<div
class=
"w-10 h-10 bg-green-400 rounded-full flex items-center justify-center flex-shrink-0"
<p
class=
"text-gray-700 mb-3 leading-relaxed"
>
{{
comment
.
content
}}
</p>
<div
class=
"flex items-center gap-6 text-sm text-gray-500"
>
<span>
{{
dayjs
(
comment
.
createTime
*
1000
).
fromNow
()
}}
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
@
click=
"handleCommentLike(comment)"
>
<span
class=
"text-white"
>
👤
</span>
<i
:class=
"
comment.hasLiked
? 'i-carbon-thumbs-up-filled text-blue-500'
: 'i-carbon-thumbs-up'
"
></i>
<span
:class=
"comment.hasLiked ? 'text-blue-500' : ''"
>
{{
comment
.
likeCount
||
'点赞'
}}
</span>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
@
click=
"handleReply(comment)"
>
回复
</button>
</div>
<!-- 回复输入框 -->
<div
v-show=
"replyingCommentId === comment.id"
class=
"mt-4 flex gap-3"
>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-medium text-gray-900"
>
低昂星残
</span>
<span
class=
"bg-red-500 text-white text-xs px-2 py-0.5 rounded"
>
UP主
</span>
</div>
<p
class=
"text-gray-800 mb-2"
>
我比较期待:黑神话 妲己🤔
</p>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2025-08-20 13:45
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
>
<span>
👍
</span>
<span>
551
</span>
<el-input
v-model=
"replyContent"
type=
"textarea"
:placeholder=
"`回复 @$
{comment.username}`"
:rows="2"
>
</el-input>
<div
class=
"flex justify-end gap-2 mt-2"
>
<button
class=
"px-4 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded-full transition-all"
@
click=
"replyingCommentId = null"
>
取消
</button>
<button
class=
"px-4 py-1.5 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full hover:shadow-lg transition-all disabled:opacity-50"
:disabled=
"!replyContent.trim()"
@
click=
"publishComment(comment.id)"
>
回复
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
👎
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
</div>
<!-- 评论项 3
-->
<div
class=
"flex gap
-3"
>
<!-- 子回复列表
-->
<div
v-if=
"comment.replies?.length"
class=
"mt-4 space-y
-3"
>
<div
class=
"w-10 h-10 bg-purple-400 rounded-full flex items-center justify-center flex-shrink-0"
v-for=
"reply in comment.replies"
:key=
"reply.id"
class=
"flex gap-3 p-3 bg-gray-50 rounded-lg"
>
<span
class=
"text-white"
>
👤
</span>
</div>
<img
:src=
"reply.avatar"
alt=
""
class=
"w-8 h-8 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-medium text-gray-900"
>
希木林M
</span>
<span
class=
"bg-red-500 text-white text-xs px-2 py-0.5 rounded"
>
UP主
</span>
</div>
<p
class=
"text-gray-800 mb-2"
>
这是黑神话😂
</p>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2025-08-20 14:08
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
>
<span>
👍
</span>
<span>
296
</span>
<div
class=
"flex items-center gap-2 mb-1"
>
<span
class=
"font-medium text-sm text-gray-800"
>
{{
reply
.
username
}}
</span>
<span
class=
"text-gray-400 text-xs"
>
回复
</span>
<span
class=
"font-medium text-sm text-gray-800"
>
@
{{
reply
.
replyToUser
}}
</span
>
</div>
<p
class=
"text-sm text-gray-700 mb-2"
>
{{
reply
.
content
}}
</p>
<div
class=
"flex items-center gap-4 text-xs text-gray-500"
>
<span>
{{
dayjs
(
reply
.
createTime
*
1000
).
fromNow
()
}}
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
@
click=
"handleCommentLike(reply)"
>
<i
:class=
"
reply.hasLiked
? 'i-carbon-thumbs-up-filled text-blue-500'
: 'i-carbon-thumbs-up'
"
></i>
<span
:class=
"reply.hasLiked ? 'text-blue-500' : ''"
>
{{
reply
.
likeCount
||
'点赞'
}}
</span>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
@
click=
"handleReply(reply, comment.id)"
>
回复
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
👎
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
</div>
<!-- 评论项 4 -->
<div
class=
"flex gap-3"
>
<div
class=
"w-10 h-10 bg-pink-400 rounded-full flex items-center justify-center flex-shrink-0"
>
<span
class=
"text-white"
>
👤
</span>
</div>
<div
class=
"flex-1"
>
<div
class=
"flex items-center gap-2 mb-2"
>
<span
class=
"font-medium text-gray-900"
>
夏冰
</span>
<span
class=
"bg-red-500 text-white text-xs px-2 py-0.5 rounded"
>
UP主
</span>
</div>
<p
class=
"text-gray-800 mb-2"
>
那还不如叫期待全能游戏本创作的游戏
</p>
<div
class=
"flex items-center gap-4 text-sm text-gray-500"
>
<span>
2025-08-20 15:07
</span>
<button
class=
"flex items-center gap-1 hover:text-blue-500 transition-colors"
>
<span>
👍
</span>
<span>
70
</span>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
👎
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
<div
class=
"text-xs text-gray-500 mt-3"
>
共37条回复,点击查看
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div
class=
"flex justify-center py-6"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:total=
"total"
@
current-change=
"goToPage"
@
size-change=
"changePageSize"
layout=
"prev, pager, next, total"
/>
</div>
</div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
// 可以在这里添加响应式数据和方法
</
script
>
<
script
lang=
"ts"
setup
>
import
dayjs
from
'dayjs'
import
relativeTime
from
'dayjs/plugin/relativeTime'
import
'dayjs/locale/zh-cn'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
usePageSearch
}
from
'@/hooks/usePageSearch'
import
{
getArticleDetail
}
from
'@/api'
<
style
scoped
lang=
"scss"
>
//
自定义样式
.line-clamp-2
{
display
:
-webkit-box
;
-webkit-line-clamp
:
2
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
dayjs
.
extend
(
relativeTime
)
dayjs
.
locale
(
'zh-cn'
)
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
route
=
useRoute
()
const
router
=
useRouter
()
const
videoId
=
route
.
params
.
id
as
string
// 视频详情
const
videoDetail
=
ref
({
title
:
''
,
videoUrl
:
''
,
tags
:
[],
viewCount
:
0
,
likeCount
:
0
,
collectCount
:
0
,
shareCount
:
0
,
commentCount
:
0
,
publishTime
:
0
,
authorName
:
''
,
authorAvatar
:
''
,
fansCount
:
0
,
description
:
''
,
hasFollowed
:
false
,
hasLiked
:
false
,
hasCollected
:
false
,
})
// 评论筛选
const
commentFilters
=
[
{
label
:
'最热'
,
value
:
'hot'
},
{
label
:
'最新'
,
value
:
'new'
},
{
label
:
'置顶'
,
value
:
'pinned'
},
]
const
activeFilter
=
ref
(
'hot'
)
// 评论相关
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
commentContent
=
ref
(
''
)
const
replyContent
=
ref
(
''
)
const
replyingCommentId
=
ref
<
number
|
null
>
(
null
)
// 推荐列表
const
recommendList
=
ref
([])
// 模拟数据和API调用
const
commentList
=
ref
([])
const
{
searchParams
,
goToPage
,
changePageSize
,
total
}
=
usePageSearch
(
// getVideoCommentList,
Promise
.
resolve
as
any
,
{
defaultParams
:
{
videoId
,
},
},
)
// 格式化数字
const
formatNumber
=
(
num
:
number
)
=>
{
if
(
!
num
)
return
0
if
(
num
>=
10000
)
{
return
(
num
/
10000
).
toFixed
(
1
)
+
'万'
}
return
num
}
// 视频播放处理
const
handlePlay
=
()
=>
{
// 记录播放
}
const
handlePause
=
()
=>
{
// 记录暂停
}
// 关注
const
handleFollow
=
()
=>
{
videoDetail
.
value
.
hasFollowed
=
!
videoDetail
.
value
.
hasFollowed
ElMessage
.
success
(
videoDetail
.
value
.
hasFollowed
?
'关注成功'
:
'取消关注'
)
}
// 点赞
const
handleLike
=
()
=>
{
videoDetail
.
value
.
hasLiked
=
!
videoDetail
.
value
.
hasLiked
videoDetail
.
value
.
likeCount
+=
videoDetail
.
value
.
hasLiked
?
1
:
-
1
ElMessage
.
success
(
videoDetail
.
value
.
hasLiked
?
'点赞成功'
:
'取消点赞'
)
}
// 收藏
const
handleCollect
=
()
=>
{
videoDetail
.
value
.
hasCollected
=
!
videoDetail
.
value
.
hasCollected
videoDetail
.
value
.
collectCount
+=
videoDetail
.
value
.
hasCollected
?
1
:
-
1
ElMessage
.
success
(
videoDetail
.
value
.
hasCollected
?
'收藏成功'
:
'取消收藏'
)
}
// 分享
const
handleShare
=
()
=>
{
ElMessage
.
success
(
'分享链接已复制'
)
}
// 发布评论
const
publishComment
=
async
(
parentId
?:
number
)
=>
{
const
content
=
parentId
?
replyContent
.
value
:
commentContent
.
value
// await addVideoComment({ videoId, content, parentId })
ElMessage
.
success
(
'评论发布成功'
)
if
(
parentId
)
{
replyContent
.
value
=
''
replyingCommentId
.
value
=
null
}
else
{
commentContent
.
value
=
''
}
// 刷新评论列表
}
</
style
>
// 回复评论
const
handleReply
=
(
comment
:
any
,
parentId
?:
number
)
=>
{
replyingCommentId
.
value
=
parentId
||
comment
.
id
replyContent
.
value
=
''
}
// 评论点赞
const
handleCommentLike
=
(
comment
:
any
)
=>
{
comment
.
hasLiked
=
!
comment
.
hasLiked
comment
.
likeCount
+=
comment
.
hasLiked
?
1
:
-
1
}
// 跳转视频
const
handleVideoClick
=
(
id
:
number
)
=>
{
router
.
push
(
`/video/
${
id
}
`
)
}
onMounted
(
async
()
=>
{
const
{
data
}
=
await
getArticleDetail
(
videoId
)
console
.
log
(
data
)
videoDetail
.
value
=
data
})
</
script
>
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