Commit 4a88ffa9 by lijiabin

【需求 17679】 wip: 继续完善页面

parent 21438b37
......@@ -3,11 +3,15 @@ import type {
AddOrUpdateArticleDto,
ArticleItemDto,
ArticleSearchParams,
InterviewOptionDto,
ColumnOptionDto,
AddCommentDto,
CommentItemDto,
CommentSearchParams,
InterviewItemDto,
ColumnItemDto,
} from './types'
import type { BackendServicePageResult } from '@/utils/request/types'
import { ArticleTypeEnum } from '@/constants'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
// 文章相关的接口(帖子 视频 实践等)
......@@ -44,32 +48,100 @@ export const getArticleDetail = (articleId: number | string) => {
}
/**
* 收藏或者取消收藏
* 获取专访列表选项 --不分页 用户新增时候选择
*/
export const addOrCancelCollect = (data: { articleId: number | string; type: ArticleTypeEnum }) => {
export const getInterviewOptions = () => {
return service.request<InterviewOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview',
method: 'POST',
})
}
/**
* 获取首页专访列表list —— 分页
*/
export const getInterviewList = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<InterviewItemDto>>({
url: '/api/yaCulture/listByPage',
method: 'POST',
data: {
...data,
type: 'interview',
},
})
}
/**
* 获取专栏列表选项-- 不分页 用户新增时候选择
*/
export const getColumnOptions = () => {
return service.request<ColumnOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=column',
method: 'POST',
})
}
/**
* 获取首页专栏列表list —— 分页
*/
export const getColumnList = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<ColumnItemDto>>({
url: '/api/yaCulture/listByPage',
method: 'POST',
data: {
...data,
type: 'column',
},
})
}
/**
* 点赞或者取消点赞文章
*/
export const addOrCanceArticlelLike = (articleId: number | string) => {
return service.request<boolean>({
url: `/api/cultureCollect/addOrCancelCollect`,
url: `/api/cultureArticle/likeArticle?articleId=${articleId}`,
method: 'POST',
})
}
/**
* 收藏或者取消收藏文章
*/
export const addOrCanceArticlelCollect = (articleId: number | string) => {
return service.request<boolean>({
url: `/api/cultureArticle/collectArticle?articleId=${articleId}`,
method: 'POST',
})
}
/**
* 获取评论列表
*/
export const getCommentList = (data: CommentSearchParams) => {
return service.request<BackendServicePageResult<CommentItemDto>>({
url: `/api/cultureComment/getComment`,
method: 'POST',
data,
})
}
/**
* 获取专访列表--不分页
* 新增评论
*/
export const getInterviewList = () => {
return service.request<InterviewItemDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview',
export const addComment = (data: AddCommentDto) => {
return service.request<boolean>({
url: `/api/cultureComment/addComment`,
method: 'POST',
data,
})
}
/**
* 获取专栏列表-- 不分页
* 点赞评论
*/
export const getColumnList = () => {
return service.request<ColumnItemDto[]>({
url: '/api/cultureColumn/listNoPage?type=column',
export const addOrCancelCommentLike = (commentId: number | string) => {
return service.request<boolean>({
url: `/api/cultureComment/likeComment?commentId=${commentId}`,
method: 'POST',
})
}
......@@ -6,6 +6,7 @@ import type { PageSearchParams } from '@/utils/request/types'
*/
export interface ArticleSearchParams extends PageSearchParams {
type?: ArticleTypeEnum
sortLogic?: number
}
/**
......@@ -41,7 +42,7 @@ interface AddOrUpdateColumnBase {
faceUrl?: string
imgUrl?: string
// 关联的专栏栏目
relateColumn: number
relateColumnId: number
mainTagId: string
isRelateColleague: BooleanFlag
sendTime?: string
......@@ -71,7 +72,7 @@ export interface AddOrUpdateInterviewBase {
faceUrl?: string
imgUrl?: string
// 关联的专访栏目
relateColumn: number
relateColumnId: number
mainTagId: string
sendTime?: string
}
......@@ -95,7 +96,6 @@ export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase {
* 文章详情
*/
// 更严格的类型定义
export interface ArticleItemDto {
id: number
title: string
......@@ -114,10 +114,17 @@ export interface ArticleItemDto {
praiseCount: number
collectionCount: number
replyCount: number
hasPraised: BooleanFlag
hasPraised: boolean
hasCollect: boolean
imgUrl: string
createUserAvatar: string
createUserName: string
}
export interface ColumnItemDto {
/**
* 专栏选项
*/
export interface ColumnOptionDto {
color: string
createTime: number
createUserId: number
......@@ -129,7 +136,34 @@ export interface ColumnItemDto {
type: 'column'
}
export interface InterviewItemDto {
/**
* 专栏列表Item
*/
export interface ColumnItemDto {
title: string
color: string
sort: number
yaColumnVoList: {
articleId: number
collectCount: number
content: string
createTime: number
description: string
faceUrl: string
hasPraised: boolean
isRecommend: number
praiseCount: number
replyCount: number
title: string
type: ArticleTypeEnum.COLUMN
viewCount: number
}[]
}
/**
* 专访选项
*/
export interface InterviewOptionDto {
color: string
createTime: number
createUserId: number
......@@ -140,3 +174,65 @@ export interface InterviewItemDto {
title: string
type: 'column'
}
/**
* 专访列表Item
*/
export interface InterviewItemDto {
title: string
color: string
sort: number
yaColumnVoList: {
articleId: number
collectCount: number
content: string
createTime: number
description: string
faceUrl: string
hasPraised: boolean
isRecommend: number
praiseCount: number
replyCount: number
title: string
type: ArticleTypeEnum.INTERVIEW
viewCount: number
}[]
}
/**
* 评论列表
*/
export interface CommentSearchParams extends PageSearchParams {
articleId: number | string
}
/**
* 新增评论
*/
export interface AddCommentDto {
articleId: number | string
content: string
pId?: number | string
}
/**
* 评论信息
*/
export interface CommentItemDto {
articleId: number
avatar: string
children: CommentItemDto[]
content: string
createTime: number
hasPraise: BooleanFlag
hiddenAvatar: string
hiddenName: string
id: number
isFeatured: number
isTop: number
pid: number
postPriseCount: number
region: string
regionHide: number
replyUser: string
userId: number
}
// 专栏列表
import service from '@/utils/request/index'
import type { ColumnItemDto } from './types'
import type { ColumnOptionDto } from './types'
/**
* 获取专栏列表
*/
export const getColumnList = () => {
return service.request<ColumnItemDto[]>({
export const getColumnOptions = () => {
return service.request<ColumnOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=column',
method: 'POST',
})
......
import { BooleanFlag } from '@/constants'
export interface ColumnItemDto {
export interface ColumnOptionDto {
color: string
createTime: number
createUserId: number
......
......@@ -4,13 +4,28 @@ import type { FielItemDto } from './types'
/**
* 获取常规的接口
*/
// export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
// const formData = new FormData()
// formData.append('file', file)
// return service.request<FielItemDto>({
// url: '/mobiles/file-upload/singleUpload',
// method: 'POST',
// data: formData,
// onUploadProgress: (progressEvent) => {
// const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1))
// onProgress?.(percentCompleted)
// },
// })
// }
/**
* 暂时调用oa正式接口
*/
import axios from 'axios'
export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
const formData = new FormData()
formData.append('file', file)
return service.request<FielItemDto>({
url: '/mobiles/file-upload/singleUpload',
method: 'POST',
data: formData,
formData.append('fileList', file)
return axios.post('http://47.112.96.71:8082/mobiles/uploadFile', formData, {
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1))
onProgress?.(percentCompleted)
......
// 专栏列表
import service from '@/utils/request/index'
import type { ColumnItemDto } from './types'
import type { ColumnOptionDto } from './types'
/**
* 获取专栏列表
*/
export const getInterviewList = () => {
return service.request<ColumnItemDto[]>({
export const getInterviewOptions = () => {
return service.request<ColumnOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview',
method: 'POST',
})
......
import { BooleanFlag } from '@/constants'
export interface InterviewItemDto {
export interface InterviewOptionDto {
color: string
createTime: number
createUserId: number
......
......@@ -58,7 +58,7 @@ export interface AuditListItemDto {
isRelateColleague: boolean
playCount: number
praiseCount: number
relateColumn: string
relateColumnId: string
releaseStatus: AuditStatusEnum
replyCount: number
showAvatar: string
......
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763611281052" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7362" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M190.193225 471.411583c14.446014 0 26.139334-11.718903 26.139334-26.13831 0-14.44499-11.69332-26.164916-26.139334-26.164916-0.271176 0-0.490164 0.149403-0.73678 0.149403l-62.496379 0.146333c-1.425466-0.195451-2.90005-0.295735-4.373611-0.295735-19.677155 0-35.621289 16.141632-35.621289 36.114522L86.622358 888.550075c0 19.949354 15.96767 35.597753 35.670407 35.597753 1.916653 0 3.808746 0.292666 5.649674 0l61.022819 0.022513c0.099261 0 0.148379 0.048095 0.24764 0.048095 0.097214 0 0.146333-0.048095 0.24457-0.048095l0.73678 0 0-0.148379c13.413498-0.540306 24.174586-11.422144 24.174586-24.960485 0-13.55983-10.760065-24.441669-24.174586-24.981974l0-0.393973-50.949392 0 1.450025-402.275993L190.193225 471.409536z" fill="#606266" p-id="7363"></path><path d="M926.52241 433.948343c-19.283182-31.445176-47.339168-44.172035-81.289398-45.546336-1.77032-0.246617-3.536546-0.39295-5.380544-0.39295l-205.447139-0.688685c13.462616-39.059598 22.698978-85.58933 22.698978-129.317251 0-28.349675-3.193739-55.962569-9.041934-82.542948l-0.490164 0.049119c-10.638291-46.578852-51.736315-81.31498-100.966553-81.31498-57.264215 0-95.466282 48.15065-95.466282 106.126063 0 3.241834-0.294712 6.387477 0 9.532097-2.996241 108.386546-91.240027 195.548698-196.23636 207.513194l0 54.881958-0.785899 222.227314 0 229.744521 10.709923 0 500.025271 0.222057 8.746198-0.243547c19.35686 0.049119 30.239721-4.817726 47.803749-16.116049 16.682961-10.761088 29.236881-25.50079 37.490869-42.156122 2.260483-3.341095 4.028757-7.075139 5.106298-11.20111l77.018118-344.324116c1.056052-4.053316 1.348718-8.181333 1.056052-12.160971C943.643346 476.446249 938.781618 453.944769 926.52241 433.948343zM893.82573 486.837924l-82.983993 367.783411-0.099261-0.049119c-2.555196 6.141884-6.879688 11.596106-12.872169 15.427364-4.177136 2.727111-8.773827 4.351098-13.414521 4.964058-1.49812-0.195451-3.046383 0-4.620227 0l-477.028511-0.540306-0.171915-407.408897c89.323375-40.266076 154.841577-79.670527 188.596356-173.661202 0.072655 0.024559 0.124843 0.049119 0.195451 0.072655 2.99931-9.137101 6.313799-20.73423 8.697079-33.164331 5.551436-29.185716 5.258771-58.123792 5.258771-58.123792-4.937452-37.98001 25.940812-52.965306 44.364417-52.965306 25.304316 0.860601 50.263777 33.656541 50.263777 52.326762 0 0 5.600555 27.563776 5.649674 57.190537 0.048095 37.366026-4.6673 56.847729-4.6673 56.847729l-0.466628 0c-5.872754 30.879288-16.214287 60.138682-30.464849 86.964654l0.36839 0.342808c-2.358721 4.815679-3.709485 10.220782-3.709485 15.943111 0 19.922748 19.088754 21.742187 38.765909 21.742187l238.761895 0.270153c0 0 14.666024 0.465604 14.690584 0.465604l0 0.100284c12.132318-0.638543 24.221658 5.207605 31.100322 16.409738 5.504364 9.016351 6.437619 19.6045 3.486404 28.988218L893.82573 486.837924z" fill="#606266" p-id="7364"></path><path d="M264.827039 924.31872c0.319272 0.024559 0.441045 0.024559 0.295735-0.024559 0.243547-0.048095 0.367367-0.074701-0.295735-0.074701s-0.539282 0.026606-0.271176 0.074701C264.43409 924.343279 264.532327 924.343279 264.827039 924.31872z" fill="#606266" p-id="7365"></path></svg>
\ No newline at end of file
......@@ -125,7 +125,9 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 上传文件
const { data } = await uploadFileApi(uploadFile.raw)
console.log('data', data)
const url = data.fileUrl || data.data[0].filePath
const name = data.fileName || data.data[0].finalName
// ✅ 上传完成后重新查找索引(第二次查找)
fileIndex = fileList.value.findIndex((file) => file.uid === uid)
......@@ -133,8 +135,8 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 更新文件信息
fileList.value[fileIndex] = {
...fileList.value[fileIndex],
url: data.fileUrl,
name: data.fileName,
url,
name,
status: 'success',
}
ElMessage.success('上传成功')
......
......@@ -2,7 +2,7 @@ import type { SetupContext, MaybeRef } from 'vue'
type Events = {
handleBackTop(): void
}
function ScrollTopComp(_: Record<string, never>, { emit }: SetupContext<Events>) {
function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
return (
<button
class="back-top-btn group cursor-pointer flex items-center gap-3 px-4 py-2.5 bg-gradient-to-r from-blue-50 to-indigo-50 hover:from-blue-100 hover:to-indigo-100 border border-blue-200/50 rounded-full transition-all duration-300 hover:shadow-lg hover:-translate-y-1 active:scale-95 shadow-sm"
......
......@@ -12,6 +12,7 @@ import type { AddOrUpdateColumnForm, AddOrUpdateColumnDto } from '@/api/article/
export default defineComponent((_, { expose }) => {
const columnStore = useColumnStore()
const { columnList } = storeToRefs(columnStore)
console.log(columnList.value)
const [form, resetForm] = useResetData<AddOrUpdateColumnForm>({
title: '',
content: '',
......@@ -23,7 +24,7 @@ export default defineComponent((_, { expose }) => {
sendType: SendTypeEnum.IMMEDIATE,
sendTime: '',
isRelateColleague: BooleanFlag.NO,
relateColumn: 0,
relateColumnId: 0,
type: ArticleTypeEnum.COLUMN,
})
const formRef = ref<InstanceType<typeof ElForm>>()
......@@ -34,7 +35,7 @@ export default defineComponent((_, { expose }) => {
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
relateColumn: [
relateColumnId: [
{ required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 },
],
}
......@@ -111,8 +112,8 @@ export default defineComponent((_, { expose }) => {
{/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} />
</el-form-item>
<el-form-item label="专栏栏目选择" prop="relateColumn">
<el-radio-group v-model={form.value.relateColumn}>
<el-form-item label="专栏栏目选择" prop="relateColumnId">
<el-radio-group v-model={form.value.relateColumnId}>
{columnList.value.map((item) => (
<el-radio value={item.id}>{item.title}</el-radio>
))}
......
......@@ -22,7 +22,7 @@ export default defineComponent((_, { expose }) => {
sendType: SendTypeEnum.IMMEDIATE,
sendTime: '',
type: ArticleTypeEnum.INTERVIEW,
relateColumn: 0,
relateColumnId: 0,
})
const formRef = ref<InstanceType<typeof ElForm>>()
const rules = {
......@@ -32,7 +32,7 @@ export default defineComponent((_, { expose }) => {
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
relateColumn: [
relateColumnId: [
{ required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 },
],
}
......@@ -109,8 +109,8 @@ export default defineComponent((_, { expose }) => {
{/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} />
</el-form-item>
<el-form-item label="专访栏目选择" prop="relateColumn">
<el-radio-group v-model={form.value.relateColumn}>
<el-form-item label="专访栏目选择" prop="relateColumnId">
<el-radio-group v-model={form.value.relateColumnId}>
{columnList.value.map((item) => (
<el-radio value={item.id}>{item.title}</el-radio>
))}
......
......@@ -27,7 +27,7 @@ export default defineComponent(
return {
...form.value,
releaseStatus,
imgUrl: form.value.imgUrl?.split(',').filter(Boolean)[0] || '',
faceUrl: form.value.imgUrl?.split(',').filter(Boolean)[0] || '',
}
}
......
......@@ -34,7 +34,7 @@ const routes = [
],
},
{
path: 'videoDetail/:',
path: 'videoDetail/:id',
name: 'CultureVideoDetail',
component: () => import('@/views/videoDetail/index.vue'),
},
......
import { defineStore } from 'pinia'
import { getColumnList } from '@/api'
import type { ColumnItemDto } from '@/api/article/types'
import { getColumnOptions } from '@/api'
import type { ColumnOptionDto } from '@/api/article/types'
/**
* 关于专栏的不分页数据
*/
export const useColumnStore = defineStore('column', () => {
const columnList = ref<ColumnItemDto[]>([])
const columnList = ref<ColumnOptionDto[]>([])
let isLoading = false
......@@ -14,7 +14,7 @@ export const useColumnStore = defineStore('column', () => {
if (isLoading) return
isLoading = true
try {
const { data } = await getColumnList()
const { data } = await getColumnOptions()
columnList.value = data
console.log(columnList.value, 'columnList')
} catch (error) {
......
import { defineStore } from 'pinia'
import { getInterviewList } from '@/api'
import type { InterviewItemDto } from '@/api/article/types'
import { getInterviewOptions } from '@/api'
import type { InterviewOptionDto } from '@/api/article/types'
/**
* 关于专访的相关数据 --不分页
*/
export const useInterviewStore = defineStore('interview', () => {
const interviewList = ref<InterviewItemDto[]>([])
const interviewList = ref<InterviewOptionDto[]>([])
let isLoading = false
......@@ -14,7 +14,7 @@ export const useInterviewStore = defineStore('interview', () => {
if (interviewList.value.length > 0) return
isLoading = true
try {
const { data } = await getInterviewList()
const { data } = await getInterviewOptions()
interviewList.value = data
console.log(interviewList.value, 'interviewList')
} catch (error) {
......
<template>
<div ref="listRef">
<div v-loading="loading" v-if="list.length > 0">
<div v-loading="loading" v-if="list.length">
<div class="space-y-3 sm:space-y-4">
<div
v-for="item in list"
......@@ -10,7 +10,7 @@
>
<div class="flex gap-3 justify-between">
<!-- 内容区域 -->
<div class="flex-1 min-w-0 flex flex-col justify-between">
<div class="flex-1 min-w-0 flex flex-col justify-between h-24">
<!-- 标题 -->
<h2
class="text-xl font-semibold text-gray-900 line-clamp-1 group-hover:text-blue-600 transition-colors duration-200 leading-tight"
......@@ -20,7 +20,7 @@
<!-- 内容摘要 -->
<div class="my-2 space-y-1">
<p class="text-gray-600 text-sm sm:text-base leading-relaxed line-clamp-2">
<p class="text-gray-600 text-sm sm:text-base leading-relaxed line-clamp-1">
{{ item.content }}
</p>
</div>
......@@ -55,7 +55,7 @@
</div>
<!-- 图片区域 -->
<div v-if="item.faceUrl" class="relative flex-shrink-0 w-36 h-24">
<div v-show="item.faceUrl" class="relative flex-shrink-0 w-36 h-24">
<img
:src="item.faceUrl"
alt="文章配图"
......@@ -116,16 +116,16 @@
<script setup lang="ts" name="RecommendList">
import { usePageSearch } from '@/hooks'
import { getArticleList } from '@/api'
import { ArticleTypeEnum, TABS_REF_KEY } from '@/constants'
import { TABS_REF_KEY } from '@/constants'
import { useScrollTop } from '@/hooks'
import dayjs from 'dayjs'
const router = useRouter()
const { list, total, searchParams, loading, goToPage, changePageSize, reset } = usePageSearch(
const { list, total, searchParams, loading, goToPage, changePageSize, refresh } = usePageSearch(
getArticleList,
{
// defaultParams: { type: ArticleTypeEnum.POST },
defaultParams: { sortLogic: 0 },
defaultCurrent: 1,
defaultSize: 5,
},
......@@ -135,6 +135,10 @@ const tabsRef = inject(TABS_REF_KEY)
const { ScrollTopComp, handleBackTop } = useScrollTop(tabsRef!)
defineExpose({
refresh: reset,
refresh: (sortLogic: number) => {
console.log('sortLogic', sortLogic)
searchParams.value.sortLogic = sortLogic
refresh()
},
})
</script>
......@@ -44,12 +44,20 @@ const activeTabComponentRef =
useTemplateRef<InstanceType<typeof RecommendList>>('activeTabComponentRef')
const handleRefresh = () => {
activeTabComponentRef.value?.refresh?.()
if (activeTab.value === '推荐') {
activeTabComponentRef.value?.refresh?.(0)
} else if (activeTab.value === '最新') {
activeTabComponentRef.value?.refresh?.(1)
} else if (activeTab.value === '视频') {
activeTabComponentRef.value?.refresh?.(2)
}
}
const handleTabChange = (tab: string) => {
if (tab === '最新') {
activeTabComponentRef.value?.refresh?.()
activeTabComponentRef.value?.refresh?.(1)
} else if (tab === '推荐') {
activeTabComponentRef.value?.refresh?.(0)
}
}
</script>
......
......@@ -10,22 +10,25 @@
<div class="flex gap-3">
<div class="left flex-1 basis-full xl:basis-3/4 transition-all duration-500">
<div ref="tabsRef" class="tabs-container h-70px flex relative md:h-60px rounded-lg mb-3">
<div ref="tabsRef" class="tabs-container h-75px flex relative rounded-lg mb-3 shadow-md">
<div
v-for="tab in tabs"
:key="tab.path"
class="flex-1 flex items-center justify-center cursor-pointer relative transition-all duration-300 hover:bg-white/10 gap-2 group"
class="flex-1 flex items-center justify-center cursor-pointer relative transition-all duration-300 group"
@click="toggleTab(tab)"
>
<div
class="flex items-center gap-2 px-12 py-2.5 rounded-xl transition-all duration-300"
:class="{
'bg-#fffdfd shadow-[inset_0_2px_4px_0_rgb(0,0,0,0.1)]': activeTab === tab.name,
'hover:bg-white/60': activeTab !== tab.name,
}"
>
<svg-icon :name="tab.svg" class="h-60px w-auto md:h-50px sm:h-40px" size="40" />
<div class="text-18px font-500 text-gray-800 md:text-16px sm:text-14px">
{{ tab.name }}
</div>
<!-- 下划线 -->
<div
class="absolute bottom-0 left-0 right-0 h-4px bg-[linear-gradient(to_right,#60a5fa_0%,#c084fc_100%)] transform scale-x-0 transition-transform duration-300 origin-center"
:class="{ 'scale-x-50': activeTab === tab.name }"
></div>
</div>
</div>
</div>
......@@ -162,13 +165,16 @@
<!-- 任务中心 -->
<div class="task-container common-box rounded-lg bg-#FFF0E5">
<div class="flex items-center justify-between mb-2">
<div>
<div class="flex items-start justify-start mb-2">
<div class="w-full">
<div class="flex items-center gap-2">
<div class="w-1 h-4 bg-gradient-to-b from-pink-500 to-rose-500 rounded-full"></div>
<h1 class="text-sm sm:text-base font-bold">任务中心</h1>
</div>
<h2 class="text-xs sm:text-sm mb-1 text-gray-600 ml-3">
<h2
class="w-full text-xs sm:text-sm mt-1 text-gray-600 flex items-center justify-between"
>
<div>
<span
v-for="item in taskTypeList"
:key="item.value"
......@@ -177,6 +183,10 @@
@click="currentTask = item.value"
>{{ item.label }}
</span>
</div>
<span class="text-#999 cursor-pointer" @click="router.push(`/userPage?key=task`)">
查看更多
</span>
</h2>
</div>
</div>
......@@ -210,13 +220,23 @@
</div>
</div>
</div>
<el-button
class="bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none hover:-translate-y-1 transition-all duration-200 text-xs sm:text-sm rounded-full"
type="primary"
<button
class="w-72px h-32px shadow-[0_1px_8px_0_rgba(255,141,54,0.25)] border-none text-xs sm:text-sm rounded-full"
:class="[
item.currentCount === item.limitCount
? 'bg-#FFC5A1'
: 'bg-[linear-gradient(to_right,#FFC5A1_0%,#FFB77F_100%)] hover:-translate-y-1 transition-all duration-200 cursor-pointer',
]"
@click="handleTask(item)"
>
<span class="text-black text-xs sm:text-sm">去完成</span>
</el-button>
<span
class="text-black text-sm"
:style="{
color: item.currentCount === item.limitCount ? '#999' : '#000',
}"
>{{ item.currentCount === item.limitCount ? '已完成' : '去完成' }}</span
>
</button>
</div>
<!-- 分割线 -->
<el-divider style="margin: 0" />
......@@ -332,6 +352,8 @@ const onDailySign = async () => {
}
const handleTask = (item: TaskItemDto) => {
if (item.currentCount === item.limitCount) return
// if (item.svgName === 'svgName') {
handleBackTop()
triggerAnimation()
......
<template>
<div>
<div v-loading="loading" v-if="list.length">
<div class="w-full max-w-6xl mx-auto">
<div
v-for="item in columnList"
:key="item.id"
v-for="(item, index) in list"
:key="index"
class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
:style="{ '--dynamic-color': item.color }"
>
......@@ -14,103 +16,119 @@
<span class="w-1 h-5 mr-2 bg-#444"></span>
{{ item.title }}
</h3>
<div class="flex items-center cursor-pointer hover:text-[var(--dynamic-color)]">
<span class="mr-1 text-14px color-#606266 hover:text-[var(--dynamic-color)]"
>查看更多</span
>
<div class="flex items-center cursor-pointer">
<span class="mr-1 text-14px color-#606266">查看更多</span>
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div v-for="item in section2Items" :key="item.id" class="group cursor-pointer">
<div v-if="item.yaColumnVoList.length" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="i in item.yaColumnVoList"
:key="i.articleId"
class="group cursor-pointer"
@click="router.push(`/postDetail/${i.articleId}`)"
>
<div class="relative mb-3 overflow-hidden rounded-lg">
<img
src="@/assets/img/culture/ask.png"
:alt="item.title"
class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
:src="i.faceUrl"
class="w-full aspect-[5/3] object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div class="absolute top-2 left-2">
<el-tag size="small" class="bg-orange-500 text-white border-none">
{{ item.tag }}
1111111
</el-tag>
</div>
</div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors">
{{ item.title }}
{{ i.title }}
</h3>
<p class="text-xs text-gray-500 mb-3 line-clamp-2">
{{ item.description }}
{{ i.content }}
</p>
<div class="flex items-center justify-between text-xs text-gray-400">
<div class="flex items-center space-x-4">
<span class="flex items-center">
<el-icon class="mr-1"><View /></el-icon>
{{ item.views }}
{{ i.viewCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><ChatDotRound /></el-icon>
{{ item.comments }}
{{ i.replyCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><Star /></el-icon>
{{ item.likes }}
{{ i.collectCount }}
</span>
</div>
<span>{{ item.date }}</span>
<span>{{ dayjs(i.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
</div>
</div>
<div v-else class="flex items-center justify-center h-48">
<el-empty description="暂无数据" />
</div>
</div>
</div>
</div>
<div class="bottom-pagination backdrop-blur-8 border-t border-gray-200">
<div class="max-w-7xl mx-auto py-6">
<div class="flex items-center justify-between">
<!-- 左侧:回到顶部按钮 -->
<div class="left">
<ScrollTopComp />
</div>
<!-- 右侧:分页器 -->
<div class="right">
<div
class="pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
:page-sizes="[15, 30, 45, 60]"
layout="prev, pager, next, jumper, total"
:total="total"
class="custom-pagination"
@current-change="
(e) => {
;(handleBackTop(), goToPage(e))
}
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<template v-else>
<div class="flex items-center justify-center h-full">
<el-empty description="暂无数据" />
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue'
import { useColumnStore } from '@/stores/column'
import { storeToRefs } from 'pinia'
import { getColumnList } from '@/api'
import { usePageSearch, useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
import dayjs from 'dayjs'
import { useRouter } from 'vue-router'
const router = useRouter()
const tabsRef = inject(TABS_REF_KEY)
const { handleBackTop, ScrollTopComp } = useScrollTop(tabsRef!)
const columnStore = useColumnStore()
const { columnList } = storeToRefs(columnStore)
const section2Items = ref([
{
id: 4,
title: '宿主,你的负面清单该更新啦!',
description: '宿主你好呀!正在文化一本分与平常心的知识,我们一起来学习吧的知识。',
image: 'https://via.placeholder.com/300x200/DDA0DD/FFFFFF?text=Image4',
tag: '推荐',
views: '300',
comments: '0',
likes: '24',
date: '2025-04-13 12:01',
},
{
id: 5,
title: '不打破惯性思维,永远无法创新',
description:
'当我们的思路被惯性思维束缚了,想要得到一些不一样的结果就会变得很困难。但是,我们可以通过一些方法来打破惯性思维。',
image: 'https://via.placeholder.com/300x200/F0E68C/FFFFFF?text=Image5',
tag: '推荐',
views: '280',
comments: '0',
likes: '24',
date: '2025-04-13 12:10',
},
const { list, total, searchParams, goToPage, changePageSize, loading } = usePageSearch(
getColumnList,
{
id: 6,
title: '文化共创|你提意见"我听你"',
description: '巧妙设手段第三名,出让各有所需满意的额外为为你们带来更多一些精彩。',
image: 'https://via.placeholder.com/300x200/FFA07A/FFFFFF?text=Image6',
tag: '推荐',
views: '280',
comments: '0',
likes: '24',
date: '2025-04-13 14:28',
defaultSize: 3,
},
])
)
</script>
<style scoped></style>
<template>
<div>
<div v-loading="loading" v-if="list.length">
<div class="w-full max-w-6xl mx-auto">
<div
v-for="item in interviewList"
:key="item.id"
v-for="(item, index) in list"
:key="index"
class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
:style="{ '--dynamic-color': item.color }"
>
......@@ -23,94 +25,112 @@
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div v-for="item in section2Items" :key="item.id" class="group cursor-pointer">
<div v-if="item.yaColumnVoList.length" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="i in item.yaColumnVoList"
:key="i.articleId"
class="group cursor-pointer"
@click="router.push(`/postDetail/${i.articleId}`)"
>
<div class="relative mb-3 overflow-hidden rounded-lg">
<img
src="@/assets/img/culture/ask.png"
:alt="item.title"
:src="i.faceUrl"
class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
/>
<div class="absolute top-2 left-2">
<el-tag size="small" class="bg-orange-500 text-white border-none">
{{ item.tag }}
1111111
</el-tag>
</div>
</div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors">
{{ item.title }}
{{ i.title }}
</h3>
<p class="text-xs text-gray-500 mb-3 line-clamp-2">
{{ item.description }}
{{ i.content }}
</p>
<div class="flex items-center justify-between text-xs text-gray-400">
<div class="flex items-center space-x-4">
<span class="flex items-center">
<el-icon class="mr-1"><View /></el-icon>
{{ item.views }}
{{ i.viewCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><ChatDotRound /></el-icon>
{{ item.comments }}
{{ i.replyCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><Star /></el-icon>
{{ item.likes }}
{{ i.collectCount }}
</span>
</div>
<span>{{ item.date }}</span>
<span>{{ dayjs(i.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
</div>
</div>
<div v-else class="flex items-center justify-center h-48">
<el-empty description="暂无数据" />
</div>
</div>
</div>
</div>
<div class="bottom-pagination backdrop-blur-8 border-t border-gray-200">
<div class="max-w-7xl mx-auto py-6">
<div class="flex items-center justify-between">
<!-- 左侧:回到顶部按钮 -->
<div class="left">
<ScrollTopComp />
</div>
<!-- 右侧:分页器 -->
<div class="right">
<div
class="pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
:page-sizes="[15, 30, 45, 60]"
layout="prev, pager, next, jumper, total"
:total="total"
class="custom-pagination"
@current-change="
(e) => {
;(handleBackTop(), goToPage(e))
}
"
@size-change="changePageSize"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<template v-else>
<div class="flex items-center justify-center h-full">
<el-empty description="暂无数据" />
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue'
import { useInterviewStore } from '@/stores/interview'
import { storeToRefs } from 'pinia'
import { getInterviewList } from '@/api'
import { usePageSearch, useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
import { useRouter } from 'vue-router'
import dayjs from 'dayjs'
const router = useRouter()
const tabsRef = inject(TABS_REF_KEY)
const { handleBackTop, ScrollTopComp } = useScrollTop(tabsRef!)
const interviewStore = useInterviewStore()
const { interviewList } = storeToRefs(interviewStore)
const section2Items = ref([
{
id: 4,
title: '宿主,你的负面清单该更新啦!',
description: '宿主你好呀!正在文化一本分与平常心的知识,我们一起来学习吧的知识。',
image: 'https://via.placeholder.com/300x200/DDA0DD/FFFFFF?text=Image4',
tag: '推荐',
views: '300',
comments: '0',
likes: '24',
date: '2025-04-13 12:01',
},
{
id: 5,
title: '不打破惯性思维,永远无法创新',
description:
'当我们的思路被惯性思维束缚了,想要得到一些不一样的结果就会变得很困难。但是,我们可以通过一些方法来打破惯性思维。',
image: 'https://via.placeholder.com/300x200/F0E68C/FFFFFF?text=Image5',
tag: '推荐',
views: '280',
comments: '0',
likes: '24',
date: '2025-04-13 12:10',
},
const { list, total, searchParams, goToPage, changePageSize, loading } = usePageSearch(
getInterviewList,
{
id: 6,
title: '文化共创|你提意见"我听你"',
description: '巧妙设手段第三名,出让各有所需满意的额外为为你们带来更多一些精彩。',
image: 'https://via.placeholder.com/300x200/FFA07A/FFFFFF?text=Image6',
tag: '推荐',
views: '280',
comments: '0',
likes: '24',
date: '2025-04-13 14:28',
defaultSize: 3,
},
])
)
</script>
<style scoped></style>
<template>
<div class="min-h-screen">
<div>
<!-- 头部Tabs -->
<div class="header h-40px items-center justify-between">
<div class="left flex gap-3 flex items-center">
......@@ -11,35 +11,9 @@
</div>
</div>
<el-divider style="margin: 10px 0 20px 0" />
<ColumnList v-if="activeTab === '专栏'" />
<InterviewList v-if="activeTab === '专访'" />
<PracticeList v-if="activeTab === '实践'" />
<!-- 底部分页 -->
<div class="bottom-pagination backdrop-blur-8 border-t border-gray-200">
<div class="max-w-7xl mx-auto px-8 py-6">
<div class="flex items-center justify-between">
<!-- 左侧:回到顶部按钮 -->
<div class="left">
<ScrollTopComp />
</div>
<!-- 右侧:分页器 -->
<div class="right">
<div
class="pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[15, 30, 45, 60]"
layout="prev, pager, next, jumper, total"
:total="400"
class="custom-pagination"
/>
</div>
</div>
</div>
</div>
</div>
<keep-alive>
<component :is="curretComponent" />
</keep-alive>
</div>
</template>
......@@ -50,14 +24,14 @@ import { Refresh } from '@element-plus/icons-vue'
import ColumnList from './components/columnList.vue'
import InterviewList from './components/interviewList.vue'
import PracticeList from './components/practiceList.vue'
import { useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
const tabsRef = inject(TABS_REF_KEY)
const { ScrollTopComp } = useScrollTop(tabsRef!)
const currentPage = ref(1)
const pageSize = ref(10)
const curretComponent = computed(() => {
return {
专栏: ColumnList,
专访: InterviewList,
实践: PracticeList,
}[activeTab.value]
})
const activeTab = ref('专栏')
const tabs = [
......@@ -66,12 +40,5 @@ const tabs = [
{ label: '专访', value: '专访' },
{ label: '视频', value: '视频' },
]
const handleBackTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
</script>
<style lang="scss" scoped></style>
......@@ -12,7 +12,7 @@
>
<div
class="flex flex-col items-center justify-center w-56px h-56px transition-all duration-200 group"
@click="handleClick(item)"
@click="handleClick(item as StatItem)"
>
<el-icon
class="group-hover:text-blue-500! cursor-pointer"
......@@ -39,11 +39,10 @@ import { Star, ChatLineSquare, Pointer } from '@element-plus/icons-vue'
import type { ArticleItemDto } from '@/api'
import type { Component } from 'vue'
import { useScrollTop } from '@/hooks'
import { addOrCancelCollect } from '@/api'
import { addOrCanceArticlelCollect, addOrCanceArticlelLike } from '@/api'
import { COMMENT_REF_KEY } from '@/constants'
const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto
}>()
const modelValue = defineModel<ArticleItemDto>('modelValue', { required: true })
const commentRef = inject(COMMENT_REF_KEY)
......@@ -55,38 +54,52 @@ interface StatItem {
count: number
label: string
active?: boolean
actionFn?: () => Promise<boolean>
actionFn?: () => Promise<void>
}
const stats = computed(() => {
return [
{
icon: Pointer,
count: articleDetail?.praiseCount,
count: modelValue.value?.praiseCount ?? 0,
label: '点赞',
active: articleDetail?.viewCount,
active: modelValue.value?.hasPraised,
api: addOrCanceArticlelLike,
async actionFn() {
await addOrCanceArticlelLike(modelValue.value.id)
// 前端直接本地修改
const isAdd = !modelValue.value.hasPraised
modelValue.value.hasPraised = isAdd
modelValue.value.praiseCount = isAdd
? modelValue.value.praiseCount + 1
: modelValue.value.praiseCount - 1
ElMessage.success(isAdd ? '点赞成功' : '取消点赞成功')
},
},
{
icon: Star,
count: articleDetail?.collectionCount,
count: modelValue.value?.collectionCount ?? 0,
label: '收藏',
active: articleDetail?.isRecommend,
api: addOrCancelCollect,
async actionFn(item: StatItem) {
const res = await addOrCancelCollect({
articleId: articleDetail.id,
type: articleDetail.type,
})
if (res) {
item.active = !item.active
}
active: modelValue.value?.hasCollect,
api: addOrCanceArticlelCollect,
async actionFn() {
await addOrCanceArticlelCollect(modelValue.value.id)
// 前端直接本地修改
const isAdd = !modelValue.value.hasCollect
modelValue.value.hasCollect = isAdd
modelValue.value.collectionCount = isAdd
? modelValue.value.collectionCount + 1
: modelValue.value.collectionCount - 1
ElMessage.success(isAdd ? '收藏成功' : '取消收藏成功')
},
},
{
icon: ChatLineSquare,
count: articleDetail?.replyCount,
count: modelValue.value?.replyCount ?? 0,
label: '评论',
active: articleDetail?.isRecommend,
// active: modelValue.value?.replyCount > 0,
actionFn: handleBackTop,
},
]
......
......@@ -85,10 +85,10 @@
</el-form-item>
</div>
<div class="mb-8">
<el-form-item prop="description">
<el-form-item prop="content">
<label class="block text-sm font-semibold text-gray-700 mb-3">视频简介</label>
<el-input
v-model="form.description"
v-model="form.content"
type="textarea"
:rows="5"
placeholder="请写上您希望介绍的内容,让更多的人了解您的作品吧!"
......@@ -201,7 +201,7 @@
<script setup lang="ts">
import UploadVideo from '@/components/common/UploadVideo/index.vue'
import { useResetData } from '@/hooks'
import { ArticleTypeEnum, SendTypeEnum } from '@/constants'
import { ArticleTypeEnum, ReleaseStatusTypeEnum, SendTypeEnum } from '@/constants'
import { addOrUpdateArticle } from '@/api'
import SelectTags from '@/components/common/SelectTags/index.vue'
......@@ -213,11 +213,12 @@ const [form] = useResetData({
videoUrl: '',
title: '视频标题',
type: ArticleTypeEnum.VIDEO,
description: '',
content: '',
mainTagId: '',
tagList: [],
sendType: SendTypeEnum.IMMEDIATE,
sendTime: '',
releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
})
const filterTagsFn = (tags: TagItemDto[]) => {
......@@ -227,7 +228,7 @@ const filterTagsFn = (tags: TagItemDto[]) => {
const rules = {
videoUrl: [{ required: true, message: '请上传视频', trigger: 'change' }],
title: [{ required: true, message: '请输入视频标题', trigger: 'blur' }],
description: [{ required: true, message: '请输入视频简介', trigger: 'blur' }],
content: [{ required: true, message: '请输入视频简介', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择标签', trigger: 'change' }],
}
......
......@@ -167,9 +167,12 @@ import { hasOfficialAccount } from '@/api'
const editUserInfoRef = useTemplateRef<InstanceType<typeof EditUserInfo>>('editUserInfoRef')
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
console.log(userInfo.value)
const route = useRoute()
const key = route.query.key as string
console.log(key)
// 当前激活的菜单
const activeMenu = ref('posts')
const activeMenu = ref(key || 'posts')
// 当前激活的标签页
const activeTab = ref('published')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment