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
aefe108b
Commit
aefe108b
authored
Nov 13, 2025
by
lijiabin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【需求 17679】wip: 继续加页面,首页、详情页等
parent
5ba0ac6a
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
977 additions
and
216 deletions
+977
-216
mainContainer.vue
src/layoutCulture/components/mainContainer.vue
+28
-3
postForm.tsx
src/layoutCulture/components/postForm.tsx
+11
-3
practiceForm.tsx
src/layoutCulture/components/practiceForm.tsx
+59
-12
publishDialog.vue
src/layoutCulture/components/publishDialog.vue
+19
-28
index.vue
src/layoutCulture/index.vue
+21
-17
index.vue
src/views/ask/index.vue
+7
-22
recommendList.vue
src/views/home/components/recommendList.vue
+24
-28
videoList.vue
src/views/home/components/videoList.vue
+7
-18
index.vue
src/views/home/index.vue
+6
-4
actionButtons.vue
src/views/postDetail/components/actionButtons.vue
+100
-0
index.vue
src/views/postDetail/index.vue
+373
-49
editUserInfo.vue
src/views/userPage/components/editUserInfo.vue
+2
-6
selfCollect.vue
src/views/userPage/components/selfCollect.vue
+138
-0
selfPraise.vue
src/views/userPage/components/selfPraise.vue
+138
-0
index.vue
src/views/userPage/index.vue
+37
-11
index.vue
src/views/ya/index.vue
+7
-15
No files found.
src/layoutCulture/components/mainContainer.vue
View file @
aefe108b
...
...
@@ -63,7 +63,7 @@
<div
class=
"flex gap-3"
>
<div
class=
"left flex-1 basis-full xl:basis-3/4 transition-all duration-500"
>
<div
class=
"tabs-container h-70px flex relative md:h-60px rounded-lg mb-3"
>
<div
ref=
"tabsRef"
class=
"tabs-container h-70px flex relative md:h-60px rounded-lg mb-3"
>
<div
v-for=
"tab in tabs"
:key=
"tab.path"
...
...
@@ -199,7 +199,16 @@
<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>
<h2
class=
"text-xs sm:text-sm mb-1 text-gray-600 ml-3"
>
<span
v-for=
"item in taskTypeList"
:key=
"item.value"
class=
"text-#333 cursor-pointer after:content-['|'] after:mx-2 after:text-#999 last:after:content-none"
:class=
"
{ 'text-#999': currentTask !== item.value }"
@click="currentTask = item.value"
>
{{
item
.
label
}}
</span>
</h2>
</div>
</div>
...
...
@@ -239,7 +248,6 @@
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
onMounted
}
from
'vue'
import
{
useRouter
,
useRoute
}
from
'vue-router'
import
front
from
'@/assets/img/culture/front_page.png'
import
ya
from
'@/assets/img/culture/ya_culture.png'
...
...
@@ -249,11 +257,13 @@ import { getTaskList, dailySign, getCarouselList } from '@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
TaskTypeEnum
}
from
'@/constants'
import
type
{
CarouselItemDto
}
from
'@/api'
import
{
TABS_REF_KEY
}
from
'@/constants'
const
route
=
useRoute
()
const
router
=
useRouter
()
const
carouselList
=
ref
<
CarouselItemDto
[]
>
([])
const
tabsRef
=
useTemplateRef
(
'tabsRef'
)
const
tabs
=
[
{
name
:
'首页'
,
path
:
'deshboard'
,
img
:
front
,
svg
:
'culture-home'
},
...
...
@@ -265,6 +275,19 @@ const activeTab = ref(
tabs
.
find
((
item
)
=>
item
.
path
===
route
.
path
.
split
(
'/'
).
at
(
-
1
))?.
name
||
'首页'
,
)
const
taskTypeList
=
[
{
label
:
'常规任务'
,
value
:
TaskTypeEnum
.
REGULAR_TASK
,
},
{
label
:
'特殊任务'
,
value
:
TaskTypeEnum
.
SPECIAL_TASK
,
},
]
const
currentTask
=
ref
(
TaskTypeEnum
.
REGULAR_TASK
)
const
toggleTab
=
(
tab
:
{
name
:
string
;
path
:
string
})
=>
{
activeTab
.
value
=
tab
.
name
router
.
push
(
`/mainContainer/
${
tab
.
path
}
`
)
...
...
@@ -292,6 +315,8 @@ onMounted(async () => {
const
{
data
}
=
await
getCarouselList
()
carouselList
.
value
=
data
})
provide
(
TABS_REF_KEY
,
tabsRef
)
</
script
>
<
style
lang=
"scss"
scoped
>
.main-container
{
...
...
src/layoutCulture/components/postForm.tsx
View file @
aefe108b
...
...
@@ -3,7 +3,7 @@ import UploadFile from '@/components/common/UploadFile/index.vue'
import
{
useResetData
}
from
'@/hooks'
export
default
defineComponent
((
_
,
{
expose
})
=>
{
const
[
form
]
=
useResetData
({
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
faceUrl
:
''
,
...
...
@@ -28,16 +28,24 @@ export default defineComponent((_, { expose }) => {
return
null
}
}
const
resetFields
=
()
=>
{
formRef
.
value
?.
resetFields
()
resetForm
()
}
expose
({
validate
,
resetFields
,
})
return
()
=>
(
<
div
>
<
el
-
form
ref=
{
formRef
}
model=
{
form
.
value
}
label
-
width=
"100px"
label
-
position=
"top"
label
-
width=
"auto"
label
-
position=
"right"
size=
"small"
rules=
{
rules
}
>
<
el
-
form
-
item
label=
"标题"
prop=
"title"
>
...
...
src/layoutCulture/components/practiceForm.tsx
View file @
aefe108b
import
{
ArticleTypeEnum
,
ReleaseStatusEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
,
ReleaseStatusEnum
,
SendTypeEnum
}
from
'@/constants'
import
UploadFile
from
'@/components/common/UploadFile/index.vue'
import
SelectTags
from
'@/components/common/SelectTags/index.vue'
...
...
@@ -6,7 +6,7 @@ import SelectTags from '@/components/common/SelectTags/index.vue'
import
{
useResetData
}
from
'@/hooks'
export
default
defineComponent
((
_
,
{
expose
})
=>
{
const
[
form
]
=
useResetData
({
const
[
form
,
resetForm
]
=
useResetData
({
title
:
''
,
content
:
''
,
faceUrl
:
''
,
...
...
@@ -14,6 +14,8 @@ export default defineComponent((_, { expose }) => {
type
:
ArticleTypeEnum
.
PRACTICE
,
mainTagId
:
''
,
tagList
:
[],
sendType
:
SendTypeEnum
.
IMMEDIATE
,
sendTime
:
''
,
})
const
formRef
=
ref
<
InstanceType
<
typeof
ElForm
>>
()
const
rules
=
{
...
...
@@ -22,6 +24,8 @@ export default defineComponent((_, { expose }) => {
faceUrl
:
[{
required
:
true
,
message
:
'请上传贴图'
,
trigger
:
'change'
}],
releaseStatus
:
[{
required
:
true
,
message
:
'请选择发布时间'
,
trigger
:
'blur'
}],
mainTagId
:
[{
required
:
true
,
message
:
'请选择主标签'
,
trigger
:
'blur'
}],
sendType
:
[{
required
:
true
,
message
:
'请选择发布类型'
,
trigger
:
'blur'
}],
sendTime
:
[{
required
:
true
,
message
:
'请选择发布时间'
,
trigger
:
'blur'
}],
}
const
transformForm
=
()
=>
{
...
...
@@ -53,16 +57,23 @@ export default defineComponent((_, { expose }) => {
return
allTags
.
filter
((
tag
)
=>
tag
.
value
!==
Number
(
form
.
value
.
mainTagId
))
}
const
resetFields
=
()
=>
{
formRef
.
value
?.
resetFields
()
resetForm
()
}
expose
({
validate
,
resetFields
,
})
return
()
=>
(
<
div
>
<
el
-
form
size=
"small"
ref=
{
formRef
}
model=
{
form
.
value
}
label
-
width=
"
100px
"
label
-
position=
"
top
"
label
-
width=
"
auto
"
label
-
position=
"
right
"
rules=
{
rules
}
>
<
el
-
form
-
item
label=
"标题"
prop=
"title"
>
...
...
@@ -89,27 +100,63 @@ export default defineComponent((_, { expose }) => {
<
UploadFile
v
-
model=
{
form
.
value
.
faceUrl
}
/>
</
el
-
form
-
item
>
<
el
-
form
-
item
label=
"主标签"
prop=
"mainTagId"
>
{
/* @ts-ignore */
}
<
SelectTags
v
-
model=
{
form
.
value
.
mainTagId
}
/>
{
{
// @ts-ignore
default
:
()
=>
<
SelectTags
v
-
model=
{
form
.
value
.
mainTagId
}
/>,
label
:
()
=>
(
// <el-tooltip content="主标签最多选1个" placement="top">
<
span
class=
"cursor-pointer"
>
副标签
{
/* <el-icon class="ml-1">
<InfoFilled />
</el-icon> */
}
</
span
>
// </el-tooltip>
),
}
}
</
el
-
form
-
item
>
<
el
-
form
-
item
label=
"副标签"
>
{
/* @ts-ignore */
}
{
{
default
:
()
=>
(
// @ts-ignore
<
SelectTags
v
-
model=
{
form
.
value
.
tagList
}
maxSelectedTags=
{
2
}
filterTagsFn=
{
filterTagsFn
}
maxSelectedTags=
{
3
}
/>
),
label
:
()
=>
(
// <el-tooltip content="副标签最多选3个" placement="top">
<
span
class=
"cursor-pointer"
>
副标签
{
/* <el-icon class="ml-1">
<InfoFilled />
</el-icon> */
}
</
span
>
// </el-tooltip>
),
}
}
</
el
-
form
-
item
>
<
el
-
form
-
item
label=
"发布
时间"
prop=
"releaseStatus
"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
releaseStatus
}
class=
"radio-group"
>
<
el
-
radio
value=
{
ReleaseStatusEnum
.
PUBLISH
}
class=
"radio-item immediate"
>
<
el
-
form
-
item
label=
"发布
类型"
prop=
"sendType
"
>
<
el
-
radio
-
group
v
-
model=
{
form
.
value
.
sendType
}
class=
"radio-group"
>
<
el
-
radio
value=
{
SendTypeEnum
.
IMMEDIATE
}
class=
"radio-item immediate"
>
立即发布
</
el
-
radio
>
<
el
-
radio
value=
{
ReleaseStatusEnum
.
DRAFT
}
class=
"radio-item scheduled"
>
<
el
-
radio
value=
{
SendTypeEnum
.
SCHEDULED
}
class=
"radio-item scheduled"
>
定时发布
</
el
-
radio
>
</
el
-
radio
-
group
>
</
el
-
form
-
item
>
{
form
.
value
.
sendType
===
SendTypeEnum
.
SCHEDULED
&&
(
<
el
-
form
-
item
label=
"发布时间"
prop=
"sendTime"
>
<
el
-
date
-
picker
class=
"ml-2"
v
-
model=
{
form
.
value
.
sendTime
}
type=
"datetime"
placeholder=
"请选择发布时间"
/>
</
el
-
form
-
item
>
)
}
</
el
-
form
>
</
div
>
)
...
...
src/layoutCulture/components/publishDialog.vue
View file @
aefe108b
...
...
@@ -3,37 +3,27 @@
v-model=
"dialogVisible"
:title=
"dialogTitle"
:close-on-click-modal=
"false"
width=
"600px"
height=
"600px"
:show-close=
"false"
width=
"500px"
class=
"post-dialog"
align-center
@
closed=
"handleClosed"
>
<div
class=
"bg-white/95 rounded-16px p-24px backdrop-blur-10px"
>
<
keep-alive
>
<
!--
<keep-alive>
--
>
<component
:is=
"currentFormComp"
ref=
"formComponentRef"
/>
<
/keep-alive
>
<
!--
</keep-alive>
--
>
<!-- 底部按钮 -->
<div
class=
"flex justify-end gap-12px mt-24px pt-20px border-t border-blue-100"
>
<button
@
click
.
prevent=
"handleCancel"
class=
"bg-white/80 border-2 border-gray-200 text-gray-600 rounded-10px px-20px py-10px font-500 transition-all-300 hover:bg-white/90 hover:border-gray-300"
>
取消
</button>
<button
@
click
.
prevent=
"handlePreview"
class=
"bg-white/80 border-2 border-blue-300 text-blue-500 rounded-10px px-20px py-10px font-500 transition-all-300 hover:bg-blue-50 hover:border-blue-500"
>
预览
</button>
<button
@
click
.
prevent=
"handleSubmit"
class=
"cursor-pointer bg-gradient-to-r from-blue-400 to-cyan-400 border-none rounded-10px px-24px py-10px font-500 text-white transition-all-300 hover:transform hover:translate-y--2px hover:shadow-lg hover:shadow-blue-300"
<div
class=
"flex justify-end gap-1"
>
<el-button
@
click=
"handleClosed"
>
取消
</el-button>
<el-button
@
click=
"handleSaveDraft"
class=
""
>
存草稿
</el-button>
<el-button
type=
"primary"
@
click=
"handleSubmit"
class=
"px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200"
>
发布
</button>
</
el-
button>
</div>
</div>
</el-dialog>
...
...
@@ -61,11 +51,11 @@ const typeMap: Record<ArticleTypeEnum, { title: string; component: Component }>
},
[
ArticleTypeEnum
.
POST
]:
{
title
:
'帖子'
,
component
:
PostForm
,
component
:
defineAsyncComponent
(()
=>
import
(
'./postForm.tsx'
))
,
},
[
ArticleTypeEnum
.
PRACTICE
]:
{
title
:
'实践'
,
component
:
PracticeForm
,
component
:
defineAsyncComponent
(()
=>
import
(
'./practiceForm.tsx'
))
,
},
[
ArticleTypeEnum
.
COLUMN
]:
{
title
:
'专栏'
,
...
...
@@ -99,14 +89,15 @@ const open = (type: ArticleTypeEnum) => {
// 关闭弹窗
const
close
=
()
=>
{
dialogVisible
.
value
=
false
formComponentRef
.
value
?.
resetFields
()
}
// 事件处理
const
handleCancel
=
()
=>
{
close
()
const
handleClosed
=
()
=>
{
dialogVisible
.
value
=
false
formComponentRef
.
value
?.
resetFields
()
}
const
handle
Preview
=
async
()
=>
{}
const
handle
SaveDraft
=
async
()
=>
{}
const
handleSubmit
=
async
()
=>
{
const
formData
=
await
formComponentRef
.
value
?.
validate
()
...
...
src/layoutCulture/index.vue
View file @
aefe108b
...
...
@@ -30,8 +30,8 @@
@
click=
"router.push('/userPage')"
>
<img
class=
"w-8 h-8 object-contain flex-shrink-0"
src=
"@/assets/img/culture/avatar_girl.png
"
class=
"w-8 h-8 object-contain flex-shrink-0
rounded-full
"
:src=
"userInfo?.avatar
"
alt=
"个人中心"
/>
<span
class=
"ml-2 text-sm text-gray-700 whitespace-nowrap hidden lg:inline"
...
...
@@ -42,7 +42,7 @@
class=
"flex items-center cursor-pointer px-2 py-1 rounded sm:px-3 sm:py-2 hover:shadow-lg duration-200"
>
<img
class=
"w-
8 h-8
object-contain flex-shrink-0"
class=
"w-
7 h-7
object-contain flex-shrink-0"
src=
"@/assets/img/culture/feedback.png"
alt=
""
/>
...
...
@@ -50,12 +50,17 @@
>
意见反馈
</span
>
</div>
<el-dropdown
placement=
"top-start"
@
command=
"handlePost"
>
<el-dropdown
placement=
"top-start"
@
command=
"handlePost"
@
visible-change=
"(val) => (isDropdownHover = val)"
>
<button
class=
"cursor-pointer w-24 h-9 text-black hover:text-black bg-[linear-gradient(to_right,#B3B8FD_0%,#7083FF_100%)] border-none hover:shadow-[0_1px_8px_0_rgba(95,0,237,0.25)] duration-200 flex-1 text-xs sm:text-sm rounded-xl group"
>
<img
class=
"h-7 w-7 transition-transform duration-300 ease-in-out group-hover:rotate-90"
class=
"h-7 w-7 transition-transform duration-300 ease-in-out"
:class=
"{ 'rotate-90': isDropdownHover }"
src=
"@/assets/img/culture/post.png"
alt=
""
/>
...
...
@@ -63,18 +68,10 @@
</button>
<
template
#
dropdown
>
<el-dropdown-menu>
<el-dropdown-item
:command=
"ArticleTypeEnum.POST"
class=
"group"
>
帖子
</el-dropdown-item
>
<el-dropdown-item
:command=
"ArticleTypeEnum.PRACTICE"
class=
"group"
>
实践
</el-dropdown-item
>
<el-dropdown-item
:command=
"ArticleTypeEnum.COLUMN"
class=
"group"
>
专栏
</el-dropdown-item
>
<el-dropdown-item
:command=
"ArticleTypeEnum.INTERVIEW"
class=
"group"
>
专访
</el-dropdown-item
>
<el-dropdown-item
:command=
"ArticleTypeEnum.POST"
>
帖子
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.PRACTICE"
>
实践
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.COLUMN"
>
专栏
</el-dropdown-item>
<el-dropdown-item
:command=
"ArticleTypeEnum.INTERVIEW"
>
专访
</el-dropdown-item>
</el-dropdown-menu>
</
template
>
</el-dropdown>
...
...
@@ -107,6 +104,12 @@ import OnlineTime from './components/onlineTime.vue'
import
type
{
RouteLocationNormalizedLoadedGeneric
}
from
'vue-router'
import
PublishDialog
from
'./components/publishDialog.vue'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
{
storeToRefs
}
from
'pinia'
import
{
useUserStore
}
from
'@/stores/user'
const
userStore
=
useUserStore
()
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
router
=
useRouter
()
const
search
=
ref
(
''
)
...
...
@@ -129,6 +132,7 @@ const handlePost = async (type: string) => {
// router.push(command)
PublishDialogRef
.
value
?.
open
(
type
)
}
const
isDropdownHover
=
ref
(
false
)
</
script
>
<
style
lang=
"scss"
scoped
>
.layout-culture
{
...
...
src/views/ask/index.vue
View file @
aefe108b
...
...
@@ -233,21 +233,7 @@
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<button
class=
"back-top-btn group 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"
title=
"回到顶部"
@
click=
"handleBackTop"
>
<div
class=
"w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300 shadow-sm"
>
<SvgIcon
color=
"#409eff"
name=
"icon_top"
size=
"16"
/>
</div>
<span
class=
"text-sm font-medium text-gray-700 group-hover:text-blue-600 transition-colors"
>
回到顶部
</span
>
</button>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
...
...
@@ -272,7 +258,6 @@
</
template
>
<
script
setup
lang=
"ts"
name=
"CultureAsk"
>
import
{
ref
}
from
'vue'
import
Tabs
from
'@/components/common/Tabs'
import
{
User
,
...
...
@@ -284,6 +269,8 @@ import {
ChatDotRound
,
ArrowDown
,
}
from
'@element-plus/icons-vue'
import
{
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
...
...
@@ -297,12 +284,10 @@ const tabs = [
const
questionContent
=
ref
(
''
)
const
tagInput
=
ref
(
''
)
const
handleBackTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
'smooth'
,
})
}
// 示例数据
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
ScrollTopComp
}
=
useScrollTop
(
tabsRef
!
)
const
questions
=
ref
([
{
title
:
'什么是顶级思维?'
,
...
...
src/views/home/components/recommendList.vue
View file @
aefe108b
<
template
>
<div>
<template
v-if=
"list.length > 0"
>
<div
ref=
"listRef"
>
<template
v-if=
"list.length > 0
&& !loading
"
>
<div
class=
"space-y-3 sm:space-y-4"
>
<div
v-for=
"item in list"
...
...
@@ -55,7 +55,7 @@
<!-- 时间 -->
<span
class=
"text-gray-400 font-medium ml-auto sm:ml-0"
>
<span
class=
"hidden sm:inline"
>
{{
dayjs
(
item
.
createTime
).
format
(
'YYYY-MM-DD HH:mm'
)
dayjs
(
item
.
createTime
*
1000
).
format
(
'YYYY-MM-DD HH:mm'
)
}}
</span>
</span>
</div>
...
...
@@ -86,21 +86,7 @@
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<button
class=
"back-top-btn group 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"
title=
"回到顶部"
@
click=
"handleBackTop"
>
<div
class=
"w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300 shadow-sm"
>
<SvgIcon
color=
"#409eff"
name=
"icon_top"
size=
"16"
/>
</div>
<span
class=
"text-sm font-medium text-gray-700 group-hover:text-blue-600 transition-colors"
>
回到顶部
</span
>
</button>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
...
...
@@ -115,7 +101,11 @@
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
@
current-change=
"goToPage"
@
current-change=
"
(e) =>
{
;(handleBackTop(), goToPage(e))
}
"
@size-change="changePageSize"
/>
</div>
...
...
@@ -124,6 +114,12 @@
</div>
</div>
</
template
>
<
template
v-else-if=
"loading"
>
<div
class=
"flex items-center justify-center h-full"
>
<el-icon
class=
"is-loading mr-2 text-gray-500"
><Loading
/></el-icon>
<span
class=
"text-gray-500"
>
加载中...
</span>
</div>
</
template
>
<
template
v-else
>
<div
class=
"flex items-center justify-center h-full"
>
<el-empty
description=
"暂无数据"
/>
...
...
@@ -135,23 +131,23 @@
<
script
setup
lang=
"ts"
name=
"RecommendList"
>
import
{
usePageSearch
}
from
'@/hooks'
import
{
getArticleList
}
from
'@/api'
import
{
ArticleTypeEnum
}
from
'@/constants'
import
{
ArticleTypeEnum
,
TABS_REF_KEY
}
from
'@/constants'
import
{
useScrollTop
}
from
'@/hooks'
import
dayjs
from
'dayjs'
const
router
=
useRouter
()
const
{
list
,
total
,
searchParams
,
goToPage
,
changePageSize
}
=
usePageSearch
(
getArticleList
,
{
const
{
list
,
total
,
searchParams
,
loading
,
goToPage
,
changePageSize
}
=
usePageSearch
(
getArticleList
,
{
defaultParams
:
{
type
:
ArticleTypeEnum
.
POST
},
defaultCurrent
:
1
,
defaultSize
:
5
,
})
},
)
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
handleBackTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
'smooth'
,
})
}
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
tabsRef
!
)
</
script
>
<!-- <style scoped>
/* 自定义分页器样式 */
...
...
src/views/home/components/videoList.vue
View file @
aefe108b
...
...
@@ -344,21 +344,7 @@
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<button
class=
"back-top-btn group 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"
title=
"回到顶部"
@
click=
"handleBackTop"
>
<div
class=
"w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300 shadow-sm"
>
<SvgIcon
color=
"#409eff"
name=
"icon_top"
size=
"16"
/>
</div>
<span
class=
"text-sm font-medium text-gray-700 group-hover:text-blue-600 transition-colors"
>
回到顶部
</span
>
</button>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
...
...
@@ -384,6 +370,12 @@
<
script
setup
lang=
"ts"
name=
"RecommendList"
>
import
{
useRouter
}
from
'vue-router'
import
{
useScrollTop
}
from
'@/hooks'
import
{
TABS_REF_KEY
}
from
'@/constants'
const
tabsRef
=
inject
(
TABS_REF_KEY
)
const
{
ScrollTopComp
,
handleBackTop
}
=
useScrollTop
(
tabsRef
!
)
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
15
)
...
...
@@ -396,9 +388,6 @@ const tabs = [
]
const
activeTab
=
ref
(
'latest'
)
const
handleBackTop
=
()
=>
{
window
.
scrollTo
({
top
:
0
,
behavior
:
'smooth'
})
}
const
goVideoDetail
=
(
n
:
number
)
=>
{
router
.
push
(
`/videoDetail?id=
${
n
}
`
)
...
...
src/views/home/index.vue
View file @
aefe108b
...
...
@@ -37,12 +37,14 @@ const activeTab = ref('推荐')
color
:
#000
!important
;
box-shadow
:
none
!important
;
/* 如果你还要禁掉 hover 阴影 */
}
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
0.3s
ease
;
}
.fade-enter-from
,
.fade-leave-to
{
opacity
:
0
;
transform
:
translateX
(
30px
);
filter
:
blur
(
4px
);
}
.fade-enter-active
,
.fade-leave-active
{
transition
:
all
0.4s
cubic-bezier
(
0.25
,
0.46
,
0.45
,
0.94
);
}
</
style
>
src/views/postDetail/components/actionButtons.vue
0 → 100644
View file @
aefe108b
<
template
>
<div
class=
"fixed right-50 top-70% -translate-y-50% flex flex-col items-center content-center gap-5"
>
<div
class=
"flex flex-col bg-white rounded-8px shadow-md overflow-hidden"
>
<div
v-for=
"item in stats"
:key=
"item.label"
:content=
"item.label"
placement=
"left"
:show-after=
"300"
>
<div
class=
"flex flex-col items-center justify-center w-56px h-56px transition-all duration-200 group"
@
click=
"handleClick(item)"
>
<el-icon
class=
"group-hover:text-blue-500! cursor-pointer"
size=
"20"
:style=
"
{ color: item.active ? '#409eff' : '#606266' }"
>
<component
:is=
"item.icon"
/>
</el-icon>
<span
class=
"text-12px cursor-pointer group-hover:text-blue-500!"
:style=
"
{ color: item.active ? '#409eff' : '#606266' }"
>
{{
item
.
count
}}
</span>
</div>
</div>
</div>
<ScrollTopComp
class=
"flex flex-col"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
Star
,
ChatLineSquare
,
Pointer
}
from
'@element-plus/icons-vue'
import
type
{
ArticleItemDto
}
from
'@/api'
import
type
{
Component
}
from
'vue'
import
{
useScrollTop
}
from
'@/hooks'
import
{
addOrCancelCollect
}
from
'@/api'
import
{
COMMENT_REF_KEY
}
from
'@/constants'
const
{
articleDetail
}
=
defineProps
<
{
articleDetail
:
ArticleItemDto
}
>
()
const
commentRef
=
inject
(
COMMENT_REF_KEY
)
const
{
ScrollTopComp
}
=
useScrollTop
(
window
)
const
{
handleBackTop
}
=
useScrollTop
(
commentRef
!
)
interface
StatItem
{
icon
:
Component
count
:
number
label
:
string
active
?:
boolean
actionFn
?:
()
=>
Promise
<
boolean
>
}
const
stats
=
computed
(()
=>
{
return
[
{
icon
:
Pointer
,
count
:
articleDetail
?.
praiseCount
,
label
:
'点赞'
,
active
:
articleDetail
?.
viewCount
,
},
{
icon
:
Star
,
count
:
articleDetail
?.
collectionCount
,
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
}
},
},
{
icon
:
ChatLineSquare
,
count
:
articleDetail
?.
replyCount
,
label
:
'评论'
,
active
:
articleDetail
?.
isRecommend
,
actionFn
:
handleBackTop
,
},
]
})
const
handleClick
=
async
(
item
:
StatItem
)
=>
{
if
(
item
.
actionFn
)
{
await
item
.
actionFn
()
}
}
</
script
>
src/views/postDetail/index.vue
View file @
aefe108b
<
template
>
<div
class=
"min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-cyan-50"
>
<!--
<div
class=
"max-w-6xl mx-auto px-4 py-6 grid grid-cols-1 lg:grid-cols-4 gap-6"
>
-->
<div
class=
"min-h-screen px-20"
>
<!-- 主内容区 -->
<ActionButtons
:articleDetail=
"articleDetail"
></ActionButtons>
<div
class=
"lg:col-span-3"
>
<!-- 帖子主体 -->
<div
...
...
@@ -32,8 +32,8 @@
</span>
</div>
<p
class=
"text-sm text-gray-500 mt-1"
>
{{
dayjs
(
articleDetail
?.
createTime
||
0
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
·
{{
articleDetail
?.
viewCount
||
0
}}
阅读
{{
dayjs
(
(
articleDetail
?.
createTime
||
0
)
*
1000
).
format
(
'YYYY-MM-DD HH:mm:ss'
)
}}
·
{{
articleDetail
?.
viewCount
||
0
}}
阅读
</p>
</div>
<button
...
...
@@ -95,38 +95,11 @@
</div>
</div>
</div>
<!-- 互动按钮 -->
<div
class=
"px-6 py-4 border-t border-gray-100 bg-gray-50/50"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-6"
>
<button
class=
"flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 text-gray-600 transition-all"
>
<i
class=
"i-carbon-favorite"
></i>
<span>
128
</span>
</button>
<button
class=
"flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 text-gray-600 transition-all"
>
<i
class=
"i-carbon-bookmark"
></i>
<span>
56
</span>
</button>
<button
class=
"flex items-center gap-2 px-4 py-2 rounded-full hover:bg-gray-100 text-gray-600 transition-all"
>
<i
class=
"i-carbon-share"
></i>
<span>
分享
</span>
</button>
</div>
</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"
>
<!-- 评论筛选 -->
...
...
@@ -137,22 +110,22 @@
>
<div
class=
"flex items-center gap-2"
>
<button
class=
"px-3 py-1.5 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full shadow-md"
class=
"
cursor-pointer
px-3 py-1.5 text-sm bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-full shadow-md"
>
最热
</button>
<button
class=
"px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
class=
"
cursor-pointer
px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
>
最新
</button>
<button
class=
"px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
class=
"
cursor-pointer
px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
>
置顶
</button>
<button
class=
"px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
class=
"
cursor-pointer
px-3 py-1.5 text-sm hover:bg-gray-100 text-gray-600 rounded-full transition-all"
>
精选
</button>
...
...
@@ -163,17 +136,14 @@
<!-- 发表评论 -->
<div
class=
"p-4 border-b border-gray-100"
>
<div
class=
"flex gap-3"
>
<img
src=
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<textarea
<el-input
v-model=
"comment"
type=
"textarea"
placeholder=
"写下你的评论..."
class=
"w-full p-3 border border-gray-200 rounded-xl resize-none focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
rows=
"3"
></textarea>
:rows=
"3"
></el-input>
<div
class=
"flex justify-between items-center mt-3"
>
<div
class=
"flex items-center gap-2 text-sm text-gray-500"
>
<button
class=
"hover:text-blue-500 transition-colors"
>
...
...
@@ -184,7 +154,9 @@
</button>
</div>
<button
class=
"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"
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"
>
发表
</button>
...
...
@@ -224,7 +196,12 @@
<i
class=
"i-carbon-favorite"
></i>
<span>
24
</span>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
回复
</button>
<button
class=
"cursor-pointer hover:text-blue-500 transition-colors"
@
click=
"handleReply(1)"
>
回复
</button>
</div>
</div>
...
...
@@ -244,12 +221,62 @@
<p
class=
"text-sm text-gray-700"
>
谢谢认可!确实这个组合在开发效率上提升很大。
</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
@
click=
"handleReply(1)"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
</div>
</div>
<div
v-show=
"currentId === 1"
class=
"flex gap-3"
>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<el-input
v-model=
"comment"
type=
"textarea"
placeholder=
"写下你的评论..."
:rows=
"3"
></el-input>
<div
class=
"flex justify-between items-center mt-3"
>
<div
class=
"flex items-center gap-2 text-sm text-gray-500"
>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-face-satisfied"
></i>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-image"
></i>
</button>
</div>
<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"
>
发表
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<el-divider
/>
<!-- 评论2 -->
<div
class=
"p-4 hover:bg-gray-50/50 transition-colors"
>
...
...
@@ -278,30 +305,327 @@
</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>
<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
@
click=
"handleReply(1)"
class=
"cursor-pointer hover:text-blue-500 transition-colors"
>
回复
</button>
</div>
</div>
</div>
</div>
<div
v-show=
"currentId === 1"
class=
"flex gap-3"
>
<img
:src=
"userInfo?.avatar"
alt=
""
class=
"w-10 h-10 rounded-full object-cover"
/>
<div
class=
"flex-1"
>
<el-input
v-model=
"comment"
type=
"textarea"
placeholder=
"写下你的评论..."
:rows=
"3"
></el-input>
<div
class=
"flex justify-between items-center mt-3"
>
<div
class=
"flex items-center gap-2 text-sm text-gray-500"
>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-face-satisfied"
></i>
</button>
<button
class=
"hover:text-blue-500 transition-colors"
>
<i
class=
"i-carbon-image"
></i>
</button>
</div>
<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"
>
发表
</button>
</div>
</div>
</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>
<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-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>
</div>
</div>
<!--
</div>
-->
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
dayjs
from
'dayjs'
import
{
getArticleDetail
,
type
ArticleItemDto
}
from
'@/api'
import
ActionButtons
from
'./components/actionButtons.vue'
import
{
useUserStore
}
from
'@/stores'
import
{
storeToRefs
}
from
'pinia'
import
{
COMMENT_REF_KEY
}
from
'@/constants'
const
userStore
=
useUserStore
()
const
commentRef
=
useTemplateRef
<
HTMLElement
|
null
>
(
'commentRef'
)
const
{
userInfo
}
=
storeToRefs
(
userStore
)
const
route
=
useRoute
()
const
id
=
route
.
params
.
articleId
as
string
const
articleDetail
=
ref
<
ArticleItemDto
>
()
const
articleDetail
=
ref
({}
as
ArticleItemDto
)
const
comment
=
ref
(
''
)
const
currentId
=
ref
(
-
1
)
const
fetchArticleDetail
=
async
()
=>
{
const
{
data
}
=
await
getArticleDetail
(
id
)
articleDetail
.
value
=
data
}
const
handleReply
=
(
id
:
number
)
=>
{
comment
.
value
=
''
currentId
.
value
=
id
}
const
handleComment
=
async
()
=>
{
console
.
log
(
comment
.
value
)
}
provide
(
COMMENT_REF_KEY
,
commentRef
)
onMounted
(
async
()
=>
{
fetchArticleDetail
()
})
...
...
src/views/userPage/components/editUserInfo.vue
View file @
aefe108b
...
...
@@ -3,8 +3,9 @@
v-model=
"dialogVisible"
title=
"个人信息"
width=
"500px"
:
before-close=
"handleClo
se"
:
close-on-click-modal=
"fal
se"
class=
"user-info-dialog"
:closed=
"close"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-width=
"60px"
class=
"px-4"
>
<!-- 昵称 -->
...
...
@@ -75,11 +76,6 @@ const close = () => {
resetForm
()
}
// 关闭弹窗前的处理
const
handleClose
=
(
done
:
()
=>
void
)
=>
{
done
()
}
// 取消操作
const
handleCancel
=
()
=>
{
close
()
...
...
src/views/userPage/components/selfCollect.vue
0 → 100644
View file @
aefe108b
<
template
>
<div
class=
"w-full h-full bg-white rounded-lg overflow-hidden"
>
<!-- Custom Tabs (保持原样式) -->
<div
class=
"flex border-b border-gray-200"
>
<div
v-for=
"tab in tabs"
:key=
"tab.key"
class=
"px-6 py-4 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors relative"
:class=
"[
searchParams.type === tab.key
? 'text-blue-500! font-medium'
: 'text-gray-600 hover:text-blue-400',
]"
@
click=
"toggleTab(tab.key)"
>
{{
tab
.
label
}}
</div>
</div>
<!-- Content Area -->
<div
class=
"flex-1 flex flex-col"
>
<!-- List Container -->
<div
class=
"flex-1 p-6"
>
<!-- Loading State -->
<div
v-if=
"loading"
class=
"flex items-center justify-center h-64"
>
<el-icon
class=
"is-loading mr-2 text-gray-500"
><Loading
/></el-icon>
<span
class=
"text-gray-500"
>
加载中...
</span>
</div>
<!-- Empty State -->
<div
v-else-if=
"!list.length"
class=
"flex flex-col items-center justify-center h-64"
>
<div
class=
"w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4"
>
<el-icon
class=
"text-2xl text-gray-300"
><Document
/></el-icon>
</div>
<div
class=
"text-gray-500 text-lg mb-2"
>
暂无内容
</div>
<div
class=
"text-gray-400 text-sm"
>
{{
getEmptyText
()
}}
</div>
</div>
<!-- List Items -->
<div
v-else
class=
"space-y-4"
>
<div
v-for=
"item in paginatedList"
:key=
"item.id"
class=
"flex items-center p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
>
<!-- Avatar -->
<el-avatar
class=
"mr-4 flex-shrink-0"
:size=
"48"
style=
"background-color: #e5f3ff"
>
<el-icon
class=
"text-blue-500"
><User
/></el-icon>
</el-avatar>
<!-- Content -->
<div
class=
"flex-1 min-w-0"
>
<div
class=
"text-gray-900 font-medium truncate"
>
{{
item
.
title
}}
</div>
<div
class=
"text-gray-500 text-sm mt-1 truncate"
>
{{
item
.
description
}}
</div>
</div>
<!-- Meta Info -->
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-icon
class=
"mr-1"
><Clock
/></el-icon>
<span>
{{
item
.
time
}}
</span>
</div>
</div>
</div>
</div>
<!-- Custom Pagination (保持原样式) -->
<div
v-if=
"list.length"
class=
"flex items-center justify-between px-6 py-4 border-t border-gray-200"
>
<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=
"[10, 20, 30, 40]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
/>
</div>
</div>
</div>
</div>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
Loading
,
User
,
Clock
,
Document
}
from
'@element-plus/icons-vue'
import
{
getSelfCollectList
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
ArticleTypeEnum
}
from
'@/constants'
interface
TabItem
{
key
:
string
label
:
string
}
const
toggleTab
=
(
key
:
string
)
=>
{
searchParams
.
value
.
type
=
key
refresh
()
}
const
tabs
:
TabItem
[]
=
[
{
key
:
'posts'
,
label
:
'帖子'
},
{
key
:
'videos'
,
label
:
'视频'
},
{
key
:
'questions'
,
label
:
'问题'
},
{
key
:
'articles'
,
label
:
'专栏'
},
{
key
:
'practice'
,
label
:
'实践'
},
{
key
:
'interviews'
,
label
:
'专访'
},
]
// State
const
{
list
,
loading
,
searchParams
,
total
,
refresh
}
=
usePageSearch
(
getSelfCollectList
,
{
defaultParams
:
{
type
:
ArticleTypeEnum
.
POST
,
},
})
// Computed
const
paginatedList
=
computed
(()
=>
{
const
start
=
(
searchParams
.
value
.
current
-
1
)
*
searchParams
.
value
.
size
const
end
=
start
+
searchParams
.
value
.
size
return
list
.
value
.
slice
(
start
,
end
)
})
const
getEmptyText
=
()
=>
{
const
emptyTexts
:
Record
<
string
,
string
>
=
{
posts
:
'还没有发布任何帖子'
,
videos
:
'还没有上传任何视频'
,
questions
:
'还没有提出任何问题'
,
articles
:
'还没有发表任何专栏文章'
,
practice
:
'还没有分享任何实践经验'
,
interviews
:
'还没有参与任何专访'
,
}
return
emptyTexts
[
searchParams
.
type
]
||
'暂无数据'
}
</
script
>
src/views/userPage/components/selfPraise.vue
0 → 100644
View file @
aefe108b
<
template
>
<div
class=
"w-full h-full bg-white rounded-lg overflow-hidden"
>
<!-- Custom Tabs (保持原样式) -->
<div
class=
"flex border-b border-gray-200"
>
<div
v-for=
"tab in tabs"
:key=
"tab.key"
class=
"px-6 py-4 cursor-pointer text-gray-600 hover:text-blue-500 transition-colors relative"
:class=
"[
searchParams.type === tab.key
? 'text-blue-500! font-medium'
: 'text-gray-600 hover:text-blue-400',
]"
@
click=
"toggleTab(tab.key)"
>
{{
tab
.
label
}}
</div>
</div>
<!-- Content Area -->
<div
class=
"flex-1 flex flex-col"
>
<!-- List Container -->
<div
class=
"flex-1 p-6"
>
<!-- Loading State -->
<div
v-if=
"loading"
class=
"flex items-center justify-center h-64"
>
<el-icon
class=
"is-loading mr-2 text-gray-500"
><Loading
/></el-icon>
<span
class=
"text-gray-500"
>
加载中...
</span>
</div>
<!-- Empty State -->
<div
v-else-if=
"!list.length"
class=
"flex flex-col items-center justify-center h-64"
>
<div
class=
"w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4"
>
<el-icon
class=
"text-2xl text-gray-300"
><Document
/></el-icon>
</div>
<div
class=
"text-gray-500 text-lg mb-2"
>
暂无内容
</div>
<div
class=
"text-gray-400 text-sm"
>
{{
getEmptyText
()
}}
</div>
</div>
<!-- List Items -->
<div
v-else
class=
"space-y-4"
>
<div
v-for=
"item in paginatedList"
:key=
"item.id"
class=
"flex items-center p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
>
<!-- Avatar -->
<el-avatar
class=
"mr-4 flex-shrink-0"
:size=
"48"
style=
"background-color: #e5f3ff"
>
<el-icon
class=
"text-blue-500"
><User
/></el-icon>
</el-avatar>
<!-- Content -->
<div
class=
"flex-1 min-w-0"
>
<div
class=
"text-gray-900 font-medium truncate"
>
{{
item
.
title
}}
</div>
<div
class=
"text-gray-500 text-sm mt-1 truncate"
>
{{
item
.
description
}}
</div>
</div>
<!-- Meta Info -->
<div
class=
"flex items-center text-gray-400 text-sm ml-4"
>
<el-icon
class=
"mr-1"
><Clock
/></el-icon>
<span>
{{
item
.
time
}}
</span>
</div>
</div>
</div>
</div>
<!-- Custom Pagination (保持原样式) -->
<div
v-if=
"list.length"
class=
"flex items-center justify-between px-6 py-4 border-t border-gray-200"
>
<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=
"[10, 20, 30, 40]"
layout=
"prev, pager, next, jumper, total"
:total=
"total"
class=
"custom-pagination"
/>
</div>
</div>
</div>
</div>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
Loading
,
User
,
Clock
,
Document
}
from
'@element-plus/icons-vue'
import
{
getSelfPraiseList
}
from
'@/api'
import
{
usePageSearch
}
from
'@/hooks'
import
{
ArticleTypeEnum
}
from
'@/constants'
interface
TabItem
{
key
:
string
label
:
string
}
const
toggleTab
=
(
key
:
string
)
=>
{
searchParams
.
value
.
type
=
key
refresh
()
}
const
tabs
:
TabItem
[]
=
[
{
key
:
'posts'
,
label
:
'帖子'
},
{
key
:
'videos'
,
label
:
'视频'
},
{
key
:
'questions'
,
label
:
'问题'
},
{
key
:
'articles'
,
label
:
'专栏'
},
{
key
:
'practice'
,
label
:
'实践'
},
{
key
:
'interviews'
,
label
:
'专访'
},
]
// State
const
{
list
,
loading
,
searchParams
,
total
,
refresh
}
=
usePageSearch
(
getSelfPraiseList
,
{
defaultParams
:
{
type
:
ArticleTypeEnum
.
POST
,
},
})
// Computed
const
paginatedList
=
computed
(()
=>
{
const
start
=
(
searchParams
.
value
.
current
-
1
)
*
searchParams
.
value
.
size
const
end
=
start
+
searchParams
.
value
.
size
return
list
.
value
.
slice
(
start
,
end
)
})
const
getEmptyText
=
()
=>
{
const
emptyTexts
:
Record
<
string
,
string
>
=
{
posts
:
'还没有发布任何帖子'
,
videos
:
'还没有上传任何视频'
,
questions
:
'还没有提出任何问题'
,
articles
:
'还没有发表任何专栏文章'
,
practice
:
'还没有分享任何实践经验'
,
interviews
:
'还没有参与任何专访'
,
}
return
emptyTexts
[
searchParams
.
type
]
||
'暂无数据'
}
</
script
>
src/views/userPage/index.vue
View file @
aefe108b
...
...
@@ -6,7 +6,6 @@
<div
class=
"absolute top-4 right-4 flex gap-2"
>
<el-button
type=
"info"
plain
size=
"small"
>
清除缓存
</el-button>
<el-button
type=
"info"
plain
size=
"small"
>
切换账号
</el-button>
<el-button
type=
"primary"
size=
"small"
>
编辑个人资料
</el-button>
</div>
</div>
...
...
@@ -58,8 +57,13 @@
<!-- 右侧内容区域 -->
<div
class=
"flex-1"
>
<div
class=
"bg-white rounded-lg shadow-sm min-h-500px"
>
<!-- 标签页 -->
<div
class=
"border-b border-gray-200"
>
<transition
name=
"fade"
mode=
"out-in"
>
<keep-alive>
<component
:is=
"currentComponent"
/>
</keep-alive>
</transition>
<!--
<div
class=
"border-b border-gray-200"
>
<div
class=
"flex"
>
<div
v-for=
"tab in tabs"
...
...
@@ -79,9 +83,7 @@
</div>
</div>
<!-- 内容区域 -->
<div
class=
"p-8"
>
<!-- 空状态 -->
<div
class=
"flex flex-col items-center justify-center py-20"
>
<div
class=
"w-120px h-120px mb-6 opacity-30"
>
<svg
viewBox=
"0 0 1024 1024"
class=
"w-full h-full fill-gray-400"
>
...
...
@@ -97,7 +99,6 @@
<p
class=
"text-gray-400 text-sm"
>
暂无帖子
</p>
</div>
<!-- 分页 -->
<div
class=
"flex justify-center mt-8"
>
<el-pagination
v-model:current-page=
"currentPage"
...
...
@@ -107,7 +108,7 @@
:hide-on-single-page=
"true"
/>
</div>
</div>
</div>
-->
</div>
</div>
</div>
...
...
@@ -132,6 +133,9 @@ import {
import
{
useUserStore
}
from
'@/stores/user'
import
{
storeToRefs
}
from
'pinia'
import
EditUserInfo
from
'./components/editUserInfo.vue'
import
SelfCollect
from
'./components/selfCollect.vue'
import
SelfPraise
from
'./components/selfPraise.vue'
import
{
hasOfficialAccount
}
from
'@/api'
const
editUserInfoRef
=
useTemplateRef
<
InstanceType
<
typeof
EditUserInfo
>>
(
'editUserInfoRef'
)
const
userStore
=
useUserStore
()
...
...
@@ -147,19 +151,22 @@ const activeTab = ref('published')
const
currentPage
=
ref
(
1
)
// 左侧菜单项
const
menuItems
=
ref
(
[
const
menuItems
=
[
{
key
:
'posts'
,
label
:
'我的帖子'
,
icon
:
User
},
{
key
:
'activity'
,
label
:
'我的活动'
,
icon
:
Calendar
},
{
key
:
'votes'
,
label
:
'我的投票'
,
icon
:
Trophy
},
{
key
:
'articles'
,
label
:
'我的文章'
,
icon
:
Document
},
{
key
:
'favorites'
,
label
:
'我的收藏'
,
icon
:
Star
},
{
key
:
'favorites'
,
label
:
'我的收藏'
,
icon
:
Star
,
component
:
SelfCollect
},
{
key
:
'praise'
,
label
:
'我的点赞'
,
icon
:
Star
,
component
:
SelfPraise
},
{
key
:
'comments'
,
label
:
'评论回复'
,
icon
:
ChatDotRound
},
{
key
:
'follows'
,
label
:
'获赞列表'
,
icon
:
Trophy
},
{
key
:
'social'
,
label
:
'关注和粉'
,
icon
:
Link
},
{
key
:
'feedback'
,
label
:
'反馈列表'
,
icon
:
View
},
{
key
:
'screen'
,
label
:
'屏蔽管理'
,
icon
:
Setting
},
])
]
const
currentComponent
=
computed
(()
=>
{
return
menuItems
.
find
((
item
)
=>
item
.
key
===
activeMenu
.
value
)?.
component
})
// 标签页
const
tabs
=
ref
([
{
key
:
'published'
,
label
:
'发布'
},
...
...
@@ -174,6 +181,17 @@ const handleEdit = () => {
signature
:
''
,
})
}
const
getIsOfficial
=
()
=>
{
setTimeout
(
async
()
=>
{
const
{
data
}
=
await
hasOfficialAccount
()
console
.
log
(
data
)
},
1000
)
}
onMounted
(()
=>
{
getIsOfficial
()
})
</
script
>
<
style
scoped
>
...
...
@@ -181,4 +199,12 @@ const handleEdit = () => {
background-image
:
url('http://soundasia.oss-cn-shenzhen.aliyuncs.com/OA/2022/10/13/1665643787806.jpg')
;
}
/* 如果需要额外的样式可以在这里添加 */
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
0.5s
ease
;
}
.fade-enter-from
,
.fade-leave-to
{
opacity
:
0
;
}
</
style
>
src/views/ya/index.vue
View file @
aefe108b
...
...
@@ -20,21 +20,7 @@
<div
class=
"flex items-center justify-between"
>
<!-- 左侧:回到顶部按钮 -->
<div
class=
"left"
>
<button
class=
"back-top-btn group 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"
title=
"回到顶部"
@
click=
"handleBackTop"
>
<div
class=
"w-8 h-8 bg-gradient-to-r from-blue-500 to-indigo-500 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300 shadow-sm"
>
<SvgIcon
color=
"#409eff"
name=
"icon_top"
size=
"16"
/>
</div>
<span
class=
"text-sm font-medium text-gray-700 group-hover:text-blue-600 transition-colors"
>
回到顶部
</span
>
</button>
<ScrollTopComp
/>
</div>
<!-- 右侧:分页器 -->
<div
class=
"right"
>
...
...
@@ -64,6 +50,12 @@ 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
)
...
...
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