Commit 4a88ffa9 by lijiabin

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

parent 21438b37
...@@ -3,11 +3,15 @@ import type { ...@@ -3,11 +3,15 @@ import type {
AddOrUpdateArticleDto, AddOrUpdateArticleDto,
ArticleItemDto, ArticleItemDto,
ArticleSearchParams, ArticleSearchParams,
InterviewOptionDto,
ColumnOptionDto,
AddCommentDto,
CommentItemDto,
CommentSearchParams,
InterviewItemDto, InterviewItemDto,
ColumnItemDto, ColumnItemDto,
} from './types' } from './types'
import type { BackendServicePageResult } from '@/utils/request/types' import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
import { ArticleTypeEnum } from '@/constants'
// 文章相关的接口(帖子 视频 实践等) // 文章相关的接口(帖子 视频 实践等)
...@@ -44,32 +48,100 @@ export const getArticleDetail = (articleId: number | string) => { ...@@ -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>({ 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', method: 'POST',
data, data,
}) })
} }
/** /**
* 获取专访列表--不分页 * 新增评论
*/ */
export const getInterviewList = () => { export const addComment = (data: AddCommentDto) => {
return service.request<InterviewItemDto[]>({ return service.request<boolean>({
url: '/api/cultureColumn/listNoPage?type=interview', url: `/api/cultureComment/addComment`,
method: 'POST', method: 'POST',
data,
}) })
} }
/** /**
* 获取专栏列表-- 不分页 * 点赞评论
*/ */
export const getColumnList = () => { export const addOrCancelCommentLike = (commentId: number | string) => {
return service.request<ColumnItemDto[]>({ return service.request<boolean>({
url: '/api/cultureColumn/listNoPage?type=column', url: `/api/cultureComment/likeComment?commentId=${commentId}`,
method: 'POST', method: 'POST',
}) })
} }
...@@ -6,6 +6,7 @@ import type { PageSearchParams } from '@/utils/request/types' ...@@ -6,6 +6,7 @@ import type { PageSearchParams } from '@/utils/request/types'
*/ */
export interface ArticleSearchParams extends PageSearchParams { export interface ArticleSearchParams extends PageSearchParams {
type?: ArticleTypeEnum type?: ArticleTypeEnum
sortLogic?: number
} }
/** /**
...@@ -41,7 +42,7 @@ interface AddOrUpdateColumnBase { ...@@ -41,7 +42,7 @@ interface AddOrUpdateColumnBase {
faceUrl?: string faceUrl?: string
imgUrl?: string imgUrl?: string
// 关联的专栏栏目 // 关联的专栏栏目
relateColumn: number relateColumnId: number
mainTagId: string mainTagId: string
isRelateColleague: BooleanFlag isRelateColleague: BooleanFlag
sendTime?: string sendTime?: string
...@@ -71,7 +72,7 @@ export interface AddOrUpdateInterviewBase { ...@@ -71,7 +72,7 @@ export interface AddOrUpdateInterviewBase {
faceUrl?: string faceUrl?: string
imgUrl?: string imgUrl?: string
// 关联的专访栏目 // 关联的专访栏目
relateColumn: number relateColumnId: number
mainTagId: string mainTagId: string
sendTime?: string sendTime?: string
} }
...@@ -95,7 +96,6 @@ export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase { ...@@ -95,7 +96,6 @@ export interface AddOrUpdateInterviewDto extends AddOrUpdateInterviewBase {
* 文章详情 * 文章详情
*/ */
// 更严格的类型定义
export interface ArticleItemDto { export interface ArticleItemDto {
id: number id: number
title: string title: string
...@@ -114,10 +114,17 @@ export interface ArticleItemDto { ...@@ -114,10 +114,17 @@ export interface ArticleItemDto {
praiseCount: number praiseCount: number
collectionCount: number collectionCount: number
replyCount: number replyCount: number
hasPraised: BooleanFlag hasPraised: boolean
hasCollect: boolean
imgUrl: string
createUserAvatar: string
createUserName: string
} }
export interface ColumnItemDto { /**
* 专栏选项
*/
export interface ColumnOptionDto {
color: string color: string
createTime: number createTime: number
createUserId: number createUserId: number
...@@ -129,7 +136,34 @@ export interface ColumnItemDto { ...@@ -129,7 +136,34 @@ export interface ColumnItemDto {
type: 'column' 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 color: string
createTime: number createTime: number
createUserId: number createUserId: number
...@@ -140,3 +174,65 @@ export interface InterviewItemDto { ...@@ -140,3 +174,65 @@ export interface InterviewItemDto {
title: string title: string
type: 'column' 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 service from '@/utils/request/index'
import type { ColumnItemDto } from './types' import type { ColumnOptionDto } from './types'
/** /**
* 获取专栏列表 * 获取专栏列表
*/ */
export const getColumnList = () => { export const getColumnOptions = () => {
return service.request<ColumnItemDto[]>({ return service.request<ColumnOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=column', url: '/api/cultureColumn/listNoPage?type=column',
method: 'POST', method: 'POST',
}) })
......
import { BooleanFlag } from '@/constants' import { BooleanFlag } from '@/constants'
export interface ColumnItemDto { export interface ColumnOptionDto {
color: string color: string
createTime: number createTime: number
createUserId: number createUserId: number
......
...@@ -4,13 +4,28 @@ import type { FielItemDto } from './types' ...@@ -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) => { export const uploadFile = (file: File, onProgress?: (progress: number) => void) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('fileList', file)
return service.request<FielItemDto>({ return axios.post('http://47.112.96.71:8082/mobiles/uploadFile', formData, {
url: '/mobiles/file-upload/singleUpload',
method: 'POST',
data: formData,
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1)) const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1))
onProgress?.(percentCompleted) onProgress?.(percentCompleted)
......
// 专栏列表 // 专栏列表
import service from '@/utils/request/index' import service from '@/utils/request/index'
import type { ColumnItemDto } from './types' import type { ColumnOptionDto } from './types'
/** /**
* 获取专栏列表 * 获取专栏列表
*/ */
export const getInterviewList = () => { export const getInterviewOptions = () => {
return service.request<ColumnItemDto[]>({ return service.request<ColumnOptionDto[]>({
url: '/api/cultureColumn/listNoPage?type=interview', url: '/api/cultureColumn/listNoPage?type=interview',
method: 'POST', method: 'POST',
}) })
......
import { BooleanFlag } from '@/constants' import { BooleanFlag } from '@/constants'
export interface InterviewItemDto { export interface InterviewOptionDto {
color: string color: string
createTime: number createTime: number
createUserId: number createUserId: number
......
...@@ -58,7 +58,7 @@ export interface AuditListItemDto { ...@@ -58,7 +58,7 @@ export interface AuditListItemDto {
isRelateColleague: boolean isRelateColleague: boolean
playCount: number playCount: number
praiseCount: number praiseCount: number
relateColumn: string relateColumnId: string
releaseStatus: AuditStatusEnum releaseStatus: AuditStatusEnum
replyCount: number replyCount: number
showAvatar: string 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) => ...@@ -125,7 +125,9 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 上传文件 // 上传文件
const { data } = await uploadFileApi(uploadFile.raw) 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) fileIndex = fileList.value.findIndex((file) => file.uid === uid)
...@@ -133,8 +135,8 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) => ...@@ -133,8 +135,8 @@ const handleChange: UploadProps['onChange'] = async (uploadFile, uploadFiles) =>
// 更新文件信息 // 更新文件信息
fileList.value[fileIndex] = { fileList.value[fileIndex] = {
...fileList.value[fileIndex], ...fileList.value[fileIndex],
url: data.fileUrl, url,
name: data.fileName, name,
status: 'success', status: 'success',
} }
ElMessage.success('上传成功') ElMessage.success('上传成功')
......
...@@ -2,7 +2,7 @@ import type { SetupContext, MaybeRef } from 'vue' ...@@ -2,7 +2,7 @@ import type { SetupContext, MaybeRef } from 'vue'
type Events = { type Events = {
handleBackTop(): void handleBackTop(): void
} }
function ScrollTopComp(_: Record<string, never>, { emit }: SetupContext<Events>) { function ScrollTopComp(_: any, { emit }: SetupContext<Events>) {
return ( return (
<button <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" 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/ ...@@ -12,6 +12,7 @@ import type { AddOrUpdateColumnForm, AddOrUpdateColumnDto } from '@/api/article/
export default defineComponent((_, { expose }) => { export default defineComponent((_, { expose }) => {
const columnStore = useColumnStore() const columnStore = useColumnStore()
const { columnList } = storeToRefs(columnStore) const { columnList } = storeToRefs(columnStore)
console.log(columnList.value)
const [form, resetForm] = useResetData<AddOrUpdateColumnForm>({ const [form, resetForm] = useResetData<AddOrUpdateColumnForm>({
title: '', title: '',
content: '', content: '',
...@@ -23,7 +24,7 @@ export default defineComponent((_, { expose }) => { ...@@ -23,7 +24,7 @@ export default defineComponent((_, { expose }) => {
sendType: SendTypeEnum.IMMEDIATE, sendType: SendTypeEnum.IMMEDIATE,
sendTime: '', sendTime: '',
isRelateColleague: BooleanFlag.NO, isRelateColleague: BooleanFlag.NO,
relateColumn: 0, relateColumnId: 0,
type: ArticleTypeEnum.COLUMN, type: ArticleTypeEnum.COLUMN,
}) })
const formRef = ref<InstanceType<typeof ElForm>>() const formRef = ref<InstanceType<typeof ElForm>>()
...@@ -34,7 +35,7 @@ export default defineComponent((_, { expose }) => { ...@@ -34,7 +35,7 @@ export default defineComponent((_, { expose }) => {
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }], mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
relateColumn: [ relateColumnId: [
{ required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 }, { required: true, message: '请选择专栏栏目', trigger: 'blur', type: 'number', min: 1 },
], ],
} }
...@@ -111,8 +112,8 @@ export default defineComponent((_, { expose }) => { ...@@ -111,8 +112,8 @@ export default defineComponent((_, { expose }) => {
{/* @ts-ignore */} {/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} /> <UploadFile v-model={form.value.imgUrl} />
</el-form-item> </el-form-item>
<el-form-item label="专栏栏目选择" prop="relateColumn"> <el-form-item label="专栏栏目选择" prop="relateColumnId">
<el-radio-group v-model={form.value.relateColumn}> <el-radio-group v-model={form.value.relateColumnId}>
{columnList.value.map((item) => ( {columnList.value.map((item) => (
<el-radio value={item.id}>{item.title}</el-radio> <el-radio value={item.id}>{item.title}</el-radio>
))} ))}
......
...@@ -22,7 +22,7 @@ export default defineComponent((_, { expose }) => { ...@@ -22,7 +22,7 @@ export default defineComponent((_, { expose }) => {
sendType: SendTypeEnum.IMMEDIATE, sendType: SendTypeEnum.IMMEDIATE,
sendTime: '', sendTime: '',
type: ArticleTypeEnum.INTERVIEW, type: ArticleTypeEnum.INTERVIEW,
relateColumn: 0, relateColumnId: 0,
}) })
const formRef = ref<InstanceType<typeof ElForm>>() const formRef = ref<InstanceType<typeof ElForm>>()
const rules = { const rules = {
...@@ -32,7 +32,7 @@ export default defineComponent((_, { expose }) => { ...@@ -32,7 +32,7 @@ export default defineComponent((_, { expose }) => {
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }], mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }],
relateColumn: [ relateColumnId: [
{ required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 }, { required: true, message: '请选择专访栏目', trigger: 'blur', type: 'number', min: 1 },
], ],
} }
...@@ -109,8 +109,8 @@ export default defineComponent((_, { expose }) => { ...@@ -109,8 +109,8 @@ export default defineComponent((_, { expose }) => {
{/* @ts-ignore */} {/* @ts-ignore */}
<UploadFile v-model={form.value.imgUrl} /> <UploadFile v-model={form.value.imgUrl} />
</el-form-item> </el-form-item>
<el-form-item label="专访栏目选择" prop="relateColumn"> <el-form-item label="专访栏目选择" prop="relateColumnId">
<el-radio-group v-model={form.value.relateColumn}> <el-radio-group v-model={form.value.relateColumnId}>
{columnList.value.map((item) => ( {columnList.value.map((item) => (
<el-radio value={item.id}>{item.title}</el-radio> <el-radio value={item.id}>{item.title}</el-radio>
))} ))}
......
...@@ -27,7 +27,7 @@ export default defineComponent( ...@@ -27,7 +27,7 @@ export default defineComponent(
return { return {
...form.value, ...form.value,
releaseStatus, releaseStatus,
imgUrl: form.value.imgUrl?.split(',').filter(Boolean)[0] || '', faceUrl: form.value.imgUrl?.split(',').filter(Boolean)[0] || '',
} }
} }
......
...@@ -34,7 +34,7 @@ const routes = [ ...@@ -34,7 +34,7 @@ const routes = [
], ],
}, },
{ {
path: 'videoDetail/:', path: 'videoDetail/:id',
name: 'CultureVideoDetail', name: 'CultureVideoDetail',
component: () => import('@/views/videoDetail/index.vue'), component: () => import('@/views/videoDetail/index.vue'),
}, },
......
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getColumnList } from '@/api' import { getColumnOptions } from '@/api'
import type { ColumnItemDto } from '@/api/article/types' import type { ColumnOptionDto } from '@/api/article/types'
/** /**
* 关于专栏的不分页数据 * 关于专栏的不分页数据
*/ */
export const useColumnStore = defineStore('column', () => { export const useColumnStore = defineStore('column', () => {
const columnList = ref<ColumnItemDto[]>([]) const columnList = ref<ColumnOptionDto[]>([])
let isLoading = false let isLoading = false
...@@ -14,7 +14,7 @@ export const useColumnStore = defineStore('column', () => { ...@@ -14,7 +14,7 @@ export const useColumnStore = defineStore('column', () => {
if (isLoading) return if (isLoading) return
isLoading = true isLoading = true
try { try {
const { data } = await getColumnList() const { data } = await getColumnOptions()
columnList.value = data columnList.value = data
console.log(columnList.value, 'columnList') console.log(columnList.value, 'columnList')
} catch (error) { } catch (error) {
......
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { getInterviewList } from '@/api' import { getInterviewOptions } from '@/api'
import type { InterviewItemDto } from '@/api/article/types' import type { InterviewOptionDto } from '@/api/article/types'
/** /**
* 关于专访的相关数据 --不分页 * 关于专访的相关数据 --不分页
*/ */
export const useInterviewStore = defineStore('interview', () => { export const useInterviewStore = defineStore('interview', () => {
const interviewList = ref<InterviewItemDto[]>([]) const interviewList = ref<InterviewOptionDto[]>([])
let isLoading = false let isLoading = false
...@@ -14,7 +14,7 @@ export const useInterviewStore = defineStore('interview', () => { ...@@ -14,7 +14,7 @@ export const useInterviewStore = defineStore('interview', () => {
if (interviewList.value.length > 0) return if (interviewList.value.length > 0) return
isLoading = true isLoading = true
try { try {
const { data } = await getInterviewList() const { data } = await getInterviewOptions()
interviewList.value = data interviewList.value = data
console.log(interviewList.value, 'interviewList') console.log(interviewList.value, 'interviewList')
} catch (error) { } catch (error) {
......
<template> <template>
<div ref="listRef"> <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 class="space-y-3 sm:space-y-4">
<div <div
v-for="item in list" v-for="item in list"
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
> >
<div class="flex gap-3 justify-between"> <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 <h2
class="text-xl font-semibold text-gray-900 line-clamp-1 group-hover:text-blue-600 transition-colors duration-200 leading-tight" 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 @@ ...@@ -20,7 +20,7 @@
<!-- 内容摘要 --> <!-- 内容摘要 -->
<div class="my-2 space-y-1"> <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 }} {{ item.content }}
</p> </p>
</div> </div>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
</div> </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 <img
:src="item.faceUrl" :src="item.faceUrl"
alt="文章配图" alt="文章配图"
...@@ -116,16 +116,16 @@ ...@@ -116,16 +116,16 @@
<script setup lang="ts" name="RecommendList"> <script setup lang="ts" name="RecommendList">
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { getArticleList } from '@/api' import { getArticleList } from '@/api'
import { ArticleTypeEnum, TABS_REF_KEY } from '@/constants' import { TABS_REF_KEY } from '@/constants'
import { useScrollTop } from '@/hooks' import { useScrollTop } from '@/hooks'
import dayjs from 'dayjs' import dayjs from 'dayjs'
const router = useRouter() const router = useRouter()
const { list, total, searchParams, loading, goToPage, changePageSize, reset } = usePageSearch( const { list, total, searchParams, loading, goToPage, changePageSize, refresh } = usePageSearch(
getArticleList, getArticleList,
{ {
// defaultParams: { type: ArticleTypeEnum.POST }, defaultParams: { sortLogic: 0 },
defaultCurrent: 1, defaultCurrent: 1,
defaultSize: 5, defaultSize: 5,
}, },
...@@ -135,6 +135,10 @@ const tabsRef = inject(TABS_REF_KEY) ...@@ -135,6 +135,10 @@ const tabsRef = inject(TABS_REF_KEY)
const { ScrollTopComp, handleBackTop } = useScrollTop(tabsRef!) const { ScrollTopComp, handleBackTop } = useScrollTop(tabsRef!)
defineExpose({ defineExpose({
refresh: reset, refresh: (sortLogic: number) => {
console.log('sortLogic', sortLogic)
searchParams.value.sortLogic = sortLogic
refresh()
},
}) })
</script> </script>
...@@ -44,12 +44,20 @@ const activeTabComponentRef = ...@@ -44,12 +44,20 @@ const activeTabComponentRef =
useTemplateRef<InstanceType<typeof RecommendList>>('activeTabComponentRef') useTemplateRef<InstanceType<typeof RecommendList>>('activeTabComponentRef')
const handleRefresh = () => { 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) => { const handleTabChange = (tab: string) => {
if (tab === '最新') { if (tab === '最新') {
activeTabComponentRef.value?.refresh?.() activeTabComponentRef.value?.refresh?.(1)
} else if (tab === '推荐') {
activeTabComponentRef.value?.refresh?.(0)
} }
} }
</script> </script>
......
...@@ -10,22 +10,25 @@ ...@@ -10,22 +10,25 @@
<div class="flex gap-3"> <div class="flex gap-3">
<div class="left flex-1 basis-full xl:basis-3/4 transition-all duration-500"> <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 <div
v-for="tab in tabs" v-for="tab in tabs"
:key="tab.path" :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)" @click="toggleTab(tab)"
> >
<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 <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="flex items-center gap-2 px-12 py-2.5 rounded-xl transition-all duration-300"
:class="{ 'scale-x-50': activeTab === tab.name }" :class="{
></div> '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>
</div> </div>
</div> </div>
...@@ -162,20 +165,27 @@ ...@@ -162,20 +165,27 @@
<!-- 任务中心 --> <!-- 任务中心 -->
<div class="task-container common-box rounded-lg bg-#FFF0E5"> <div class="task-container common-box rounded-lg bg-#FFF0E5">
<div class="flex items-center justify-between mb-2"> <div class="flex items-start justify-start mb-2">
<div> <div class="w-full">
<div class="flex items-center gap-2"> <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> <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> <h1 class="text-sm sm:text-base font-bold">任务中心</h1>
</div> </div>
<h2 class="text-xs sm:text-sm mb-1 text-gray-600 ml-3"> <h2
<span class="w-full text-xs sm:text-sm mt-1 text-gray-600 flex items-center justify-between"
v-for="item in taskTypeList" >
:key="item.value" <div>
class="text-#333 cursor-pointer after:content-['|'] after:mx-2 after:text-#999 last:after:content-none" <span
:class="{ 'text-#999': currentTask !== item.value }" v-for="item in taskTypeList"
@click="currentTask = item.value" :key="item.value"
>{{ item.label }} 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>
</div>
<span class="text-#999 cursor-pointer" @click="router.push(`/userPage?key=task`)">
查看更多
</span> </span>
</h2> </h2>
</div> </div>
...@@ -210,13 +220,23 @@ ...@@ -210,13 +220,23 @@
</div> </div>
</div> </div>
</div> </div>
<el-button <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" 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"
type="primary" :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)" @click="handleTask(item)"
> >
<span class="text-black text-xs sm:text-sm">去完成</span> <span
</el-button> class="text-black text-sm"
:style="{
color: item.currentCount === item.limitCount ? '#999' : '#000',
}"
>{{ item.currentCount === item.limitCount ? '已完成' : '去完成' }}</span
>
</button>
</div> </div>
<!-- 分割线 --> <!-- 分割线 -->
<el-divider style="margin: 0" /> <el-divider style="margin: 0" />
...@@ -332,6 +352,8 @@ const onDailySign = async () => { ...@@ -332,6 +352,8 @@ const onDailySign = async () => {
} }
const handleTask = (item: TaskItemDto) => { const handleTask = (item: TaskItemDto) => {
if (item.currentCount === item.limitCount) return
// if (item.svgName === 'svgName') { // if (item.svgName === 'svgName') {
handleBackTop() handleBackTop()
triggerAnimation() triggerAnimation()
......
<template> <template>
<div class="w-full max-w-6xl mx-auto"> <div>
<div <div v-loading="loading" v-if="list.length">
v-for="item in columnList" <div class="w-full max-w-6xl mx-auto">
:key="item.id" <div
class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden" v-for="(item, index) in list"
:style="{ '--dynamic-color': item.color }" :key="index"
> class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
<div :style="{ '--dynamic-color': item.color }"
class="flex items-center justify-between pr-4 pl-4 pt-2 pb-2 bg-green-50 border-b border-green-100" >
:style="{ backgroundColor: item.color, '--dynamic-color': item.color }" <div
> class="flex items-center justify-between pr-4 pl-4 pt-2 pb-2 bg-green-50 border-b border-green-100"
<h3 class="text-lg font-medium text-gray-800 flex items-center"> :style="{ backgroundColor: item.color, '--dynamic-color': item.color }"
<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
> >
<el-icon><ArrowRight /></el-icon> <h3 class="text-lg font-medium text-gray-800 flex items-center">
</div> <span class="w-1 h-5 mr-2 bg-#444"></span>
</div> {{ item.title }}
</h3>
<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="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div v-if="item.yaColumnVoList.length" 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
<div class="relative mb-3 overflow-hidden rounded-lg"> v-for="i in item.yaColumnVoList"
<img :key="i.articleId"
src="@/assets/img/culture/ask.png" class="group cursor-pointer"
:alt="item.title" @click="router.push(`/postDetail/${i.articleId}`)"
class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300" >
/> <div class="relative mb-3 overflow-hidden rounded-lg">
<div class="absolute top-2 left-2"> <img
<el-tag size="small" class="bg-orange-500 text-white border-none"> :src="i.faceUrl"
{{ item.tag }} class="w-full aspect-[5/3] object-cover group-hover:scale-105 transition-transform duration-300"
</el-tag> />
<div class="absolute top-2 left-2">
<el-tag size="small" class="bg-orange-500 text-white border-none">
1111111
</el-tag>
</div>
</div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors">
{{ i.title }}
</h3>
<p class="text-xs text-gray-500 mb-3 line-clamp-2">
{{ 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>
{{ i.viewCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><ChatDotRound /></el-icon>
{{ i.replyCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><Star /></el-icon>
{{ i.collectCount }}
</span>
</div>
<span>{{ dayjs(i.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
</div> </div>
</div> </div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors"> <div v-else class="flex items-center justify-center h-48">
{{ item.title }} <el-empty description="暂无数据" />
</h3> </div>
<p class="text-xs text-gray-500 mb-3 line-clamp-2"> </div>
{{ item.description }} </div>
</p> </div>
<div class="flex items-center justify-between text-xs text-gray-400"> <div class="bottom-pagination backdrop-blur-8 border-t border-gray-200">
<div class="flex items-center space-x-4"> <div class="max-w-7xl mx-auto py-6">
<span class="flex items-center"> <div class="flex items-center justify-between">
<el-icon class="mr-1"><View /></el-icon> <!-- 左侧:回到顶部按钮 -->
{{ item.views }} <div class="left">
</span> <ScrollTopComp />
<span class="flex items-center"> </div>
<el-icon class="mr-1"><ChatDotRound /></el-icon> <!-- 右侧:分页器 -->
{{ item.comments }} <div class="right">
</span> <div
<span class="flex items-center"> class="pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
<el-icon class="mr-1"><Star /></el-icon> >
{{ item.likes }} <el-pagination
</span> 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>
<span>{{ item.date }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<template v-else>
<div class="flex items-center justify-center h-full">
<el-empty description="暂无数据" />
</div>
</template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue' import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue'
import { useColumnStore } from '@/stores/column' import { getColumnList } from '@/api'
import { storeToRefs } from 'pinia' import { usePageSearch, useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
const columnStore = useColumnStore() import dayjs from 'dayjs'
const { columnList } = storeToRefs(columnStore) import { useRouter } from 'vue-router'
const router = useRouter()
const tabsRef = inject(TABS_REF_KEY)
const { handleBackTop, ScrollTopComp } = useScrollTop(tabsRef!)
const section2Items = ref([ const { list, total, searchParams, goToPage, changePageSize, loading } = usePageSearch(
{ getColumnList,
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',
},
{ {
id: 6, defaultSize: 3,
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',
}, },
]) )
</script> </script>
<style scoped></style> <style scoped></style>
<template> <template>
<div class="w-full max-w-6xl mx-auto"> <div>
<div <div v-loading="loading" v-if="list.length">
v-for="item in interviewList" <div class="w-full max-w-6xl mx-auto">
:key="item.id" <div
class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden" v-for="(item, index) in list"
:style="{ '--dynamic-color': item.color }" :key="index"
> class="bg-white rounded-lg shadow-sm mb-6 overflow-hidden"
<div :style="{ '--dynamic-color': item.color }"
class="flex items-center justify-between pr-4 pl-4 pt-2 pb-2 bg-green-50 border-b border-green-100" >
:style="{ backgroundColor: item.color, '--dynamic-color': item.color }" <div
> class="flex items-center justify-between pr-4 pl-4 pt-2 pb-2 bg-green-50 border-b border-green-100"
<h3 class="text-lg font-medium text-gray-800 flex items-center"> :style="{ backgroundColor: item.color, '--dynamic-color': item.color }"
<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
> >
<el-icon><ArrowRight /></el-icon> <h3 class="text-lg font-medium text-gray-800 flex items-center">
</div> <span class="w-1 h-5 mr-2 bg-#444"></span>
</div> {{ 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
>
<el-icon><ArrowRight /></el-icon>
</div>
</div>
<div class="p-4"> <div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div v-if="item.yaColumnVoList.length" 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
<div class="relative mb-3 overflow-hidden rounded-lg"> v-for="i in item.yaColumnVoList"
<img :key="i.articleId"
src="@/assets/img/culture/ask.png" class="group cursor-pointer"
:alt="item.title" @click="router.push(`/postDetail/${i.articleId}`)"
class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300" >
/> <div class="relative mb-3 overflow-hidden rounded-lg">
<div class="absolute top-2 left-2"> <img
<el-tag size="small" class="bg-orange-500 text-white border-none"> :src="i.faceUrl"
{{ item.tag }} class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300"
</el-tag> />
<div class="absolute top-2 left-2">
<el-tag size="small" class="bg-orange-500 text-white border-none">
1111111
</el-tag>
</div>
</div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors">
{{ i.title }}
</h3>
<p class="text-xs text-gray-500 mb-3 line-clamp-2">
{{ 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>
{{ i.viewCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><ChatDotRound /></el-icon>
{{ i.replyCount }}
</span>
<span class="flex items-center">
<el-icon class="mr-1"><Star /></el-icon>
{{ i.collectCount }}
</span>
</div>
<span>{{ dayjs(i.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}</span>
</div>
</div> </div>
</div> </div>
<h3 class="text-sm font-medium text-gray-800 mb-2 transition-colors"> <div v-else class="flex items-center justify-center h-48">
{{ item.title }} <el-empty description="暂无数据" />
</h3> </div>
<p class="text-xs text-gray-500 mb-3 line-clamp-2"> </div>
{{ item.description }} </div>
</p> </div>
<div class="flex items-center justify-between text-xs text-gray-400"> <div class="bottom-pagination backdrop-blur-8 border-t border-gray-200">
<div class="flex items-center space-x-4"> <div class="max-w-7xl mx-auto py-6">
<span class="flex items-center"> <div class="flex items-center justify-between">
<el-icon class="mr-1"><View /></el-icon> <!-- 左侧:回到顶部按钮 -->
{{ item.views }} <div class="left">
</span> <ScrollTopComp />
<span class="flex items-center"> </div>
<el-icon class="mr-1"><ChatDotRound /></el-icon> <!-- 右侧:分页器 -->
{{ item.comments }} <div class="right">
</span> <div
<span class="flex items-center"> class="pagination-wrapper bg-white rounded-xl shadow-sm border border-gray-100 p-3"
<el-icon class="mr-1"><Star /></el-icon> >
{{ item.likes }} <el-pagination
</span> 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>
<span>{{ item.date }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<template v-else>
<div class="flex items-center justify-center h-full">
<el-empty description="暂无数据" />
</div>
</template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue' import { ArrowRight, View, ChatDotRound, Star } from '@element-plus/icons-vue'
import { useInterviewStore } from '@/stores/interview' import { getInterviewList } from '@/api'
import { storeToRefs } from 'pinia' import { usePageSearch, useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
const interviewStore = useInterviewStore() import { useRouter } from 'vue-router'
const { interviewList } = storeToRefs(interviewStore) import dayjs from 'dayjs'
const router = useRouter()
const tabsRef = inject(TABS_REF_KEY)
const { handleBackTop, ScrollTopComp } = useScrollTop(tabsRef!)
const section2Items = ref([ const { list, total, searchParams, goToPage, changePageSize, loading } = usePageSearch(
{ getInterviewList,
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',
},
{ {
id: 6, defaultSize: 3,
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',
}, },
]) )
</script> </script>
<style scoped></style> <style scoped></style>
<template> <template>
<div class="min-h-screen"> <div>
<!-- 头部Tabs --> <!-- 头部Tabs -->
<div class="header h-40px items-center justify-between"> <div class="header h-40px items-center justify-between">
<div class="left flex gap-3 flex items-center"> <div class="left flex gap-3 flex items-center">
...@@ -11,35 +11,9 @@ ...@@ -11,35 +11,9 @@
</div> </div>
</div> </div>
<el-divider style="margin: 10px 0 20px 0" /> <el-divider style="margin: 10px 0 20px 0" />
<ColumnList v-if="activeTab === '专栏'" /> <keep-alive>
<InterviewList v-if="activeTab === '专访'" /> <component :is="curretComponent" />
<PracticeList v-if="activeTab === '实践'" /> </keep-alive>
<!-- 底部分页 -->
<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>
</div> </div>
</template> </template>
...@@ -50,14 +24,14 @@ import { Refresh } from '@element-plus/icons-vue' ...@@ -50,14 +24,14 @@ import { Refresh } from '@element-plus/icons-vue'
import ColumnList from './components/columnList.vue' import ColumnList from './components/columnList.vue'
import InterviewList from './components/interviewList.vue' import InterviewList from './components/interviewList.vue'
import PracticeList from './components/practiceList.vue' import PracticeList from './components/practiceList.vue'
import { useScrollTop } from '@/hooks'
import { TABS_REF_KEY } from '@/constants'
const tabsRef = inject(TABS_REF_KEY) const curretComponent = computed(() => {
return {
const { ScrollTopComp } = useScrollTop(tabsRef!) 专栏: ColumnList,
const currentPage = ref(1) 专访: InterviewList,
const pageSize = ref(10) 实践: PracticeList,
}[activeTab.value]
})
const activeTab = ref('专栏') const activeTab = ref('专栏')
const tabs = [ const tabs = [
...@@ -66,12 +40,5 @@ const tabs = [ ...@@ -66,12 +40,5 @@ const tabs = [
{ label: '专访', value: '专访' }, { label: '专访', value: '专访' },
{ label: '视频', value: '视频' }, { label: '视频', value: '视频' },
] ]
const handleBackTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
> >
<div <div
class="flex flex-col items-center justify-center w-56px h-56px transition-all duration-200 group" 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 <el-icon
class="group-hover:text-blue-500! cursor-pointer" class="group-hover:text-blue-500! cursor-pointer"
...@@ -39,11 +39,10 @@ import { Star, ChatLineSquare, Pointer } from '@element-plus/icons-vue' ...@@ -39,11 +39,10 @@ import { Star, ChatLineSquare, Pointer } from '@element-plus/icons-vue'
import type { ArticleItemDto } from '@/api' import type { ArticleItemDto } from '@/api'
import type { Component } from 'vue' import type { Component } from 'vue'
import { useScrollTop } from '@/hooks' import { useScrollTop } from '@/hooks'
import { addOrCancelCollect } from '@/api' import { addOrCanceArticlelCollect, addOrCanceArticlelLike } from '@/api'
import { COMMENT_REF_KEY } from '@/constants' import { COMMENT_REF_KEY } from '@/constants'
const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto const modelValue = defineModel<ArticleItemDto>('modelValue', { required: true })
}>()
const commentRef = inject(COMMENT_REF_KEY) const commentRef = inject(COMMENT_REF_KEY)
...@@ -55,38 +54,52 @@ interface StatItem { ...@@ -55,38 +54,52 @@ interface StatItem {
count: number count: number
label: string label: string
active?: boolean active?: boolean
actionFn?: () => Promise<boolean> actionFn?: () => Promise<void>
} }
const stats = computed(() => { const stats = computed(() => {
return [ return [
{ {
icon: Pointer, icon: Pointer,
count: articleDetail?.praiseCount, count: modelValue.value?.praiseCount ?? 0,
label: '点赞', 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, icon: Star,
count: articleDetail?.collectionCount, count: modelValue.value?.collectionCount ?? 0,
label: '收藏', label: '收藏',
active: articleDetail?.isRecommend, active: modelValue.value?.hasCollect,
api: addOrCancelCollect, api: addOrCanceArticlelCollect,
async actionFn(item: StatItem) { async actionFn() {
const res = await addOrCancelCollect({ await addOrCanceArticlelCollect(modelValue.value.id)
articleId: articleDetail.id, // 前端直接本地修改
type: articleDetail.type, const isAdd = !modelValue.value.hasCollect
}) modelValue.value.hasCollect = isAdd
if (res) { modelValue.value.collectionCount = isAdd
item.active = !item.active ? modelValue.value.collectionCount + 1
} : modelValue.value.collectionCount - 1
ElMessage.success(isAdd ? '收藏成功' : '取消收藏成功')
}, },
}, },
{ {
icon: ChatLineSquare, icon: ChatLineSquare,
count: articleDetail?.replyCount, count: modelValue.value?.replyCount ?? 0,
label: '评论', label: '评论',
active: articleDetail?.isRecommend, // active: modelValue.value?.replyCount > 0,
actionFn: handleBackTop, actionFn: handleBackTop,
}, },
] ]
......
...@@ -85,10 +85,10 @@ ...@@ -85,10 +85,10 @@
</el-form-item> </el-form-item>
</div> </div>
<div class="mb-8"> <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> <label class="block text-sm font-semibold text-gray-700 mb-3">视频简介</label>
<el-input <el-input
v-model="form.description" v-model="form.content"
type="textarea" type="textarea"
:rows="5" :rows="5"
placeholder="请写上您希望介绍的内容,让更多的人了解您的作品吧!" placeholder="请写上您希望介绍的内容,让更多的人了解您的作品吧!"
...@@ -201,7 +201,7 @@ ...@@ -201,7 +201,7 @@
<script setup lang="ts"> <script setup lang="ts">
import UploadVideo from '@/components/common/UploadVideo/index.vue' import UploadVideo from '@/components/common/UploadVideo/index.vue'
import { useResetData } from '@/hooks' import { useResetData } from '@/hooks'
import { ArticleTypeEnum, SendTypeEnum } from '@/constants' import { ArticleTypeEnum, ReleaseStatusTypeEnum, SendTypeEnum } from '@/constants'
import { addOrUpdateArticle } from '@/api' import { addOrUpdateArticle } from '@/api'
import SelectTags from '@/components/common/SelectTags/index.vue' import SelectTags from '@/components/common/SelectTags/index.vue'
...@@ -213,11 +213,12 @@ const [form] = useResetData({ ...@@ -213,11 +213,12 @@ const [form] = useResetData({
videoUrl: '', videoUrl: '',
title: '视频标题', title: '视频标题',
type: ArticleTypeEnum.VIDEO, type: ArticleTypeEnum.VIDEO,
description: '', content: '',
mainTagId: '', mainTagId: '',
tagList: [], tagList: [],
sendType: SendTypeEnum.IMMEDIATE, sendType: SendTypeEnum.IMMEDIATE,
sendTime: '', sendTime: '',
releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
}) })
const filterTagsFn = (tags: TagItemDto[]) => { const filterTagsFn = (tags: TagItemDto[]) => {
...@@ -227,7 +228,7 @@ const filterTagsFn = (tags: TagItemDto[]) => { ...@@ -227,7 +228,7 @@ const filterTagsFn = (tags: TagItemDto[]) => {
const rules = { const rules = {
videoUrl: [{ required: true, message: '请上传视频', trigger: 'change' }], videoUrl: [{ required: true, message: '请上传视频', trigger: 'change' }],
title: [{ required: true, message: '请输入视频标题', trigger: 'blur' }], title: [{ required: true, message: '请输入视频标题', trigger: 'blur' }],
description: [{ required: true, message: '请输入视频简介', trigger: 'blur' }], content: [{ required: true, message: '请输入视频简介', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择标签', trigger: 'change' }], mainTagId: [{ required: true, message: '请选择标签', trigger: 'change' }],
} }
......
...@@ -167,9 +167,12 @@ import { hasOfficialAccount } from '@/api' ...@@ -167,9 +167,12 @@ import { hasOfficialAccount } from '@/api'
const editUserInfoRef = useTemplateRef<InstanceType<typeof EditUserInfo>>('editUserInfoRef') const editUserInfoRef = useTemplateRef<InstanceType<typeof EditUserInfo>>('editUserInfoRef')
const userStore = useUserStore() const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) 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') 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