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
797a579b
Commit
797a579b
authored
Nov 25, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】 feat: 完善首页、视频发布页、搜索页等相关内容
parent
53921419
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
926 additions
and
526 deletions
+926
-526
recommendList.vue
src/views/homePage/homeTab/components/recommendList.vue
+7
-0
videoList.vue
src/views/homePage/homeTab/components/videoList.vue
+140
-96
columnList.vue
src/views/homePage/yaTab/components/columnList.vue
+4
-4
practiceList.vue
src/views/homePage/yaTab/components/practiceList.vue
+18
-107
publishPractice.vue
src/views/homePage/yaTab/components/publishPractice.vue
+238
-0
selectTagsDialog.vue
src/views/homePage/yaTab/components/selectTagsDialog.vue
+46
-0
index.vue
src/views/publishCase/index.vue
+33
-57
index.vue
src/views/publishVideo/index.vue
+255
-83
index.vue
src/views/searchPage/index.vue
+185
-179
No files found.
src/views/homePage/homeTab/components/recommendList.vue
View file @
797a579b
...
...
@@ -9,6 +9,13 @@
@
click=
"handleClickItem(item)"
>
<div
class=
"flex gap-3 justify-between"
>
<!--
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
-->
<!-- 内容区域 -->
<div
class=
"flex-1 min-w-0 flex flex-col justify-between h-24"
>
<!-- 标题 -->
...
...
src/views/homePage/homeTab/components/videoList.vue
View file @
797a579b
...
...
@@ -3,7 +3,7 @@
<!-- tabs -->
<div
class=
"shadow-sm"
>
<div
class=
"max-w-7xl mx-auto px-4"
>
<div
class=
"flex items-center justify-between p
y
-4"
>
<div
class=
"flex items-center justify-between p
b
-4"
>
<!-- 左侧 Tabs -->
<div
class=
"flex items-center space-x-1"
>
<div
...
...
@@ -25,50 +25,64 @@
</div>
<div
v-loading=
"loading"
>
<!-- 第一页的特殊布局 -->
<
template
v-if=
"searchParams.current === 0
"
>
<
div
v-show=
"searchParams.current === 1
"
>
<!-- 前三个特殊布局 -->
<div
class=
"grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"
>
<!-- 第一个视频 - 占据两列 -->
<div
@
click=
"router.push(`/videoDetail/$
{list[0]?.id}`)"
class="lg:col-span-2 group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-xl transition-all duration-500 cursor-pointer"
>
<div
class=
"relative overflow-hidden"
>
<div
class=
"relative overflow-hidden
mb-5
"
>
<img
src=
"https://picsum.photos/800/400?random=1
"
class=
"w-full h-
72
object-cover group-hover:scale-105 transition-transform duration-700"
:src=
"list[0]?.faceUrl
"
class=
"w-full h-
90
object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent"
></div>
<!-- 标签和数据 -->
<div
<
!--
<
div
class=
"absolute top-4 left-4 bg-gradient-to-r from-orange-500 to-red-500 text-white px-3 py-1.5 rounded-full text-sm font-semibold shadow-lg"
>
🔥 推荐
</div>
-->
<div
v-if=
"list[0]?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<div
class=
"absolute bottom-4 right-4 bg-black/80 backdrop-blur-sm text-white px-3 py-1.5 rounded-lg text-sm"
>
01:34:30
{{
list
[
0
]?.
videoDuration
}}
</div>
<div
class=
"absolute bottom-4 left-4 flex gap-4 text-white"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<
SvgIcon
name=
"icon_play"
size=
"14"
/
>
<span
class=
"text-sm font-medium"
>
1.7万
</span>
<
el-icon
class=
"text-sm"
><View
/></el-icon
>
<span
>
{{
list
[
0
]?.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<SvgIcon
name=
"icon_comment"
size=
"14"
/>
<span
class=
"text-sm font-medium"
>
112
</span>
<el-icon
class=
"text-sm"
><ChatDotRound
/></el-icon>
<span>
{{
list
[
0
]?.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-lg"
>
<el-icon
class=
"text-sm"
><Star
/></el-icon>
<span>
{{
list
[
0
]?.
replyCount
}}
</span>
</div>
</div>
<!-- 播放按钮 -->
<div
<
!--
<
div
class=
"absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
<div
...
...
@@ -76,28 +90,30 @@
>
<SvgIcon
name=
"icon_play"
size=
"24"
color=
"#374151"
/>
</div>
</div>
</div>
-->
</div>
<div
class=
"p-6"
>
<h3
class=
"font-bold text-xl mb-3 text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-
2
"
class=
"font-bold text-xl mb-3 text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-
1
"
>
为什么你的配色"不高级"?
{{
list
[
0
]?.
title
}}
</h3>
<
p
class=
"text-gray-600 mb-4 line-clamp-2 leading-relaxed"
>
【色彩搭配】别让配色毁了你的设计!视觉传达专业暑假改指南,3步让作品集秒变高级...
</
p
>
<
h2
class=
"text-gray-600 mb-4 line-clamp-2 leading-relaxed"
>
{{
list
[
0
]?.
content
}}
</
h2
>
<div
class=
"flex items-center justify-between text-gray-500 text-sm"
>
<div
class=
"flex items-center gap-2"
>
<
div
class=
"w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full flex items-center justify-center text-white text-xs font-bold
"
>
柏
</div
>
<span
class=
"font-medium"
>
平面设计小课堂_柏子
</span>
<
img
:src=
"list[0]?.showAvatar
"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/
>
<span
class=
"font-medium"
>
{{
list
[
0
]?.
showName
}}
</span>
</div>
<span
class=
"text-xs bg-gray-100 px-2 py-1 rounded-full"
>
8-1
</span>
<span
class=
"text-xs bg-gray-100 px-2 py-1 rounded-full"
>
{{
dayjs
((
list
[
0
]?.
createTime
??
0
)
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
</div>
</div>
</div>
...
...
@@ -105,58 +121,85 @@
<!-- 右侧两个视频 -->
<div
class=
"flex flex-col gap-6"
>
<div
v-for=
"
n in 2
"
:key=
"
n
"
v-for=
"
(item, index) in [list[1], list[2]]
"
:key=
"
index
"
class=
"group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer flex-1"
@
click=
"
goVideoDetail(n
)"
@
click=
"
router.push(`/videoDetail/$
{item?.id}`
)"
>
<div
class=
"relative overflow-hidden"
>
<img
:src=
"
`https://picsum.photos/400/200?random=$
{n + 1}`
"
class="w-full h-
36
object-cover group-hover:scale-105 transition-transform duration-500"
: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/
5
0 to-transparent"
></div>
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/
4
0 to-transparent"
></div>
<!-- 标签 -->
<!--
<div
class=
"absolute top-3 left-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-2.5 py-1 rounded-full text-xs font-semibold"
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
class=
"absolute top-2 left-2 bg-gradient-to-r from-blue-500 to-cyan-500 text-white px-2 py-1 rounded-full text-xs font-semibold"
v-if=
"item?.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
{{
n
===
1
?
'数码'
:
'教程'
}}
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-
2 right-2 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded
text-xs"
class=
"absolute bottom-
3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg
text-xs"
>
09:35
{{
item
?.
videoDuration
}}
</div>
<!-- 播放按钮 -->
<div
class=
"absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
<!-- 数据 -->
<div
class=
"absolute bottom-3 left-3 flex gap-3 text-white text-xs"
>
<div
class=
"flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded-lg"
>
<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"
>
<el-icon
class=
"text-sm"
><ChatDotRound
/></el-icon>
<span>
{{
item
?.
replyCount
}}
</span>
</div>
<div
class=
"
w-10 h-10 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center shadow
-lg"
class=
"
flex items-center gap-1 bg-black/50 backdrop-blur-sm px-2 py-1 rounded
-lg"
>
<SvgIcon
name=
"icon_play"
size=
"16"
color=
"#374151"
/>
<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=
"bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center shadow-xl transform scale-90 group-hover:scale-100 transition-transform duration-300"
>
<el-icon
size=
"50"
color=
"#333"
><VideoPlay
/></el-icon>
</div>
</div>
-->
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
n
===
1
?
'复日奶茶'
:
'PS最新版零基础全套'
}}
{{
item
?.
title
}}
</h3>
<p
class=
"text-gray-600 text-sm mb-3 line-clamp-2"
>
{{
n
===
1
?
'Figma+3D 奶茶小卡片|清爽夏日实用指南...'
:
'【PS全套教程】3节课,Photoshop零基础入门...'
}}
</p>
<div
class=
"flex items-center justify-between text-gray-500 text-xs"
>
<span
class=
"font-medium"
>
{{
n
===
1
?
'设计师的日常'
:
'PS教程'
}}
</span>
<span
class=
"bg-gray-100 px-2 py-1 rounded-full"
>
{{
n
===
1
?
'7-29'
:
'7-27'
<div
class=
"flex items-center gap-2"
>
<img
:src=
"item?.showAvatar"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/>
<span
class=
"font-medium"
>
{{
item
?.
showName
}}
</span>
</div>
<span>
{{
dayjs
((
item
?.
createTime
??
0
)
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</div>
</div>
...
...
@@ -167,7 +210,8 @@
<!-- 剩余视频 - 标准网格 -->
<div
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
v-for=
"item in list"
@
click=
"router.push(`/videoDetail/$
{item.id}`)"
v-for="item in list.slice(3)"
:key="item.id"
class="group relative rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer"
>
...
...
@@ -179,17 +223,24 @@
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<!-- 标签 -->
<div
<
!--
<
div
class=
"absolute top-3 left-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-2.5 py-1 rounded-full text-xs font-semibold"
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
15:18
{{
item
.
videoDuration
}}
</div>
<!-- 数据 -->
...
...
@@ -197,58 +248,54 @@
<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>
{{
item
.
viewCount
}}
万
</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"
>
<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"
>
<
SvgIcon
name=
"icon_comment"
size=
"12"
/
>
<
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=
"
absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity
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"
>
<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"
>
<SvgIcon
name=
"icon_play"
size=
"20"
color=
"#374151"
/>
</div>
<el-icon
size=
"50"
color=
"#333"
><VideoPlay
/></el-icon>
</div>
</div>
-->
</div>
<div
class=
"p-4"
>
<h3
class=
"font-semibold text-base mb-2 group-hover:text-blue-600 transition-colors line-clamp-1"
>
{{
item
.
title
}}
{{
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"
>
{{
item
.
createUserName
}}
</div>
<span
class=
"font-medium"
>
{{
item
.
createUserName
}}
</span>
<img
:src=
"item?.showAvatar"
alt=
""
class=
"w-6 h-6 rounded-full object-cover"
/>
<span
class=
"font-medium"
>
{{
item
?.
showName
}}
</span>
</div>
<span
class=
"bg-gray-100 px-2 py-1 rounded-full"
>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
<span>
{{
dayjs
(
item
?.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</div>
</div>
</div>
</div>
</
template
>
</
div
>
<!-- 其他页面 - 标准3列网格 -->
<
template
v-else
>
<
div
v-show=
"searchParams.current !== 1"
>
<div
class=
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"
>
<div
@
click=
"router.push(`/videoDetail/$
{item.id}`)"
...
...
@@ -264,17 +311,24 @@
<div
class=
"absolute inset-0 bg-gradient-to-t from-black/40 to-transparent"
></div>
<!-- 标签 -->
<div
<
!--
<
div
class=
"absolute top-3 left-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white px-2.5 py-1 rounded-full text-xs font-semibold"
>
{{
item
.
tagNameList
[
0
]
}}
</div>
-->
<div
v-if=
"item.isRecommend"
class=
"absolute top-0 left-0 w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
<!-- 时长 -->
<div
class=
"absolute bottom-3 right-3 bg-black/80 backdrop-blur-sm text-white px-2 py-1 rounded-lg text-xs"
>
15:18
{{
item
?.
videoDuration
}}
</div>
<!-- 数据 -->
...
...
@@ -326,7 +380,7 @@
</div>
</div>
</div>
</
template
>
</
div
>
<!-- 底部分页 -->
<div
class=
"bottom-pagination backdrop-blur-8 border-t border-gray-200"
>
...
...
@@ -345,7 +399,7 @@
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[
12
, 24, 36, 48]"
:page-sizes=
"[
9
, 24, 36, 48]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
...
...
@@ -383,7 +437,7 @@ const { list, total, searchParams, loading, goToPage, changePageSize, refresh }
{
defaultParams
:
{
type
:
ArticleTypeEnum
.
VIDEO
,
sortLogic
:
0
},
defaultCurrent
:
1
,
defaultSize
:
12
,
defaultSize
:
9
,
immediate
:
false
,
},
)
...
...
@@ -397,9 +451,6 @@ const toggleTab = (sortLogic: number) => {
refresh
()
}
const
goVideoDetail
=
(
n
:
number
)
=>
{
router
.
push
(
`/videoDetail?id=
${
n
}
`
)
}
defineExpose
({
refresh
:
()
=>
{
refresh
()
...
...
@@ -407,11 +458,4 @@ defineExpose({
})
</
script
>
<
style
>
/* 文本截断样式 */
.line-clamp-2
{
display
:
-webkit-box
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
}
</
style
>
<
style
></
style
>
src/views/homePage/yaTab/components/columnList.vue
View file @
797a579b
...
...
@@ -36,19 +36,19 @@
/>
<div
v-if=
"i.isRecommend"
class=
"absolute top-
0 left-0
w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
class=
"absolute top-
-1 left--1
w-15 h-7 z-1000 bg-#FFF9B9 flex items-center justify-center border-2px border-solid border-#f4f0eb rounded-tl-lg rounded-br-lg"
>
<img
class=
"w-6"
src=
"@/assets/img/culture/recommend.png"
alt=
""
/>
<div
class=
"text-12px text-#000 line-height-12px"
>
推荐
</div>
</div>
</div>
<h3
class=
"text
-sm
font-medium text-gray-800 mb-2 transition-colors"
>
<h3
class=
"text font-medium text-gray-800 mb-2 transition-colors"
>
{{
i
.
title
}}
</h3>
<p
class=
"text-
xs
text-gray-500 mb-3 line-clamp-2"
>
<p
class=
"text-
sm
text-gray-500 mb-3 line-clamp-2"
>
{{
i
.
content
}}
</p>
<div
class=
"flex items-center justify-between text-xs text-gray-
4
00"
>
<div
class=
"flex items-center justify-between text-xs text-gray-
5
00"
>
<div
class=
"flex items-center space-x-4"
>
<span
class=
"flex items-center"
>
<el-icon
class=
"mr-1"
><View
/></el-icon>
...
...
src/views/homePage/yaTab/components/practiceList.vue
View file @
797a579b
<
template
>
<div>
<!-- 发布区域 -->
<div
class=
"bg-white p-6 mb-6 rounded-lg shadow-sm"
>
<div
class=
"flex-1 bg-white rounded-lg border border-gray-200"
>
<!-- 主输入区域 -->
<div
class=
"flex gap-3 mb-4 items-center"
>
<!-- 用户头像 -->
<el-avatar
:size=
"48"
:src=
"userInfo.avatar"
class=
"flex-shrink-0"
>
<el-icon><User
/></el-icon>
</el-avatar>
<!-- 输入区域 -->
<div
class=
"flex-1"
>
<!-- 话题标签输入 -->
<div
class=
"mb-4"
>
<el-input
v-model=
"tagInput"
placeholder=
"话题描述...... (非必填)"
class=
"tag-input"
clearable
/>
</div>
<!-- 主要内容输入 -->
<div
class=
"relative mb-3"
>
<el-input
type=
"textarea"
placeholder=
"请输入你想发布的话题"
:rows=
"3"
:maxlength=
"500"
resize=
"none"
class=
"main-textarea"
/>
<!-- 字符计数 -->
<div
class=
"absolute bottom-3 right-3 text-xs text-gray-400"
>
1/30
</div>
</div>
</div>
</div>
<!-- 工具栏 -->
<div
class=
"flex items-center justify-between pl-15"
>
<!-- 左侧工具按钮 -->
<div
class=
"flex items-center gap-1"
>
<el-tooltip
content=
"添加标签"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><CollectionTag
/></el-icon>
</el-button>
</el-tooltip>
<el-tooltip
content=
"添加图片"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><Picture
/></el-icon>
</el-button>
</el-tooltip>
<el-tooltip
content=
"添加视频"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><VideoPlay
/></el-icon>
</el-button>
</el-tooltip>
<el-tooltip
content=
"添加附件"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><Paperclip
/></el-icon>
</el-button>
</el-tooltip>
</div>
<!-- 右侧操作按钮 -->
<div
class=
"flex items-center gap-3"
>
<el-button
class=
"px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-lg border border-gray-200 text-sm"
>
存草稿
</el-button>
<el-button
type=
"primary"
:disabled=
"!tagInput"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
>
发布话题
</el-button>
</div>
</div>
</div>
</div>
<PublishPractice
/>
<!-- 标签导航 -->
<div
class=
"bg-white p-4 mb-6 rounded-lg shadow-sm"
>
...
...
@@ -133,7 +38,7 @@
<div
class=
"flex items-center gap-2"
>
<div
class=
"w-1 h-6 bg-red-500 rounded"
></div>
<h2
class=
"text-lg font-medium"
>
{{
tagList
.
find
((
tag
)
=>
searchParams
.
tagIdList
?.
includes
(
tag
.
id
))?.
title
??
'最新'
}}
{{
filterText
}}
</h2>
</div>
<div
...
...
@@ -143,10 +48,9 @@
查看更多 >>
</div>
</div>
<el-divider
/>
<el-divider
class=
"my-1!"
/>
<!-- 动态列表 -->
<div
class=
"divide-y divide-gray-100"
>
<div
class=
"divide-y bg-#fff"
>
<div
@
click=
"router.push(`/postDetail/$
{item.id}`)"
v-for="item in list"
...
...
@@ -156,9 +60,9 @@
<div
class=
"flex gap-3 items-center"
>
<!-- 左侧内容 -->
<div
class=
"flex-1"
>
<h
3
class=
"text-base font-medium text-gray-900 mb-2 leading-relaxed
"
>
<h
2
class=
"font-medium text-gray-900 mb-2 leading-relaxed line-clamp-1
"
>
{{
item
.
title
}}
</h
3
>
</h
2
>
<!-- 带图片的内容 -->
<div
class=
"flex gap-3 mb-3"
>
...
...
@@ -169,7 +73,7 @@
class=
"w-20 h-20 object-cover rounded-lg flex-shrink-0"
/>
<div
class=
"flex-1"
>
<div
class=
"text-gray-600 text-sm leading-relaxed"
>
<div
class=
"text-gray-600 text-sm leading-relaxed
line-clamp-3
"
>
{{
item
.
content
}}
</div>
</div>
...
...
@@ -239,13 +143,13 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
import
{
getPracticeList
}
from
'@/api/practice'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
dayjs
from
'dayjs'
import
{
TABS_REF_KEY
}
from
'@/constants'
import
{
useTagsStore
}
from
'@/stores/tags'
import
PublishPractice
from
'./publishPractice.vue'
const
tagsStore
=
useTagsStore
()
const
{
tagList
}
=
storeToRefs
(
tagsStore
)
...
...
@@ -253,7 +157,6 @@ const { tagList } = storeToRefs(tagsStore)
const
router
=
useRouter
()
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
userInfo
}
=
storeToRefs
(
useUserStore
())
const
filterOptions
=
ref
([
{
title
:
'最热'
,
id
:
0
},
...
...
@@ -261,8 +164,6 @@ const filterOptions = ref([
{
title
:
'最多观看'
,
id
:
2
},
])
const
tagInput
=
ref
(
''
)
const
{
handleBackTop
,
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
const
{
list
,
total
,
searchParams
,
goToPage
,
changePageSize
,
refresh
}
=
usePageSearch
(
getPracticeList
,
...
...
@@ -273,6 +174,15 @@ const { list, total, searchParams, goToPage, changePageSize, refresh } = usePage
immediate
:
false
,
},
)
const
filterText
=
computed
(()
=>
{
return
(
filterOptions
.
value
.
find
((
item
)
=>
item
.
id
===
searchParams
.
value
.
sortLogic
)?.
title
+
(
searchParams
.
value
.
tagIdList
?.
length
?
'——'
:
''
)
+
(
searchParams
.
value
.
tagIdList
?.
length
?
tagList
.
value
.
find
((
item
)
=>
searchParams
.
value
.
tagIdList
?.
includes
(
item
.
id
))?.
title
:
''
)
)
})
const
toggleFilter
=
(
id
:
number
)
=>
{
searchParams
.
value
.
sortLogic
=
id
...
...
@@ -285,6 +195,7 @@ const toggleTag = (id: number) => {
refresh
()
handleBackTop
()
}
defineExpose
({
refresh
:
()
=>
{
refresh
()
...
...
src/views/homePage/yaTab/components/publishPractice.vue
0 → 100644
View file @
797a579b
<
template
>
<div
class=
"bg-white p-6 mb-6 rounded-lg shadow-sm"
>
<div
class=
"flex-1 bg-white rounded-lg border border-gray-200"
>
<!-- 主输入区域 -->
<div
class=
"flex gap-3 mb-4 items-start"
>
<!-- 用户头像 -->
<el-avatar
:size=
"48"
:src=
"userInfo.avatar"
class=
"flex-shrink-0"
>
<el-icon><User
/></el-icon>
</el-avatar>
<!-- 输入区域 -->
<div
class=
"flex-1"
>
<!-- 话题标签输入 -->
<div
class=
"mb-4"
>
<el-input
v-model=
"form.title"
placeholder=
"实践标题"
class=
"tag-input"
clearable
/>
</div>
<!-- 主要内容输入 -->
<div
class=
"relative mb-3"
>
<el-input
type=
"textarea"
placeholder=
"请输入实践内容"
:rows=
"3"
:maxlength=
"500"
resize=
"none"
class=
"main-textarea"
v-model=
"form.content"
/>
<!-- 字符计数 -->
<div
class=
"absolute bottom-3 right-3 text-xs text-gray-400"
>
1/30
</div>
</div>
<!-- 标签内容 -->
<div
class=
"mb-2"
>
<!-- 选择的标签内容 -->
<div
class=
"flex items-center gap-2"
>
<span
v-if=
"mainTagText"
class=
"text-sm text-gray-500"
>
主标签:
<el-tag>
{{
mainTagText
}}
</el-tag>
</span>
<span
v-if=
"subTagTextList.length > 0"
class=
"text-sm text-gray-500"
>
副标签:
<el-tag
class=
"mr-2"
v-for=
"tag in subTagTextList"
:key=
"tag"
>
{{
tag
}}
</el-tag>
</span>
</div>
</div>
<!-- 图片相关 -->
<div
v-if=
"form.imgUrl.length"
class=
"flex flex-wrap gap-2"
>
<!-- 删除图片 -->
<div
class=
"relative w-20 h-20 rounded-lg overflow-hidden group"
v-for=
"img in form.imgUrl"
:key=
"img"
>
<div
class=
"absolute top-1 right-1 z-10 w-5 h-5 flex items-center justify-center bg-black/60 rounded-full cursor-pointer opacity-0 group-hover:opacity-100 transition-all duration-200 hover:bg-black/80 hover:scale-110"
@
click=
"handleDeleteImg(img)"
>
<el-icon
class=
"text-white text-xs"
>
<Close
/>
</el-icon>
</div>
<el-image
:src=
"img"
class=
"w-full h-full rounded-lg border border-gray-200"
fit=
"cover"
/>
</div>
</div>
</div>
</div>
<!-- 工具栏 -->
<div
class=
"flex items-center justify-between pl-15"
>
<!-- 左侧工具按钮 -->
<div
class=
"flex items-center gap-1"
>
<el-tooltip
content=
"添加标签"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@
click=
"handleAddTag"
>
<el-icon
size=
"18"
><CollectionTag
/></el-icon>
</el-button>
</el-tooltip>
<!-- 隐藏上传文件的input -->
<input
type=
"file"
class=
"hidden"
ref=
"fileInputRef"
@
change=
"handleFileChange"
/>
<el-tooltip
content=
"添加图片"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
@
click=
"fileInputRef?.click()"
>
<el-icon
size=
"18"
><Picture
/></el-icon>
</el-button>
</el-tooltip>
<!--
<el-tooltip
content=
"添加视频"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><VideoPlay
/></el-icon>
</el-button>
</el-tooltip>
<el-tooltip
content=
"添加附件"
placement=
"top"
>
<el-button
text
class=
"w-10 h-10 text-gray-500 hover:bg-gray-100 hover:text-gray-700 rounded-lg"
>
<el-icon
size=
"18"
><Paperclip
/></el-icon>
</el-button>
</el-tooltip>
-->
</div>
<!-- 右侧操作按钮 -->
<div
class=
"flex items-center gap-3"
>
<el-button
class=
"px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-lg border border-gray-200 text-sm"
@
click=
"handlePublish(ReleaseStatusTypeEnum.DRAFT)"
>
存草稿
</el-button>
<el-button
type=
"primary"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
@
click=
"handlePublish(ReleaseStatusTypeEnum.PUBLISH)"
>
发布实践
</el-button>
</div>
</div>
</div>
<SelectTagsDialog
v-model:mainTagId=
"form.mainTagId"
v-model:tagList=
"form.tagList"
ref=
"selectTagsDialogRef"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
SelectTagsDialog
from
'./selectTagsDialog.vue'
import
{
useResetData
}
from
'@/hooks'
import
{
ReleaseStatusTypeEnum
,
SendTypeEnum
}
from
'@/constants'
import
{
useTagsStore
}
from
'@/stores'
import
{
uploadFile
}
from
'@/api'
import
{
Close
}
from
'@element-plus/icons-vue'
import
{
addOrUpdatePractice
}
from
'@/api'
import
type
{
AddOrUpdatePracticeDto
}
from
'@/api/practice/types'
const
tagsStore
=
useTagsStore
()
const
{
tagList
}
=
storeToRefs
(
tagsStore
)
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
selectTagsDialogRef
=
useTemplateRef
<
InstanceType
<
typeof
SelectTagsDialog
>>
(
'selectTagsDialogRef'
)
const
fileInputRef
=
useTemplateRef
<
HTMLInputElement
>
(
'fileInputRef'
)
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
imgUrl
:
[],
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
mainTagId
:
''
,
tagList
:
[],
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
})
const
mainTagText
=
computed
(()
=>
{
return
tagList
.
value
.
find
((
tag
)
=>
tag
.
id
===
Number
(
form
.
value
.
mainTagId
))?.
title
})
const
subTagTextList
=
computed
(()
=>
{
return
form
.
value
.
tagList
.
map
((
tag
)
=>
tagList
.
value
.
find
((
t
)
=>
t
.
id
===
tag
)?.
title
)
})
const
handleAddTag
=
()
=>
{
selectTagsDialogRef
.
value
?.
open
()
}
const
handleFileChange
=
async
(
e
:
Event
)
=>
{
const
file
=
(
e
.
target
as
HTMLInputElement
).
files
?.[
0
]
if
(
file
)
{
const
{
data
}
=
await
uploadFile
(
file
)
form
.
value
.
imgUrl
.
push
(
data
.
data
[
0
].
filePath
)
}
}
const
handleDeleteImg
=
(
img
:
string
)
=>
{
form
.
value
.
imgUrl
=
form
.
value
.
imgUrl
.
filter
((
item
)
=>
item
!==
img
)
}
const
validateForm
=
()
=>
{
if
(
!
form
.
value
.
title
)
{
ElMessage
.
error
(
'请输入实践标题'
)
return
false
}
if
(
!
form
.
value
.
content
)
{
ElMessage
.
error
(
'请输入实践内容'
)
return
false
}
if
(
!
form
.
value
.
mainTagId
)
{
ElMessage
.
error
(
'请选择主标签'
)
return
false
}
return
true
}
const
transformForm
=
(
releaseStatus
:
ReleaseStatusTypeEnum
):
AddOrUpdatePracticeDto
=>
{
return
{
...
form
.
value
,
releaseStatus
,
faceUrl
:
form
.
value
.
imgUrl
[
0
]
||
''
,
imgUrl
:
form
.
value
.
imgUrl
.
join
(
','
),
tagList
:
[
form
.
value
.
mainTagId
,
...
form
.
value
.
tagList
].
map
((
item
,
index
)
=>
({
sort
:
index
,
tagId
:
Number
(
item
),
})),
}
}
const
handlePublish
=
async
(
releaseStatus
:
ReleaseStatusTypeEnum
)
=>
{
if
(
!
validateForm
())
return
await
addOrUpdatePractice
(
transformForm
(
releaseStatus
))
ElMessage
.
success
(
releaseStatus
===
ReleaseStatusTypeEnum
.
PUBLISH
?
'发布成功'
:
'存草稿成功'
)
resetForm
()
}
</
script
>
<
style
scoped
></
style
>
src/views/homePage/yaTab/components/selectTagsDialog.vue
0 → 100644
View file @
797a579b
<
template
>
<el-dialog
v-model=
"dialogVisible"
title=
"选择标签"
width=
"500px"
:close-on-click-modal=
"false"
>
<div
class=
"space-y-6 px-2"
>
<div
class=
"flex items-start gap-4"
>
<div
class=
"text-sm text-gray-700 w-16 flex-shrink-0"
>
主标签
</div>
<div
class=
"flex-1"
>
<SelectTags
v-model=
"mainTagId"
/>
</div>
</div>
<div
class=
"flex items-start gap-4"
>
<div
class=
"text-sm text-gray-700 w-16 flex-shrink-0"
>
副标签
</div>
<div
class=
"flex-1"
>
<SelectTags
v-model=
"subTagIdList"
:max-selected-tags=
"3"
:filter-tags-fn=
"filterTagsFn"
/>
</div>
</div>
</div>
</el-dialog>
</
template
>
<
script
setup
lang=
"ts"
>
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
import
type
{
TagItemDto
}
from
'@/api'
const
dialogVisible
=
ref
(
false
)
const
mainTagId
=
defineModel
<
string
>
(
'mainTagId'
,
{
required
:
true
})
const
subTagIdList
=
defineModel
<
number
[]
>
(
'tagList'
,
{
required
:
true
})
const
open
=
()
=>
{
dialogVisible
.
value
=
true
}
const
filterTagsFn
=
(
allTags
:
TagItemDto
[])
=>
{
return
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
mainTagId
.
value
))
}
defineExpose
({
open
,
})
</
script
>
<
style
scoped
></
style
>
src/views/publishCase/index.vue
View file @
797a579b
...
...
@@ -3,20 +3,9 @@
<div
class=
"max-w-4xl mx-auto"
>
<!-- 主表单卡片 -->
<div
class=
"bg-white rounded-lg shadow-lg p-8"
>
<el-form
ref=
"formRef"
:model=
"form"
label-position=
"top"
size=
"default"
>
<!-- 案例编号 -->
<div
class=
"mb-6 flex items-center gap-2"
>
<span
class=
"text-gray-700 font-medium"
>
案例编号:
</span>
<span
class=
"text-gray-900 font-semibold"
>
{{
form
.
number
}}
</span>
<el-tooltip
content=
"案例编号自动生成"
placement=
"top"
>
<el-icon
class=
"text-pink-500 cursor-pointer"
>
<QuestionFilled
/>
</el-icon>
</el-tooltip>
</div>
<el-form
ref=
"formRef"
:model=
"form"
label-position=
"top"
:rules=
"rules"
>
<!-- 标题输入 -->
<el-form-item
class=
"mb-6"
label=
"标题"
>
<el-form-item
class=
"mb-6"
label=
"标题"
prop=
"title"
>
<el-input
v-model=
"form.title"
placeholder=
"请输入【案例】标题"
...
...
@@ -27,7 +16,7 @@
</el-form-item>
<!-- 内容输入 -->
<el-form-item
class=
"mb-6 relative"
label=
"内容"
>
<el-form-item
class=
"mb-6 relative"
label=
"内容"
prop=
"content"
>
<el-input
v-model=
"form.content"
type=
"textarea"
...
...
@@ -38,7 +27,7 @@
/>
</el-form-item>
<!-- 关键词选择 -->
<el-form-item
label=
"文化关键词"
class=
"mb-6"
>
<el-form-item
label=
"文化关键词"
class=
"mb-6"
prop=
"mainTagId"
>
<div
class=
"flex flex-wrap gap-3"
>
主标签
<SelectTags
v-model=
"form.mainTagId"
/>
...
...
@@ -56,34 +45,19 @@
</el-form-item>
<!-- 是否同步发布 -->
<el-form-item
label=
"
*是否同步发布到实践"
class=
"mb-6
"
>
<el-radio-group
v-model=
"form.
publishToPractice
"
>
<el-radio
:label=
"
true
"
>
是
</el-radio>
<el-radio
:label=
"
false
"
>
否
</el-radio>
<el-form-item
label=
"
是否同步发布到实践"
class=
"mb-6"
prop=
"isSync
"
>
<el-radio-group
v-model=
"form.
isSync
"
>
<el-radio
:label=
"
1
"
>
是
</el-radio>
<el-radio
:label=
"
0
"
>
否
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 发布时间 -->
<!--
<el-form-item
label=
"*发布时间"
class=
"mb-8"
>
<el-radio-group
v-model=
"form.publishTime"
>
<el-radio
label=
"now"
>
立即发布
</el-radio>
<el-radio
label=
"scheduled"
>
定时发布
</el-radio>
</el-radio-group>
<el-date-picker
v-if=
"form.publishTime === 'scheduled'"
v-model=
"form.scheduledTime"
type=
"datetime"
placeholder=
"选择发布时间"
class=
"ml-4"
/>
</el-form-item>
-->
<!-- 底部按钮组 -->
<div
class=
"flex items-center justify-between gap-4 pt-4"
>
<div
class=
"flex gap-4"
>
<el-button
@
click=
"handleCancel"
>
取消
</el-button>
<
el-button
@
click=
"handlePreview"
>
预览
</el-button
>
<el-button
type=
"info"
plain
@
click=
"handleS
aveDraf
t"
>
存草稿
</el-button>
<
!--
<el-button
@
click=
"handlePreview"
>
预览
</el-button>
--
>
<el-button
type=
"info"
plain
@
click=
"handleS
ubmi
t"
>
存草稿
</el-button>
</div>
<el-button
type=
"primary"
@
click=
"handleSubmit"
>
提交
</el-button>
</div>
...
...
@@ -94,14 +68,15 @@
</
template
>
<
script
setup
lang=
"tsx"
>
import
{
QuestionFilled
}
from
'@element-plus/icons-vue'
import
{
addOrUpdateCase
,
getMaxCaseNumber
}
from
'@/api'
import
{
addOrUpdateCase
}
from
'@/api'
import
{
useResetData
}
from
'@/hooks'
import
type
{
AddOrUpdateCaseDto
}
from
'@/api'
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
import
type
{
FormInstance
}
from
'element-plus'
import
type
{
TagItemDto
}
from
'@/api'
import
{
TagTypeEnum
,
TagLevelEnum
}
from
'@/constants'
import
{
TagTypeEnum
,
TagLevelEnum
,
BooleanFlag
,
ReleaseStatusTypeEnum
}
from
'@/constants'
const
router
=
useRouter
()
const
formRef
=
useTemplateRef
<
FormInstance
>
(
'formRef'
)
...
...
@@ -110,31 +85,35 @@ type FormData = Omit<AddOrUpdateCaseDto, 'tagRelationDtoList'> & {
subTagIds
:
number
[]
}
const
[
form
]
=
useResetData
<
FormData
>
({
const
rules
=
{
title
:
[{
required
:
true
,
message
:
'请输入标题'
,
trigger
:
'blur'
}],
content
:
[{
required
:
true
,
message
:
'请输入内容'
,
trigger
:
'blur'
}],
mainTagId
:
[{
required
:
true
,
message
:
'请选择文化关键词'
,
trigger
:
'blur'
}],
isSync
:
[{
required
:
true
,
message
:
'请选择是否同步发布到实践'
,
trigger
:
'blur'
}],
}
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
number
:
''
,
mainTagId
:
''
,
subTagIds
:
[],
isSync
:
BooleanFlag
.
NO
,
releaseStatus
:
ReleaseStatusTypeEnum
.
DRAFT
,
})
// 取消
const
handleCancel
=
()
=>
{
ElMessage
.
warning
(
'确定要取消吗?'
)
// 可以添加确认对话框
resetForm
(
)
router
.
back
()
}
// 预览
const
handlePreview
=
()
=>
{
console
.
log
(
'预览'
,
form
)
ElMessage
.
success
(
'预览功能待实现'
)
}
//
const handlePreview = () => {
//
console.log('预览', form)
//
ElMessage.success('预览功能待实现')
//
}
// 保存草稿
const
handleSaveDraft
=
()
=>
{
console
.
log
(
'保存草稿'
,
form
)
ElMessage
.
success
(
'草稿保存成功'
)
}
const
transformData
=
(
formData
:
FormData
):
AddOrUpdateCaseDto
=>
{
const
{
mainTagId
,
subTagIds
,
...
rest
}
=
formData
...
...
@@ -142,6 +121,7 @@ const transformData = (formData: FormData): AddOrUpdateCaseDto => {
...
rest
,
tagRelationDtoList
:
[],
}
// 添加标签内容
obj
.
tagRelationDtoList
.
push
({
tagId
:
Number
(
mainTagId
),
type
:
TagTypeEnum
.
CULTURE_TAG
,
...
...
@@ -163,6 +143,8 @@ const handleSubmit = async () => {
const
res
=
await
addOrUpdateCase
(
transformData
(
form
.
value
))
if
(
res
)
{
ElMessage
.
success
(
'提交成功'
)
resetForm
()
router
.
back
()
}
}
...
...
@@ -230,14 +212,8 @@ const filterTagsFn = (allTags: TagItemDto[]) => {
return
allTags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
form
.
value
.
mainTagId
))
}
const
fetchMaxCaseNumber
=
async
()
=>
{
const
{
data
}
=
await
getMaxCaseNumber
()
form
.
value
.
number
=
data
}
onActivated
(()
=>
{
showSubmissionGuide
()
fetchMaxCaseNumber
()
})
</
script
>
...
...
src/views/publishVideo/index.vue
View file @
797a579b
...
...
@@ -12,9 +12,10 @@
<h3
class=
"text-xl font-bold text-gray-800"
>
上传视频
</h3>
</div>
<el-form-item
prop=
"videoUrl"
>
<UploadVideo
v-model=
"form.videoUrl"
/>
<UploadVideo
v-model=
"form.videoUrl"
@
uploadSuccess=
"handleVideoChange"
/>
</el-form-item>
</div>
<!-- 基本设置 -->
<div
class=
"bg-white backdrop-blur-sm rounded-lg shadow-lg border border-white/20 p-8 hover:shadow-xl transition-all duration-300"
...
...
@@ -25,49 +26,44 @@
<!-- 封面设置 -->
<div
class=
"mb-8"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-4"
>
封面选择
</label>
<div
class=
"flex gap-6 items-start"
>
<!-- 主封面 -->
<div
class=
"relative group"
>
<div
class=
"w-48 h-28 bg-gradient-to-br from-gray-100 to-gray-200 rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-all duration-300"
>
<img
src=
"@/assets/img/culture/ask.png"
alt=
"主封面"
class=
"w-full h-full object-cover"
/>
<div
class=
"absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
></div>
<div
class=
"absolute bottom-2 left-2 bg-gradient-to-r from-indigo-500 to-purple-600 text-white text-xs px-2 py-1 rounded-md font-medium"
>
智能封面
<el-form-item
prop=
"faceUrl"
>
<div
class=
""
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
封面选择
</label>
<div
class=
"flex gap-6"
>
<!-- 主封面预览 -->
<div
class=
"relative group"
>
<div
class=
"w-48 h-28 bg-gradient-to-br from-gray-100 to-gray-200 rounded-lg shadow-md hover:shadow-lg transition-all duration-300 cursor-pointer overflow-hidden"
@
click=
"showCoverDialog = true"
>
<img
v-if=
"form.faceUrl"
:src=
"form.faceUrl"
alt=
"视频封面"
class=
"w-full h-full object-cover"
/>
<div
v-else
class=
"w-full h-full flex items-center justify-center text-gray-400"
>
<el-icon
:size=
"32"
><Picture
/></el-icon>
</div>
<div
class=
"absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center shadow-lg"
>
<span
class=
"text-white text-sm font-medium"
>
点击选择封面
</span>
</div>
<div
v-if=
"form.faceUrl"
class=
"absolute bottom-2 left-2 bg-gradient-to-r from-indigo-500 to-purple-600 text-white text-xs px-2 py-1 rounded-md font-medium"
>
当前封面
</div>
</div>
</div>
</div>
</div>
<!-- 封面选项 -->
<div
class=
"flex gap-3"
>
<div
v-for=
"i in 4"
:key=
"i"
class=
"w-20 h-12 bg-gray-100 rounded-lg overflow-hidden border-2 hover:border-indigo-400 cursor-pointer transition-all duration-200"
:class=
"i === 1 ? 'border-indigo-500 shadow-md' : 'border-gray-200'"
>
<img
src=
"@/assets/img/culture/ask.png"
alt=
"封面选项"
class=
"w-full h-full object-cover"
/>
</div>
</div>
</div>
<p
class=
"text-sm text-gray-500 mt-3 flex items-center gap-2"
>
<el-icon
class=
"text-indigo-500"
><InfoFilled
/></el-icon>
选择封面UI中的第一张,让作品获得更多推荐机会!
</p>
</el-form-item>
</div>
<!-- 标题 -->
...
...
@@ -79,11 +75,11 @@
placeholder=
"请输入视频标题..."
maxlength=
"80"
show-word-limit
size=
"large"
class=
"title-input"
/>
</el-form-item>
</div>
<div
class=
"mb-8"
>
<el-form-item
prop=
"content"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
视频简介
</label>
...
...
@@ -95,43 +91,44 @@
/>
</el-form-item>
</div>
<div
class=
"mb-8"
>
<el-form-item
prop=
"mainTagId"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
主标签
<el-tooltip
content=
"添加相关标签可以帮助更多人发现您的作品"
placement=
"top"
>
<el-icon
class=
"text-gray-400 cursor-help"
><QuestionFilled
/></el-icon>
</el-tooltip
></label>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
主标签
<el-tooltip
content=
"主标签最多添加一个"
placement=
"top"
>
<el-icon
class=
"text-gray-400 cursor-help"
><QuestionFilled
/></el-icon>
</el-tooltip>
</label>
<SelectTags
class=
"w-full"
v-model=
"form.mainTagId"
:max-selected-tags=
"1"
/>
</el-form-item>
</div>
<div
class=
"mb-8"
>
<el-form-item
prop=
"mainTagId"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
副标签
<el-tooltip
content=
"副标签可以添加多个,最多3个"
placement=
"top"
>
<el-icon
class=
"text-gray-400 cursor-help"
><QuestionFilled
/></el-icon>
</el-tooltip
></label>
<el-form-item
prop=
"tagList"
>
<label
class=
"block text-sm font-semibold text-gray-700 mb-3"
>
副标签
<el-tooltip
content=
"副标签最多添加3个"
placement=
"top"
>
<el-icon
class=
"text-gray-400 cursor-help"
><QuestionFilled
/></el-icon>
</el-tooltip>
</label>
<SelectTags
class=
"w-full"
v-model=
"form.tagList"
v-model=
"form.tagList
as number[]
"
:max-selected-tags=
"3"
:filter-tags-fn=
"filterTagsFn"
/>
</el-form-item>
</div>
</div>
<div
class=
"bg-white backdrop-blur-sm rounded-lg shadow-lg border border-white/20 p-6 hover:shadow-xl transition-all duration-300"
>
<h4
class=
"text-lg font-bold text-gray-800 mb-4"
>
发布设置
</h4>
<div
class=
"space-y-4"
>
<div
class=
"flex items-center justify-between p-3
bg-gray-50
rounded-lg"
>
<div
class=
"flex items-center justify-between p-3 rounded-lg"
>
<div>
<div
class=
"text-sm font-medium text-gray-800"
>
定时发布
</div>
<div
class=
"text-xs text-gray-500"
>
设置发布时间
</div>
...
...
@@ -160,6 +157,7 @@
</div>
</div>
</div>
<div
class=
"bg-white/70 backdrop-blur-sm rounded-lg shadow-lg border border-white/20 p-6"
>
<div
class=
"space-y-3 flex justify-center items-center gap-4"
>
<el-button
...
...
@@ -169,7 +167,6 @@
<el-icon
class=
"mr-2"
><Document
/></el-icon>
保存草稿
</el-button>
<span></span>
<el-button
type=
"primary"
size=
"large"
...
...
@@ -184,36 +181,87 @@
</div>
</div>
</el-form>
<!-- 右上角tips -->
<!--
<div
class=
"w-200px fixed bottom-0 left-0 bg-gradient-to-br from-amber-50 to-orange-50 rounded-lg p-6 border border-amber-200"
<!-- 封面选择弹窗 -->
<el-dialog
v-model=
"showCoverDialog"
title=
"选择视频封面"
width=
"800px"
:close-on-click-modal=
"false"
>
<div
class=
"flex items-start gap-3"
>
<el-icon
class=
"text-amber-500 mt-1"
><Warning
/></el-icon>
<div>
<h5
class=
"text-sm font-semibold text-amber-800 mb-2"
>
发布小贴士
</h5>
<ul
class=
"text-xs text-amber-700 space-y-1"
>
<li>
• 选择合适的封面能提高点击率
</li>
<li>
• 添加相关标签帮助推荐
</li>
<li>
• 详细的简介让观众更了解内容
</li>
</ul>
<div
v-if=
"locationVideoBlolUrl"
class=
"space-y-6"
>
<!-- 视频预览 -->
<div
class=
"relative"
>
<video
ref=
"videoRef"
:src=
"locationVideoBlolUrl"
class=
"w-full max-h-96 rounded-lg bg-black"
@
loadedmetadata=
"onVideoLoaded"
@
timeupdate=
"onTimeUpdate"
/>
<div
class=
"absolute bottom-4 left-4 right-4 bg-black/50 backdrop-blur-sm rounded-lg p-3"
>
<div
class=
"text-white text-sm mb-2"
>
当前时间:
{{
formatTime
(
currentTime
)
}}
/
{{
formatTime
(
videoDuration
)
}}
</div>
<el-slider
v-model=
"currentTime"
:max=
"videoDuration"
:step=
"0.1"
@
input=
"(time) => seekVideo(time as number)"
class=
"custom-slider"
/>
</div>
</div>
<!-- 封面预览 -->
<div
class=
"bg-gray-50 rounded-lg p-4"
>
<div
class=
"text-sm font-semibold text-gray-700 mb-3"
>
封面预览
</div>
<div
class=
"flex items-center gap-6"
>
<div
class=
"relative w-60 h-34 bg-gray-200 rounded-lg overflow-hidden"
>
<img
v-if=
"form.faceUrl"
:src=
"form.faceUrl"
class=
"w-full h-full object-cover"
/>
<div
v-else
class=
"w-full h-full flex items-center justify-center text-gray-400"
>
<el-icon
:size=
"40"
><Picture
/></el-icon>
</div>
<canvas
ref=
"canvasRef"
class=
"hidden"
/>
</div>
<div
class=
"flex-1"
>
<el-button
type=
"primary"
@
click=
"captureFrame"
:icon=
"Camera"
>
截取当前帧
</el-button>
<p
class=
"text-xs text-gray-500 mt-2"
>
拖动进度条找到合适的画面,点击截取按钮生成封面
</p>
</div>
</div>
</div>
</div>
</div>
-->
<template
#
footer
>
<div
class=
"flex justify-end gap-3"
>
<el-button
@
click=
"showCoverDialog = false"
>
取消
</el-button>
<el-button
type=
"primary"
@
click=
"confirmCover"
:disabled=
"!form.faceUrl"
>
确认使用
</el-button>
</div>
</
template
>
</el-dialog>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
UploadVideo
from
'@/components/common/UploadVideo/index.vue'
import
{
useResetData
}
from
'@/hooks'
import
{
ArticleTypeEnum
,
ReleaseStatusTypeEnum
,
SendTypeEnum
}
from
'@/constants'
import
{
addOrUpdateArticle
}
from
'@/api'
import
{
addOrUpdateArticle
,
uploadFile
}
from
'@/api'
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
import
type
{
TagItemDto
}
from
'@/api'
import
type
{
TagItemDto
,
AddOrUpdateVideoDto
}
from
'@/api'
import
{
Camera
,
Picture
}
from
'@element-plus/icons-vue'
const
router
=
useRouter
()
const
formRef
=
useTemplateRef
(
'formRef'
)
const
[
form
]
=
useResetData
({
const
[
form
,
resetData
]
=
useResetData
<
AddOrUpdateVideoDto
>
({
videoUrl
:
''
,
title
:
'视频标题'
,
type
:
ArticleTypeEnum
.
VIDEO
,
...
...
@@ -223,8 +271,89 @@ const [form] = useResetData({
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
releaseStatus
:
ReleaseStatusTypeEnum
.
PUBLISH
,
faceUrl
:
''
,
// 封面URL
videoDuration
:
''
,
})
// 封面选择相关
const
locationVideoBlolUrl
=
ref
(
''
)
const
showCoverDialog
=
ref
(
false
)
const
videoRef
=
ref
<
HTMLVideoElement
>
()
const
canvasRef
=
ref
<
HTMLCanvasElement
>
()
const
videoDuration
=
ref
(
0
)
const
currentTime
=
ref
(
0
)
// 视频加载完成
const
onVideoLoaded
=
()
=>
{
if
(
videoRef
.
value
)
{
videoDuration
.
value
=
videoRef
.
value
.
duration
currentTime
.
value
=
1
// 默认第1秒
seekVideo
(
1
)
}
}
// 视频时间更新
const
onTimeUpdate
=
()
=>
{
if
(
videoRef
.
value
)
{
currentTime
.
value
=
videoRef
.
value
.
currentTime
}
}
// 跳转到指定时间
const
seekVideo
=
(
time
:
number
)
=>
{
if
(
videoRef
.
value
)
{
videoRef
.
value
.
currentTime
=
time
}
}
// 截取当前帧
const
captureFrame
=
()
=>
{
const
video
=
videoRef
.
value
const
canvas
=
canvasRef
.
value
if
(
!
video
||
!
canvas
)
return
// 用canvas 绘制 视频当前帧的图片 然后上传
canvas
.
width
=
video
.
videoWidth
canvas
.
height
=
video
.
videoHeight
// 绘制当前帧
const
ctx
=
canvas
.
getContext
(
'2d'
)
if
(
ctx
)
{
ctx
.
drawImage
(
video
,
0
,
0
,
canvas
.
width
,
canvas
.
height
)
// 转换为Blob
canvas
.
toBlob
(
async
(
blob
)
=>
{
if
(
blob
)
{
// 直接在这里上传 不转为本地了
const
{
data
}
=
await
uploadFile
(
new
File
([
blob
],
`video
${
Date
.
now
()}
.jpg`
))
form
.
value
.
faceUrl
=
data
.
data
[
0
].
filePath
ElMessage
.
success
(
'封面截取成功'
)
}
},
'image/jpeg'
,
0.9
,
)
}
}
// 确认使用封面
const
confirmCover
=
async
()
=>
{
if
(
!
form
.
value
.
faceUrl
)
return
showCoverDialog
.
value
=
false
ElMessage
.
success
(
'封面设置成功'
)
}
// 格式化时间
const
formatTime
=
(
seconds
:
number
)
=>
{
const
mins
=
Math
.
floor
(
seconds
/
60
)
const
secs
=
Math
.
floor
(
seconds
%
60
)
return
`
${
mins
}
:
${
secs
.
toString
().
padStart
(
2
,
'0'
)}
`
}
const
filterTagsFn
=
(
tags
:
TagItemDto
[])
=>
{
return
tags
.
filter
((
tag
)
=>
tag
.
id
!==
Number
(
form
.
value
.
mainTagId
))
}
...
...
@@ -233,7 +362,8 @@ const rules = {
videoUrl
:
[{
required
:
true
,
message
:
'请上传视频'
,
trigger
:
'change'
}],
title
:
[{
required
:
true
,
message
:
'请输入视频标题'
,
trigger
:
'blur'
}],
content
:
[{
required
:
true
,
message
:
'请输入视频简介'
,
trigger
:
'blur'
}],
mainTagId
:
[{
required
:
true
,
message
:
'请选择标签'
,
trigger
:
'change'
}],
mainTagId
:
[{
required
:
true
,
message
:
'请选择主标签'
,
trigger
:
'change'
}],
faceUrl
:
[{
required
:
true
,
message
:
'请选择视频封面'
,
trigger
:
'change'
}],
}
const
tansformData
=
()
=>
{
...
...
@@ -244,17 +374,59 @@ const tansformData = () => {
sort
:
index
,
})),
mainTagId
:
Number
(
form
.
value
.
mainTagId
),
faceUrl
:
'https://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/readName/png/2025/11/21/Common/1763710823097.png'
,
}
}
const
loading
=
ref
(
false
)
const
resetPageData
=
()
=>
{
resetData
()
locationVideoBlolUrl
.
value
=
''
}
const
handleSubmit
=
async
()
=>
{
await
formRef
.
value
?.
validate
()
const
res
=
await
addOrUpdateArticle
(
tansformData
())
if
(
res
)
{
loading
.
value
=
true
try
{
addOrUpdateArticle
(
tansformData
())
ElMessage
.
success
(
'发布成功'
)
resetPageData
()
router
.
push
(
'/'
)
// 重置数据
}
catch
(
e
)
{
console
.
log
(
e
)
}
finally
{
loading
.
value
=
false
}
}
const
handleVideoChange
=
({
file
,
videoDuration
,
}:
{
file
:
File
url
:
string
videoDuration
:
string
})
=>
{
if
(
locationVideoBlolUrl
.
value
)
{
URL
.
revokeObjectURL
(
locationVideoBlolUrl
.
value
)
}
if
(
file
)
{
locationVideoBlolUrl
.
value
=
URL
.
createObjectURL
(
file
)
}
form
.
value
.
videoDuration
=
videoDuration
}
</
script
>
<
style
scoped
></
style
>
<
style
scoped
>
/* 自定义滑块样式 */
:deep
(
.custom-slider
.el-slider__runway
)
{
background-color
:
rgba
(
255
,
255
,
255
,
0.3
);
}
:deep
(
.custom-slider
.el-slider__bar
)
{
background-color
:
#6366f1
;
}
:deep
(
.custom-slider
.el-slider__button
)
{
border-color
:
#6366f1
;
}
</
style
>
src/views/searchPage/index.vue
View file @
797a579b
<
template
>
<div
class=
"min-h-screen bg-white
"
>
<div
ref=
"searchPageRef"
class=
"bg-white/90
"
>
<div
class=
"max-w-1400px mx-auto p-6"
>
<!-- 搜索栏 -->
<div
class=
"mb-
8
"
>
<div
class=
"mb-
1 p-4
"
>
<div
class=
"relative flex items-center gap-3"
>
<el-input
v-model=
"search
Keyword
"
v-model=
"search
Params.title
"
placeholder=
"输入关键词搜索"
size=
"large"
class=
"search-input flex-1"
class=
"w-300px! flex-1"
@
keyup
.
enter=
"handleSearch"
clearable
/>
<el-button
type=
"primary"
size=
"large"
class=
"search-btn"
@
click=
"handleSearch"
>
搜索
</el-button>
<el-button
type=
"primary"
@
click=
"handleSearch"
>
搜索
</el-button>
</div>
</div>
<!-- 分类 Tabs -->
<div
class=
"mb-6 flex gap-3 flex items-center"
>
<Tabs
v-model=
"activeTab"
:tabs=
"tabs"
@
change=
"(value) => handleTabChange(value as string)"
/>
<button
v-for=
"sort in sortOptions"
:key=
"sort.value"
class=
"px-4 py-1.5 rounded-lg text-14px transition-colors"
:class=
"
{
'text-blue-600 bg-blue-50': activeSort === sort.value,
'text-gray-600 hover:text-blue-600': activeSort !== sort.value,
}"
@click="activeSort = sort.value"
>
{{
sort
.
label
}}
</button>
</div>
<!-- 排序方式 -->
<div
class=
"flex items-center justify-between mb-6 py-4 border-b border-gray-200"
>
<!-- 一级分类 -->
<div
class=
"flex items-center justify-between mb-6 border-b border-gray-200 p-4"
>
<div
class=
"flex items-center gap-2"
>
<span
class=
"text-gray-600 text-14px"
>
排序方式
:
</span>
<span
class=
"text-gray-600 text-14px"
>
分类
:
</span>
<div
class=
"flex gap-2"
>
<button
v-for=
"sort in
sor
tOptions"
v-for=
"sort in
articleTypeLis
tOptions"
:key=
"sort.value"
class=
"px-4 py-1.5 rounded-lg text-14px transition-colors"
class=
"px-4 py-1.5 rounded-lg text-14px transition-colors
cursor-pointer
"
:class=
"
{
'text-blue-600 bg-blue-50':
activeSort
=== sort.value,
'text-gray-600 hover:text-blue-600':
activeSort
!== sort.value,
'text-blue-600 bg-blue-50':
searchParams.type
=== sort.value,
'text-gray-600 hover:text-blue-600':
searchParams.type
!== sort.value,
}"
@click="
activeSort = sort.value
"
@click="
changeType(sort.value)
"
>
{{
sort
.
label
}}
</button>
</div>
</div>
<div
class=
"text-gray-500 text-14px"
>
共找到
<span
class=
"text-blue-600 font-600"
>
{{
totalResults
}}
</span>
条结果
<el-select
v-model=
"searchParams.sortLogic"
placeholder=
"请选择排序方式"
class=
"w-100px!"
@
change=
"changeSort"
>
<el-option
v-for=
"sort in sortOptions"
:key=
"sort.value"
:label=
"sort.label"
:value=
"sort.value"
/>
</el-select>
</div>
</div>
<!-- 二级分类 -->
<!--
<div
v-show=
"
searchParams.type === ArticleTypeEnum.COLUMN ||
searchParams.type === ArticleTypeEnum.INTERVIEW
"
class=
"flex items-center gap-2 px-4"
>
<span
class=
"text-gray-600 text-14px"
>
二级分类:
</span>
<div
class=
"flex gap-2"
>
<button
v-for=
"sort in articleTypeListOptions"
:key=
"sort.value"
class=
"px-4 py-1.5 rounded-lg text-14px transition-colors cursor-pointer"
:class=
"
{
'text-blue-600 bg-blue-50': searchParams.type === sort.value,
'text-gray-600 hover:text-blue-600': searchParams.type !== sort.value,
}"
@click="changeType(sort.value)"
>
{{
sort
.
label
}}
</button>
</div>
</div>
-->
<!-- 搜索结果列表 -->
<div
class=
"space-y-4"
>
<div
v-for=
"item in searchResults"
:key=
"item.id"
class=
"flex gap-4 p-4 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"
>
<!-- 封面图 -->
<div
v-show=
"list.length"
>
<div
class=
"space-y-4"
>
<div
class=
"flex-shrink-0 w-240px h-135px rounded-lg overflow-hidden bg-gray-100 relative"
v-for=
"item in list"
:key=
"item.id"
class=
"flex gap-4 p-4 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"
@
click=
"handleClick(item)"
>
<
img
:src=
"item.cover"
class=
"w-full h-full object-cover"
/
>
<
!-- 封面图 --
>
<div
class=
"absolute bottom-2 right-2 bg-black/70 text-white text-12px px-2 py-0.5 rounded"
v-if=
"item.faceUrl"
class=
"flex-shrink-0 w-240px h-135px rounded-lg overflow-hidden bg-gray-100 relative"
>
{{
item
.
duration
}}
<img
:src=
"item.faceUrl"
class=
"w-full h-full object-cover"
/>
<div
v-if=
"item.type === ArticleTypeEnum.VIDEO"
class=
"absolute bottom-2 right-2 bg-black/70 text-white text-12px px-2 py-0.5 rounded"
>
{{
item
.
videoDuration
}}
</div>
</div>
</div>
<!-- 内容信息 -->
<div
class=
"flex-1 flex flex-col justify-between min-w-0"
>
<div>
<!-- 标题 -->
<h3
class=
"text-16px font-500 text-gray-900 mb-2 line-clamp-1"
>
{{
item
.
title
}}
</h3>
<!-- 描述 -->
<p
class=
"text-14px text-gray-600 mb-3 line-clamp-2"
>
{{
item
.
description
}}
</p>
<!-- 内容信息 -->
<div
class=
"flex-1 flex flex-col justify-between min-w-0"
>
<div>
<!-- 标题 -->
<h3
class=
"text-16px font-500 text-gray-900 mb-2 line-clamp-1"
>
{{
item
.
title
}}
</h3>
<!-- 描述 -->
<p
class=
"text-14px text-gray-600 mb-3 line-clamp-2"
>
{{
item
.
content
}}
</p>
</div>
<!-- 底部信息 -->
<div
class=
"flex items-center gap-4 text-13px text-gray-500"
>
<span>
{{
item
.
showName
}}
</span>
<span>
{{
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
</span>
<div
class=
"flex items-center gap-1"
>
<el-icon
class=
"text-sm"
><View
/></el-icon>
<span
class=
"font-medium text-gray-500"
>
{{
item
.
viewCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1"
>
<el-icon
class=
"text-sm"
><ChatDotRound
/></el-icon>
<span
class=
"font-medium"
>
{{
item
.
replyCount
}}
</span>
</div>
<div
class=
"flex items-center gap-1"
>
<el-icon
class=
"text-sm"
><Star
/></el-icon>
<span
class=
"font-medium"
>
{{
item
.
praiseCount
}}
</span>
</div>
</div>
</div>
<!-- 底部信息 -->
<div
class=
"flex items-center gap-4 text-13px text-gray-500"
>
<span>
{{
item
.
author
}}
</span>
<span>
{{
item
.
views
}}
</span>
<span>
{{
item
.
time
}}
</span>
<div
class=
"flex-shrink-0 self-end"
>
<span
class=
"text-blue-600 text-13px"
>
{{
articleTypeListOptions
.
find
((
i
)
=>
i
.
value
===
item
.
type
)?.
label
}}
</span>
</div>
</div>
</div>
<!-- 右侧视频标签 -->
<div
class=
"flex-shrink-0 self-end"
>
<span
class=
"text-blue-600 text-13px"
>
视频
</span>
<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-end gap-4"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
<div
class=
"right"
>
<div
class=
"pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page=
"searchParams.current"
v-model:page-size=
"searchParams.size"
:page-sizes=
"[5, 20, 1]"
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>
<!-- 分页 -->
<div
class=
"mt-8 flex justify-center"
>
<el-pagination
v-model:current-page=
"currentPage"
v-model:page-size=
"pageSize"
:total=
"totalResults"
:page-sizes=
"[10, 20, 30, 50]"
layout=
"total, sizes, prev, pager, next, jumper"
/>
<div
v-show=
"!list.length"
>
<div
class=
"flex items-center justify-center h-full"
>
<el-empty
description=
"暂无数据"
/>
</div>
</div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
import
Tabs
from
'@/components/common/Tabs'
interface
SearchResult
{
id
:
number
title
:
string
description
:
string
cover
:
string
duration
:
string
author
:
string
views
:
string
time
:
string
}
const
searchKeyword
=
ref
(
'wayward'
)
const
tabs
=
[
{
label
:
'综合'
,
value
:
'综合'
,
count
:
189
},
{
label
:
'视频'
,
value
:
'视频'
,
count
:
89
},
{
label
:
'帖子'
,
value
:
'帖子'
,
count
:
0
},
{
label
:
'实践'
,
value
:
'实践'
,
count
:
0
},
{
label
:
'专栏'
,
value
:
'专栏'
,
count
:
0
},
{
label
:
'专访'
,
value
:
'专访'
,
count
:
51
},
{
label
:
'问题'
,
value
:
'问题'
,
count
:
0
},
import
{
articleTypeListOptions
}
from
'@/constants'
import
{
getArticleList
}
from
'@/api/article'
import
{
usePageSearch
,
useScrollTop
}
from
'@/hooks'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
dayjs
from
'dayjs'
import
type
{
ArticleListItemDto
}
from
'@/api'
const
router
=
useRouter
()
const
route
=
useRoute
()
const
searchPageRef
=
ref
<
HTMLElement
|
null
>
(
null
)
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
searchPageRef
)
const
sortOptions
=
[
{
label
:
'最热'
,
value
:
0
},
{
label
:
'最新'
,
value
:
1
},
]
const
activeTab
=
ref
(
'综合'
)
const
sortOptions
=
ref
([
{
label
:
'综合排序'
,
value
:
'default'
},
{
label
:
'最多播放'
,
value
:
'views'
},
{
label
:
'最新发布'
,
value
:
'time'
},
{
label
:
'最多收藏'
,
value
:
'favorite'
},
])
const
activeSort
=
ref
(
'default'
)
const
totalResults
=
ref
(
189
)
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
20
)
const
searchResults
=
ref
<
SearchResult
[]
>
([
{
id
:
1
,
title
:
'wayward:已经20小时了!第3次挑战灭火了'
,
description
:
'wayward这次挑战能否成功?经历了前两次失败后,这次能否成功灭火?让我们拭目以待...'
,
cover
:
'https://picsum.photos/400/225?random=1'
,
duration
:
'20:45'
,
author
:
'waywardzz'
,
views
:
'1.2万'
,
time
:
'2天前'
,
},
{
id
:
2
,
title
:
'wayward:只是喝成这样还怎么玩?长江水果实太快了'
,
description
:
'这期节目真的太搞笑了,wayward的状态实在是...'
,
cover
:
'https://picsum.photos/400/225?random=2'
,
duration
:
'15:30'
,
author
:
'waywardzz'
,
views
:
'8956'
,
time
:
'3天前'
,
},
const
{
total
,
goToPage
,
changePageSize
,
refresh
,
searchParams
,
list
}
=
usePageSearch
(
getArticleList
,
{
id
:
3
,
title
:
'wayward生存挑战20小时记录'
,
description
:
'完整记录wayward的20小时生存挑战过程,从开始到结束的所有精彩瞬间...'
,
cover
:
'https://picsum.photos/400/225?random=3'
,
duration
:
'1:20:15'
,
author
:
'waywardFan'
,
views
:
'5.6万'
,
time
:
'1周前'
,
defaultParams
:
{
type
:
articleTypeListOptions
[
0
]?.
value
,
sortLogic
:
sortOptions
[
0
]?.
value
,
},
immediate
:
false
,
},
]
)
)
const
handleSearch
=
()
=>
{
console
.
log
(
'搜索关键词:'
,
searchKeyword
.
value
)
const
changeType
=
(
value
:
ArticleTypeEnum
)
=>
{
searchParams
.
value
.
type
=
value
refresh
()
}
const
handleTabChange
=
(
value
:
string
)
=>
{
console
.
log
(
'切换分类:'
,
value
)
activeTab
.
value
=
value
const
changeSort
=
(
value
:
number
)
=>
{
searchParams
.
value
.
sortLogic
=
value
refresh
()
}
</
script
>
<
style
scoped
>
:deep
(
.search-input
.el-input__wrapper
)
{
border-radius
:
0.5rem
;
border
:
1px
solid
#e5e7eb
;
const
handleSearch
=
()
=>
{
refresh
()
}
:deep
(
.search-btn
)
{
background
:
#3b82f6
;
border
:
none
;
border-radius
:
0.5rem
;
padding
:
0
2rem
;
const
handleClick
=
(
item
:
ArticleListItemDto
)
=>
{
if
(
item
.
type
===
ArticleTypeEnum
.
VIDEO
)
{
router
.
push
(
`/videoDetail/
${
item
.
id
}
`
)
}
else
{
router
.
push
(
`/postDetail/
${
item
.
id
}
`
)
}
}
.line-clamp-1
{
display
:
-webkit-box
;
-webkit-line-clamp
:
1
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
}
onActivated
(()
=>
{
if
(
route
.
query
.
title
)
{
searchParams
.
value
.
title
=
route
.
query
.
title
as
string
}
if
(
route
.
query
.
type
)
{
searchParams
.
value
.
type
=
route
.
query
.
type
as
ArticleTypeEnum
}
refresh
()
})
</
script
>
.line-clamp-2
{
display
:
-webkit-box
;
-webkit-line-clamp
:
2
;
-webkit-box-orient
:
vertical
;
overflow
:
hidden
;
}
</
style
>
<
style
scoped
></
style
>
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