Commit daeb355b by lijiabin

【需求 17679】 feat: 继续完善优化

parent dedb7b4a
...@@ -239,3 +239,46 @@ export const getCommentChildren = (data: CommentChildrenSearchParams) => { ...@@ -239,3 +239,46 @@ export const getCommentChildren = (data: CommentChildrenSearchParams) => {
data, data,
}) })
} }
/**
* 个人中心——问吧回答问题列表
*/
export const answerQuestionPage = (data: PageSearchParams) => {
return service.request<BackendServicePageResult<ArticleItemDto>>({
url: `/api/personalCenter/addQuestionPage`,
method: 'POST',
data,
})
}
/**
* 问吧列表 添加问题到回答问题列表
*/
export const addOrCancelToAnswerList = (data: { articleId: number }) => {
return service.request<boolean>({
url: `/api/cultureQuestionAdd/addOrCancel`,
method: 'POST',
data,
})
}
/**
* 问吧 用户添加问题的数量
*/
export const getUserQestionNum = () => {
return service.request<number>({
url: `/api/cultureQuestionAdd/addQuestionNum`,
method: 'POST',
})
}
/**
* 举报帖子
*/
export const addComplaint = (data: { articleId: number; reason: string }) => {
return service.request<boolean>({
url: `/cultureReport/addReport`,
method: 'POST',
data,
})
}
...@@ -148,6 +148,7 @@ export interface ArticleItemDto { ...@@ -148,6 +148,7 @@ export interface ArticleItemDto {
relateColumn?: string relateColumn?: string
rewardNum: number rewardNum: number
isExpand: boolean isExpand: boolean
hasAddQuestion: boolean
} }
/** /**
......
...@@ -17,6 +17,9 @@ import type { ...@@ -17,6 +17,9 @@ import type {
SelfDraftSearchParams, SelfDraftSearchParams,
SelfTaskSearchParams, SelfTaskSearchParams,
SelfTaskItemDto, SelfTaskItemDto,
ComplaintListItemDto,
AuditComplaintDto,
ComplaintListSearchParams,
} from './types' } from './types'
import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types' import type { BackendServicePageResult, PageSearchParams } from '@/utils/request/types'
...@@ -140,3 +143,25 @@ export const auditArticle = (data: AuditArticleDto) => { ...@@ -140,3 +143,25 @@ export const auditArticle = (data: AuditArticleDto) => {
data, data,
}) })
} }
/**
* 举报列表
*/
export const getComplaintList = (data: ComplaintListSearchParams) => {
return service.request<BackendServicePageResult<ComplaintListItemDto>>({
url: '/cultureReport/getReportList',
method: 'POST',
data,
})
}
/**
* 审核举报
*/
export const auditComplaint = (data: AuditComplaintDto) => {
return service.request({
url: '/cultureReport/auditReport',
method: 'POST',
data,
})
}
...@@ -222,6 +222,7 @@ export interface SelfCaseItemDto { ...@@ -222,6 +222,7 @@ export interface SelfCaseItemDto {
*/ */
export interface SelfCommentSearchParams extends PageSearchParams { export interface SelfCommentSearchParams extends PageSearchParams {
messageType: CommentTypeEnum messageType: CommentTypeEnum
type: ArticleTypeEnum
} }
/** /**
...@@ -278,3 +279,40 @@ export interface SelfTaskItemDto { ...@@ -278,3 +279,40 @@ export interface SelfTaskItemDto {
title: string title: string
totalTasks: number totalTasks: number
} }
/**
* 举报列表item
*/
export interface ComplaintListItemDto {
articleId: number
auditId: number
auditName: string
auditTime: number
createId: number
createName: string
createTime: number
id: number
isDelete: number
reason: string
showAvatar: string
showName: string
status: number
title: string
type: string
}
/**
* 举报列表搜索参数
*/
export interface ComplaintListSearchParams extends PageSearchParams {
status: AuditStatusEnum
}
/**
* 审核举报
*/
export interface AuditComplaintDto {
id: number
status: AuditStatusEnum
remark?: string
}
<template>
<el-dropdown @command="handleMore" trigger="click">
<!-- <el-button class="p-2 rounded-md cursor-pointer border-none!"> -->
<el-icon class="cursor-pointer"><More /></el-icon>
<!-- </el-button> -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="举报">举报</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { addComplaint } from '@/api'
import type { ArticleItemDto } from '@/api/article/types'
// defineOptions({
// inheritAttrs: false,
// })
const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto
}>()
const handleMore = async (command: string) => {
if (command === '举报') {
const { value } = await ElMessageBox.prompt('请输入举报原因', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^\S+$/,
inputPlaceholder: '请输入举报原因',
inputErrorMessage: '举报原因不能为空',
})
addComplaint({ articleId: articleDetail.id, reason: value })
ElMessage.success('举报成功')
}
}
</script>
...@@ -31,10 +31,24 @@ ...@@ -31,10 +31,24 @@
· {{ articleDetail?.viewCount || 0 }} 阅读 · {{ articleDetail?.viewCount || 0 }} 阅读
</p> </p>
</div> </div>
<el-button v-if="articleDetail.relateColumn" type="primary" link>{{
articleDetail.relateColumn <!-- 优化后的右侧内容 -->
}}</el-button> <div class="flex items-center gap-3">
<el-button type="primary" link>{{ articleType }}</el-button> <span
class="px-3 py-1.5 text-sm font-medium bg-gray-100 text-gray-700 rounded-md"
v-if="articleDetail.relateColumn"
>
{{ articleDetail.relateColumn }}
</span>
<span
class="px-3 py-1.5 text-sm font-medium bg-blue-50 text-blue-600 rounded-md hover:bg-blue-100 transition-colors"
>
{{ articleType }}
</span>
<ActionMore :articleDetail="articleDetail" />
</div>
</div> </div>
</div> </div>
...@@ -86,8 +100,9 @@ ...@@ -86,8 +100,9 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { type ArticleItemDto } from '@/api' import type { ArticleItemDto } from '@/api'
import { articleTypeListOptions, ArticleTypeEnum } from '@/constants' import { articleTypeListOptions, ArticleTypeEnum } from '@/constants'
import ActionMore from '@/components/common/ActionMore/index.vue'
const { articleDetail } = defineProps<{ const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto articleDetail: ArticleItemDto
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
</el-upload> </el-upload>
<el-dialog v-model="dialogVisible"> <el-dialog :append-to-body="true" v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" /> <img class="w-full" :src="dialogImageUrl" alt="Preview Image" />
</el-dialog> </el-dialog>
</template> </template>
......
...@@ -24,9 +24,9 @@ export const app_config: { [key: string]: IConfig } = { ...@@ -24,9 +24,9 @@ export const app_config: { [key: string]: IConfig } = {
// 开发环境 // 开发环境
development: { development: {
baseUrl: 'http://culture.yswg.com.cn:8089', // 线上测试机 // baseUrl: 'http://culture.yswg.com.cn:8089', // 线上测试机
// baseUrl: 'http://192.168.2.168:8089', // 立鹏本地/ // baseUrl: 'http://192.168.2.168:8089', // 立鹏本地/
// baseUrl: 'http://192.168.2.55:8089', // 首拥本地 baseUrl: 'http://192.168.2.55:8089', // 首拥本地
loginType: 1, loginType: 1,
wxRedirect: '', wxRedirect: '',
}, },
......
import type { InjectionKey, Ref } from 'vue' import type { InjectionKey, Ref } from 'vue'
export const TABS_REF_KEY = Symbol() as InjectionKey<Ref<HTMLElement | null>> export const TABS_REF_KEY = Symbol('tabsRef') as InjectionKey<Ref<HTMLElement | null>>
export const COMMENT_REF_KEY = Symbol() as InjectionKey<Ref<HTMLElement | null>> export const COMMENT_REF_KEY = Symbol('commentRef') as InjectionKey<Ref<HTMLElement | null>>
...@@ -77,12 +77,12 @@ ...@@ -77,12 +77,12 @@
<el-dropdown-item :command="ArticleTypeEnum.VIDEO">视频</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.VIDEO">视频</el-dropdown-item>
<el-dropdown-item :command="ArticleTypeEnum.QUESTION">问吧</el-dropdown-item> <el-dropdown-item :command="ArticleTypeEnum.QUESTION">问吧</el-dropdown-item>
<el-dropdown-item <el-dropdown-item
v-if="userInfo.isOfficialAccount" v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.COLUMN" :command="ArticleTypeEnum.COLUMN"
>专栏</el-dropdown-item >专栏</el-dropdown-item
> >
<el-dropdown-item <el-dropdown-item
v-if="userInfo.isOfficialAccount" v-if="userInfo.isOfficialAccount || userInfo.isAdmin"
:command="ArticleTypeEnum.INTERVIEW" :command="ArticleTypeEnum.INTERVIEW"
>专访</el-dropdown-item >专访</el-dropdown-item
> >
...@@ -133,6 +133,7 @@ const showSearchInupt = computed(() => route.path !== '/searchPage') ...@@ -133,6 +133,7 @@ const showSearchInupt = computed(() => route.path !== '/searchPage')
// 获取二级路由的 key // 获取二级路由的 key
const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
// 取第一级作为key homePage // 取第一级作为key homePage
const pathSegments = route.path.split('/').filter(Boolean) const pathSegments = route.path.split('/').filter(Boolean)
// console.log(route) // console.log(route)
// console.log(pathSegments) // console.log(pathSegments)
...@@ -140,6 +141,7 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { ...@@ -140,6 +141,7 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
const key = Object.keys(route.params).length const key = Object.keys(route.params).length
? pathSegments.slice(0, 2).join('/') ? pathSegments.slice(0, 2).join('/')
: pathSegments.slice(0, 1).join('/') : pathSegments.slice(0, 1).join('/')
console.log(key, '*********************')
return key return key
} }
......
...@@ -35,33 +35,98 @@ const routes = [ ...@@ -35,33 +35,98 @@ const routes = [
}, },
], ],
}, },
// 个人中心
{
path: 'userPage',
name: 'CultureUserPage',
redirect: '/userPage/selfPublish',
component: () => import('@/views/userPage/index.vue'),
children: [
// 我的帖子
{
path: 'selfPublish',
name: 'CultureSelfPublish',
component: () => import('@/views/userPage/components/selfPublish.vue'),
},
// 我的草稿
{
path: 'selfDraft',
name: 'CultureSelfDraft',
component: () => import('@/views/userPage/components/selfDraft.vue'),
},
// 我的收藏
{
path: 'selfCollect',
name: 'CultureSelfCollect',
component: () => import('@/views/userPage/components/selfCollect.vue'),
},
// 我的点赞
{
path: 'selfPraise',
name: 'CultureSelfPraise',
component: () => import('@/views/userPage/components/selfPraise.vue'),
},
// 我的案例库
{
path: 'selfCase',
name: 'CultureSelfCase',
component: () => import('@/views/userPage/components/selfCase.vue'),
},
// 我的任务
{
path: 'selfTask',
name: 'CultureSelfTask',
component: () => import('@/views/userPage/components/selfTask.vue'),
},
// 评论回复
{
path: 'selfComment',
name: 'CultureSelfComment',
component: () => import('@/views/userPage/components/selfComment.vue'),
},
// 回答问题
{
path: 'selfAnswer',
name: 'CultureSelfAnswer',
component: () => import('@/views/userPage/components/selfAnswer.vue'),
},
// 审核帖子列表
{
path: 'selfAudit',
name: 'CultureSelfAudit',
component: () => import('@/views/userPage/components/selfAudit.vue'),
},
// 审核举报列表
{
path: 'selfComplaint',
name: 'CultureSelfComplaint',
component: () => import('@/views/userPage/components/selfComplaint.vue'),
},
],
},
{ {
path: 'videoDetail/:id', path: 'videoDetail/:id',
name: 'CultureVideoDetail', name: 'CultureVideoDetail',
component: () => import('@/views/videoDetail/index.vue'), component: () => import('@/views/videoDetail/index.vue'),
}, },
{ {
path: 'articleDetail/:id',
name: 'CultureArticleDetail',
component: () => import('@/views/articleDetail/index.vue'),
},
{
path: 'pointsStore', path: 'pointsStore',
name: 'CulturePointsStore', name: 'CulturePointsStore',
component: () => import('@/views/pointsStore/index.vue'), component: () => import('@/views/pointsStore/index.vue'),
}, },
{
path: 'articleDetail/:articleId',
name: 'CultureArticleDetail',
component: () => import('@/views/articleDetail/index.vue'),
},
// 发布视频 // 发布视频
{ {
path: 'publishVideo', path: 'publishVideo',
name: 'CulturePublishVideo', name: 'CulturePublishVideo',
component: () => import('@/views/publishVideo/index.vue'), component: () => import('@/views/publishVideo/index.vue'),
}, },
// 个人中心
{
path: 'userPage',
name: 'CultureUserPage',
component: () => import('@/views/userPage/index.vue'),
},
// 去投稿 // 去投稿
{ {
path: 'publishCase', path: 'publishCase',
......
...@@ -38,25 +38,20 @@ export function clearScrollPosition(path?: string): void { ...@@ -38,25 +38,20 @@ export function clearScrollPosition(path?: string): void {
export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => { export const scrollBehavior: RouterScrollBehavior = (to, from, savedPosition) => {
return new Promise((resolve) => { return new Promise((resolve) => {
console.log('触发路由滚动') console.log('触发路由滚动')
// setTimeout(() => {
// console.log(document.querySelector('#tabsRef'))
// }, 1000)
// 1. 如果有浏览器保存的位置(前进/后退),优先使用 // 1. 如果有浏览器保存的位置(前进/后退),优先使用
if (savedPosition) { if (savedPosition) {
resolve(savedPosition) resolve(savedPosition)
return return
} }
// 2. 如果有锚点,滚动到锚点 // 2. 如果有锚点, 约定 默认不滚动 然后在具体的组件里面onActivated里面 或者watch处理滚动逻辑 以及漫游逻辑等
if (to.hash) { if (to.hash) {
console.log(to.hash) // console.log(to.hash, window.scrollY)
setTimeout(() => { // resolve({
resolve({ // top: window.scrollY,
el: to.hash.split('?')[0], //去除?后面的查询字符串 // })
behavior: 'smooth', // 平滑滚动
top: 52,
})
}, 800)
return return
} }
......
...@@ -3,3 +3,4 @@ export * from './tags' ...@@ -3,3 +3,4 @@ export * from './tags'
export * from './column' export * from './column'
export * from './interview' export * from './interview'
export * from './video' export * from './video'
export * from './question'
import { defineStore } from 'pinia'
import { getUserQestionNum } from '@/api'
/**
* 问吧数据
*/
export const useQuestionStore = defineStore('question', () => {
const userQestionNum = ref<number>(0)
const fetchUserQestionNum = async () => {
try {
const { data } = await getUserQestionNum()
userQestionNum.value = data
} catch (error) {
console.error(error)
}
}
return { userQestionNum, fetchUserQestionNum }
})
...@@ -23,7 +23,7 @@ import ArticleContent from '@/components/common/ArticleContent/index.vue' ...@@ -23,7 +23,7 @@ import ArticleContent from '@/components/common/ArticleContent/index.vue'
const commentRef = useTemplateRef<typeof Comment | null>('commentRef') const commentRef = useTemplateRef<typeof Comment | null>('commentRef')
const route = useRoute() const route = useRoute()
const id = route.params.articleId as string const id = route.params.id as string
const articleDetail = ref({} as ArticleItemDto) const articleDetail = ref({} as ArticleItemDto)
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column prop="image" label="跳转路径" min-width="300"> <el-table-column prop="image" label="资源" min-width="300">
<template #default="{ row }"> <template #default="{ row }">
<div class="image-cell"> <div class="image-cell">
<el-image <el-image
...@@ -23,11 +23,13 @@ ...@@ -23,11 +23,13 @@
fit="cover" fit="cover"
class="carousel-image" class="carousel-image"
:preview-src-list="[row.assetUrl]" :preview-src-list="[row.assetUrl]"
:preview-teleported="true"
/> />
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="url" label="跳转链接" width="100" align="center" />
<el-table-column prop="sort" label="排序" width="100" align="center" /> <el-table-column prop="sort" label="排序" width="100" align="center" />
<el-table-column prop="type" label="类型" width="120" align="center"> <el-table-column prop="type" label="类型" width="120" align="center">
...@@ -81,7 +83,7 @@ ...@@ -81,7 +83,7 @@
<el-form-item label="显示资源" prop="image"> <el-form-item label="显示资源" prop="image">
<div class="upload-section"> <div class="upload-section">
<UploadFile v-model="form.assetUrl" :limit="1" /> <UploadFile v-model="form.assetUrl" :limit="1" />
<div class="upload-hint">上传图片,推荐比例为 W/H: 1280 / 480</div> <div class="upload-hint">上传图片,推荐比例为 W/H: 4 / 1</div>
</div> </div>
</el-form-item> </el-form-item>
......
...@@ -149,13 +149,12 @@ ...@@ -149,13 +149,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Search, Plus, Upload } from '@element-plus/icons-vue' import { Search, Upload } from '@element-plus/icons-vue'
import { usePageSearch, useResetData } from '@/hooks' import { usePageSearch, useResetData } from '@/hooks'
import { import {
addOrUpdateColumn, addOrUpdateColumn,
deleteColumn, deleteColumn,
hideColumn, hideColumn,
getExchangeList,
getBackendExchangeList, getBackendExchangeList,
issueProduct, issueProduct,
} from '@/api/backend' } from '@/api/backend'
...@@ -164,7 +163,6 @@ import type { FormInstance, FormRules } from 'element-plus' ...@@ -164,7 +163,6 @@ import type { FormInstance, FormRules } from 'element-plus'
import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend' import type { BackendColumnListItemDto, AddOrUpdateColumnDto } from '@/api/backend'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ArticleTypeEnum } from '@/constants' import { ArticleTypeEnum } from '@/constants'
import { sortByOriginalOrder } from 'element-plus/es/components/cascader-panel/src/utils.mjs'
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getBackendExchangeList, { usePageSearch(getBackendExchangeList, {
......
...@@ -12,14 +12,14 @@ ...@@ -12,14 +12,14 @@
</div> </div>
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<div class="mx-auto py-6"> <div class="mx-auto pt-6">
<PublishBox :type="ArticleTypeEnum.QUESTION" ref="publishBoxRef" /> <PublishBox :type="ArticleTypeEnum.QUESTION" ref="publishBoxRef" />
<div v-loading="loading" v-if="list.length"> <div v-loading="loading" v-if="list.length">
<!-- 问题列表 --> <!-- 问题列表 -->
<div class="space-y-4"> <div class="space-y-4">
<el-card <el-card
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="item.id"
class="question-card !rounded-lg mb-4 transition-all duration-300" class="question-card !rounded-lg mb-4 transition-all duration-300"
shadow="hover" shadow="hover"
> >
...@@ -81,25 +81,37 @@ ...@@ -81,25 +81,37 @@
<!-- 操作按钮组 --> <!-- 操作按钮组 -->
<div class="flex items-center"> <div class="flex items-center">
<el-button size="small" plain> <el-button
<el-icon><Plus /></el-icon> size="small"
添加 plain
:class="{ 'opacity-50': item.hasAddQuestion }"
@click="handleAddQuestion(item)"
>
<el-icon>
<component :is="item.hasAddQuestion ? 'CircleCheckFilled' : 'Plus'" />
</el-icon>
{{ item.hasAddQuestion ? '已添加' : '添加' }}
</el-button>
<el-button
size="small"
plain
:class="{ 'opacity-50': item.hasCollect }"
@click="handleCollect(item)"
>
<el-icon>
<component :is="item.hasCollect ? 'StarFilled' : 'Star'" />
</el-icon>
{{ item.hasCollect ? '已关注' : '关注' }}
</el-button> </el-button>
<!-- 回答按钮保持不变 -->
<el-button size="small" plain @click="handleComment(index)"> <el-button size="small" plain @click="handleComment(index)">
<el-icon><Edit /></el-icon> <el-icon><Edit /></el-icon>
回答 回答
</el-button> </el-button>
<el-button size="small" plain> <ActionMore class="ml-4" :articleDetail="item" />
<el-icon><Star /></el-icon>
关注
</el-button>
<el-button size="small" type="danger" plain>
<el-icon><Warning /></el-icon>
举报
</el-button>
</div> </div>
</div> </div>
...@@ -189,7 +201,7 @@ ...@@ -189,7 +201,7 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="CultureAsk"> <script setup lang="ts">
import Tabs from '@/components/common/Tabs' import Tabs from '@/components/common/Tabs'
import { Star, View, ChatDotRound, Refresh } from '@element-plus/icons-vue' import { Star, View, ChatDotRound, Refresh } from '@element-plus/icons-vue'
import Comment from '@/components/common/Comment/index.vue' import Comment from '@/components/common/Comment/index.vue'
...@@ -198,8 +210,12 @@ import { TABS_REF_KEY } from '@/constants' ...@@ -198,8 +210,12 @@ import { TABS_REF_KEY } from '@/constants'
import PublishBox from '@/components/common/PublishBox/index.vue' import PublishBox from '@/components/common/PublishBox/index.vue'
import { ArticleTypeEnum } from '@/constants' import { ArticleTypeEnum } from '@/constants'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { getArticleList, addOrCanceArticlelCollect } from '@/api' import { getArticleList, addOrCanceArticlelCollect, addOrCancelToAnswerList } from '@/api'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { useQuestionStore } from '@/stores/question'
import ActionMore from '@/components/common/ActionMore/index.vue'
const { fetchUserQestionNum } = useQuestionStore()
const route = useRoute() const route = useRoute()
const open = ref(false) const open = ref(false)
...@@ -213,6 +229,7 @@ const tabs = [ ...@@ -213,6 +229,7 @@ const tabs = [
] ]
const { list, total, searchParams, loading, refresh } = usePageSearch(getArticleList, { const { list, total, searchParams, loading, refresh } = usePageSearch(getArticleList, {
immediate: false,
defaultParams: { defaultParams: {
type: ArticleTypeEnum.QUESTION, type: ArticleTypeEnum.QUESTION,
}, },
...@@ -226,7 +243,7 @@ const { list, total, searchParams, loading, refresh } = usePageSearch(getArticle ...@@ -226,7 +243,7 @@ const { list, total, searchParams, loading, refresh } = usePageSearch(getArticle
const tabsRef = inject(TABS_REF_KEY) const tabsRef = inject(TABS_REF_KEY)
const { ScrollTopComp } = useScrollTop(tabsRef!) const { ScrollTopComp, handleBackTop } = useScrollTop(tabsRef!)
const handleCollect = async (item: ArticleItemDto) => { const handleCollect = async (item: ArticleItemDto) => {
await addOrCanceArticlelCollect(item.id) await addOrCanceArticlelCollect(item.id)
...@@ -243,6 +260,13 @@ const handleCommentSuccess = (index: number) => { ...@@ -243,6 +260,13 @@ const handleCommentSuccess = (index: number) => {
list.value[index]!.replyCount++ list.value[index]!.replyCount++
} }
const handleAddQuestion = async (item: ArticleItemDto) => {
await addOrCancelToAnswerList({ articleId: item.id })
item.hasAddQuestion = !item.hasAddQuestion
fetchUserQestionNum()
ElMessage.success(item.hasAddQuestion ? '添加成功' : '取消添加')
}
const handleRefresh = () => { const handleRefresh = () => {
refresh() refresh()
} }
...@@ -267,17 +291,28 @@ const isOverThreeLine = (index: number) => { ...@@ -267,17 +291,28 @@ const isOverThreeLine = (index: number) => {
const handleExpand = (item: ArticleItemDto) => { const handleExpand = (item: ArticleItemDto) => {
item.isExpand = !item.isExpand item.isExpand = !item.isExpand
} }
// 监听路由变化 如果包含#tabsRef 则打开漫游 setInterval(() => {
console.log(contentRefList.value)
}, 3000)
// 是否打开漫游
watch( watch(
() => route.fullPath, () => route.fullPath,
(newVal) => { async (newVal) => {
if (newVal.includes('#tabsRef')) { // 处理在当前页面跳转到当前页面的情况 onActivated 不会触发
setTimeout(() => { if (newVal.includes('#tabsRef') && newVal.includes('?t')) {
await handleBackTop()
open.value = true open.value = true
}, 1500)
} }
}, },
) )
onActivated(async () => {
if (route.fullPath.includes('#tabsRef')) {
await handleBackTop()
open.value = true
}
refresh()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.fade-enter-from, .fade-enter-from,
......
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="RecommendList"> <script setup lang="ts">
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { getArticleList, type ArticleItemDto } from '@/api' import { getArticleList, type ArticleItemDto } from '@/api'
import { TABS_REF_KEY, ArticleTypeEnum } from '@/constants' import { TABS_REF_KEY, ArticleTypeEnum } from '@/constants'
......
...@@ -418,7 +418,7 @@ ...@@ -418,7 +418,7 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="RecommendList"> <script setup lang="ts" name="VideoList">
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useScrollTop } from '@/hooks' import { useScrollTop } from '@/hooks'
import { ArticleTypeEnum, TABS_REF_KEY } from '@/constants' import { ArticleTypeEnum, TABS_REF_KEY } from '@/constants'
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="CultureHome"> <script setup lang="ts">
import RecommendList from './components/recommendList.vue' import RecommendList from './components/recommendList.vue'
import VideoList from './components/videoList.vue' import VideoList from './components/videoList.vue'
import Tabs from '@/components/common/Tabs' import Tabs from '@/components/common/Tabs'
......
<template> <template>
<div class="main-container"> <div class="main-container">
<div class="banner mb-3 w-full aspect-[16/6] overflow-hidden"> <!-- aspect-[16/6] -->
<div class="banner mb-3 w-full aspect-[4/1] overflow-hidden">
<el-carousel class="h-full w-full shadow-lg rounded-lg"> <el-carousel class="h-full w-full shadow-lg rounded-lg">
<el-carousel-item v-for="(item, index) in carouselList" :key="index" class="h-full w-full"> <el-carousel-item v-for="(item, index) in carouselList" :key="index" class="h-full w-full">
<el-image :src="item.assetUrl" class="w-full h-full object-cover" /> <el-image :src="item.assetUrl" class="w-full h-full object-cover" />
...@@ -55,6 +56,7 @@ ...@@ -55,6 +56,7 @@
<!-- 等级等相关信息 --> <!-- 等级等相关信息 -->
<div <div
ref="levelContainerRef" ref="levelContainerRef"
id="levelContainerRef"
class="level-container common-box flex flex-col justify-center items-center gap-4 rounded-lg bg-#E4F5FE" class="level-container common-box flex flex-col justify-center items-center gap-4 rounded-lg bg-#E4F5FE"
> >
<div class="top flex items-center justify-center gap-3"> <div class="top flex items-center justify-center gap-3">
...@@ -138,18 +140,22 @@ ...@@ -138,18 +140,22 @@
<div class="grid grid-cols-3 gap-2 sm:gap-4 mb-4"> <div class="grid grid-cols-3 gap-2 sm:gap-4 mb-4">
<div <div
class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10" class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10"
@click="router.push('/homePage/askTab')" @click="publishTopic"
> >
<svg-icon name="topic_release" size="80" /> <svg-icon name="topic_release" size="80" />
<div class="text-xs sm:text-sm">话题发布</div> <div class="text-xs sm:text-sm">话题发布</div>
</div> </div>
<div <div
class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10" class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10"
@click="router.push('/homePage/askTab')" @click="router.push('/userPage/selfAnswer')"
> >
<el-badge :value="userQestionNum" :offset="[-5, 20]" :hidden="!userQestionNum">
<svg-icon name="answer" size="80" /> <svg-icon name="answer" size="80" />
</el-badge>
<div class="text-xs sm:text-sm">回答问题</div> <div class="text-xs sm:text-sm">回答问题</div>
</div> </div>
<div <div
@click="router.push('/publishVideo')" @click="router.push('/publishVideo')"
class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10" class="flex flex-col items-center justify-center text-center cursor-pointer hover:-translate-y-1 transition-transform duration-200 p-2 rounded-lg hover:bg-white/10"
...@@ -160,7 +166,7 @@ ...@@ -160,7 +166,7 @@
</div> </div>
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
<el-button <el-button
@click="router.push('/homePage/askTab')" @click="router.push(`/userPage/selfComment?type=${ArticleTypeEnum.QUESTION}`)"
class="bg-[linear-gradient(to_right,#D6C9FF_0%,#C5B1FF_100%)] shadow-[0_1px_4px_0_rgba(95,0,237,0.25)] border-none hover:-translate-y-1 transition-all duration-200 text-xs sm:text-sm w-116px" class="bg-[linear-gradient(to_right,#D6C9FF_0%,#C5B1FF_100%)] shadow-[0_1px_4px_0_rgba(95,0,237,0.25)] border-none hover:-translate-y-1 transition-all duration-200 text-xs sm:text-sm w-116px"
type="primary" type="primary"
> >
...@@ -275,20 +281,26 @@ import ya from '@/assets/img/culture/ya_culture.png' ...@@ -275,20 +281,26 @@ import ya from '@/assets/img/culture/ya_culture.png'
import ask from '@/assets/img/culture/ask.png' import ask from '@/assets/img/culture/ask.png'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router' import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import { getTaskList, dailySign, getCarouselList, getUserAccountData, getRecordData } from '@/api' import { getTaskList, dailySign, getCarouselList, getUserAccountData, getRecordData } from '@/api'
import { TaskTypeEnum, TaskDateLimitTypeText } from '@/constants' import { TaskTypeEnum, TaskDateLimitTypeText, ArticleTypeEnum } from '@/constants'
import type { CarouselItemDto, TaskItemDto, UserAccountDataDto, UserRecordDataDto } from '@/api' import type { CarouselItemDto, TaskItemDto, UserAccountDataDto, UserRecordDataDto } from '@/api'
import { TABS_REF_KEY, levelListOptions } from '@/constants' import { TABS_REF_KEY, levelListOptions } from '@/constants'
import { useScrollTop } from '@/hooks' import { useScrollTop } from '@/hooks'
import { useQuestionStore } from '@/stores/question'
import { storeToRefs } from 'pinia'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const open = ref(false) const open = ref(false)
const levelContainerRef = useTemplateRef<HTMLElement>('levelContainerRef') const levelContainerRef = useTemplateRef<HTMLElement>('levelContainerRef')
const dailySignBtnRef = useTemplateRef<HTMLElement>('dailySignBtnRef') const dailySignBtnRef = useTemplateRef<HTMLElement>('dailySignBtnRef')
const { handleBackTop } = useScrollTop(levelContainerRef) const { handleBackTop } = useScrollTop(levelContainerRef)
const questionStore = useQuestionStore()
const { userQestionNum } = storeToRefs(questionStore)
const getThirdLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { const getThirdLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
console.log(route.fullPath, '三级路由') // console.log(route.fullPath, '三级路由首页')
// console.log(route.path, 11111111111111) // console.log(route.path, 11111111111111)
// return route.fullPath // fullpath带有query参数 // return route.fullPath // fullpath带有query参数
return route.path return route.path
...@@ -385,15 +397,19 @@ const handleTask = async (item: TaskItemDto) => { ...@@ -385,15 +397,19 @@ const handleTask = async (item: TaskItemDto) => {
//每日签到 //每日签到
await handleBackTop() await handleBackTop()
open.value = true open.value = true
// triggerAnimation()
} else if (item.svgName === 'valid_comments') { } else if (item.svgName === 'valid_comments') {
// 发布评论 // 发布评论
ElMessage.info('快去文章评论区去发表评论吧~') ElMessage.info('快去文章评论区去发表评论吧~')
} else if (item.svgName === 'topic_publish') { } else if (item.svgName === 'topic_publish') {
router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`) // 加一个时间戳 是因为对于不同的时间戳 用的keepalive是 path 都是同一个组件 在组件里面监听watch(()=>to.fullPath) 每次都能监听得到 if (route.path.includes('/homePage/askTab')) {
// 同一个页面 需要加事件触发 watch
router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`)
} else {
router.push(`/homePage/askTab#tabsRef`)
}
} else if (item.svgName === 'answer_ask') { } else if (item.svgName === 'answer_ask') {
// 回答问题 // 回答问题
router.push('/homePage/askTab') router.push('/userPage/selfAnswer')
} else if (item.svgName === 'video_publish') { } else if (item.svgName === 'video_publish') {
// 视频发布 // 视频发布
router.push('/publishVideo') router.push('/publishVideo')
...@@ -403,6 +419,15 @@ const handleTask = async (item: TaskItemDto) => { ...@@ -403,6 +419,15 @@ const handleTask = async (item: TaskItemDto) => {
} }
} }
const publishTopic = () => {
if (route.path.includes('/homePage/askTab')) {
// 同一个页面 需要加事件触发 watch
router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`)
} else {
router.push(`/homePage/askTab#tabsRef`)
}
}
const initPage = () => { const initPage = () => {
Promise.allSettled([getCarouselList(), getUserAccountData(), getRecordData()]).then( Promise.allSettled([getCarouselList(), getUserAccountData(), getRecordData()]).then(
([r1, r2, r3]) => { ([r1, r2, r3]) => {
...@@ -429,9 +454,14 @@ const refreshTaskData = async (refreshRecordData = false) => { ...@@ -429,9 +454,14 @@ const refreshTaskData = async (refreshRecordData = false) => {
specialTaskList.value = data.filter((item) => item.taskType === TaskTypeEnum.SPECIAL_TASK) specialTaskList.value = data.filter((item) => item.taskType === TaskTypeEnum.SPECIAL_TASK)
} }
// 刷新任务进度 // 刷新任务进度 以及是否打开漫游
onActivated(() => { onActivated(async () => {
questionStore.fetchUserQestionNum()
refreshTaskData(false) refreshTaskData(false)
if (route.fullPath.includes('#levelContainerRef')) {
await handleBackTop()
open.value = true
}
}) })
onMounted(() => { onMounted(() => {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</div> </div>
</template> </template>
<script setup lang="ts" name="CultureAsk"> <script setup lang="ts">
import Tabs from '@/components/common/Tabs' import Tabs from '@/components/common/Tabs'
import { Refresh } from '@element-plus/icons-vue' import { Refresh } from '@element-plus/icons-vue'
import ColumnList from './components/columnList.vue' import ColumnList from './components/columnList.vue'
......
<template>
<div class="flex-1 flex flex-col" v-loading="loading">
<!-- List Container -->
<div class="flex-1 p-4 pt-1">
<div class="relative">
<el-tabs v-model="searchParams.status" @tab-change="toggleTab">
<el-tab-pane
v-for="tab in auditTypeListOptions"
:key="tab.value"
:label="tab.label"
:name="tab.value"
/>
</el-tabs>
<div class="absolute right-0 top-2.5 z-1000">
<el-icon
size="15"
class="cursor-pointer hover:rotate-180 transition-all duration-300"
@click="refresh"
><Refresh
/></el-icon>
</div>
</div>
<div v-if="!list.length" class="flex flex-col items-center justify-center h-64">
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<el-icon class="text-2xl text-gray-300"><Document /></el-icon>
</div>
<div class="text-gray-500 text-lg mb-2">暂无内容</div>
</div>
<div v-else class="space-y-4">
<div
v-for="item in list"
:key="item.id"
class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
>
<!-- Content -->
<div class="flex-1 min-w-0">
<!-- 头像 + 内容区 -->
<div class="flex gap-3">
<el-image :src="item.showAvatar" class="w-12 h-12 rounded-full flex-shrink-0" />
<div class="flex-1 min-w-0 flex flex-col justify-center">
<!-- 第一行:用户名 + 时间 + 标签 -->
<div class="flex items-center gap-2 mb-1.5">
<span class="text-gray-900 text-sm flex-shrink-0">{{ item.createName }}</span>
<span
class="text-gray-400 text-sm flex-shrink-0 whitespace-nowrap flex items-center"
>
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
<span class="flex items-center px-2"> 举报帖子 </span>
<el-link type="primary" :underline="false" @click="handleToDetail(item)">
{{ item.title }}
</el-link>
</span>
<!-- <div class="flex flex-wrap gap-1.5 flex-1 min-w-0">
<el-tag size="small">
{{ articleTypeListOptions.find((i) => i.value === item.type)?.label }}
</el-tag>
</div> -->
</div>
<div class="text-gray-700 font-medium">举报理由:{{ item.reason }}</div>
</div>
</div>
</div>
<div
v-if="searchParams.status === AuditStatusEnum.UNAUDITED"
class="flex items-center text-gray-400 text-sm ml-4"
>
<el-button
type="primary"
link
@click="handleAudit({ id: item.id, status: AuditStatusEnum.AGREED })"
>
同意
</el-button>
<el-button
type="danger"
link
@click="
handleAudit({
id: item.id,
status: AuditStatusEnum.REJECTED,
})
"
>
拒绝
</el-button>
</div>
</div>
</div>
</div>
<div
v-if="list.length"
class="flex items-center justify-end px-6 py-4 border-t border-gray-200"
>
<div class="pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3">
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
@size-change="changePageSize"
@current-change="goToPage"
:page-sizes="[10, 20, 30, 40]"
layout="prev, pager, next, jumper, total"
:total="total"
class="custom-pagination"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { Document } from '@element-plus/icons-vue'
import { getComplaintList, auditComplaint } from '@/api'
import { usePageSearch } from '@/hooks'
import { auditTypeListOptions } from '@/constants/options'
import dayjs from 'dayjs'
import { AuditStatusEnum, ArticleTypeEnum } from '@/constants'
import type { AuditComplaintDto, ComplaintListItemDto } from '@/api'
import type { TabPaneName } from 'element-plus'
const router = useRouter()
const toggleTab = (key: TabPaneName) => {
searchParams.value.status = key as AuditStatusEnum
refresh()
}
// State
const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch(
getComplaintList,
{
defaultParams: {
status: AuditStatusEnum.UNAUDITED,
},
},
)
const handleAudit = async (data: AuditComplaintDto) => {
if (data.status === AuditStatusEnum.REJECTED) {
const { value } = await ElMessageBox.prompt('请输入拒绝理由', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^\S+$/,
inputErrorMessage: '请输入拒绝理由',
})
data.remark = value
}
await auditComplaint(data)
ElMessage.success('审核成功')
refresh()
}
const handleToDetail = (item: ComplaintListItemDto) => {
if (item.type === ArticleTypeEnum.VIDEO) {
router.push(`/videoDetail/${item.articleId}`)
} else {
router.push(`/articleDetail/${item.articleId}`)
}
}
</script>
<template>
<div class="flex-1 flex flex-col" v-loading="loading">
<div class="flex-1 p-4 pt-1">
<div class="flex items-center justify-end">
<div>
<el-icon
size="15"
class="cursor-pointer hover:rotate-180 transition-all duration-300"
@click="refresh"
><Refresh
/></el-icon>
</div>
</div>
<div v-if="!list.length" class="flex flex-col items-center justify-center h-64">
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
<el-icon class="text-2xl text-gray-300"><Document /></el-icon>
</div>
<div class="text-gray-500 text-lg mb-2">暂无内容</div>
</div>
<div v-else class="space-y-4">
<div
v-for="item in list"
:key="item.id"
class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
>
<div class="flex-1 min-w-0">
<div class="text-gray-900 font-medium truncate">{{ item.title }}</div>
<div class="text-gray-500 text-sm mt-1 truncate">
<span class="mr-2">
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</span>
<span class="mr-2">评论 {{ item.collectionCount }}</span>
</div>
</div>
<div class="flex items-center text-gray-400 text-sm ml-4">
<el-button type="primary" link @click="router.push(`/articleDetail/${item.id}`)">
去回复
</el-button>
</div>
</div>
</div>
</div>
<div
v-if="list.length"
class="flex items-center justify-end px-6 py-4 border-t border-gray-200"
>
<div class="pagination-wrapper bg-white rounded-lg shadow-sm border border-gray-100 p-3">
<el-pagination
v-model:current-page="searchParams.current"
v-model:page-size="searchParams.size"
@size-change="changePageSize"
@current-change="goToPage"
:page-sizes="[10, 20, 30, 40]"
layout="prev, pager, next, jumper, total"
:total="total"
class="custom-pagination"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { Document, Refresh } from '@element-plus/icons-vue'
import { answerQuestionPage } from '@/api'
import { usePageSearch } from '@/hooks'
import dayjs from 'dayjs'
const router = useRouter()
const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch(
answerQuestionPage,
{
immediate: false,
},
)
onActivated(() => {
refresh()
})
</script>
...@@ -46,12 +46,9 @@ ...@@ -46,12 +46,9 @@
<span class="text-gray-400 text-sm flex-shrink-0 whitespace-nowrap"> <span class="text-gray-400 text-sm flex-shrink-0 whitespace-nowrap">
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }}
</span> </span>
<div <div class="flex flex-wrap gap-1.5 flex-1 min-w-0">
class="flex flex-wrap gap-1.5 flex-1 min-w-0" <el-tag size="small">
v-if="item.tagNameList?.length" {{ articleTypeListOptions.find((i) => i.value === item.type)?.label }}
>
<el-tag v-for="tag in item.tagNameList" :key="tag" size="small">
{{ tag }}
</el-tag> </el-tag>
</div> </div>
</div> </div>
...@@ -92,6 +89,12 @@ ...@@ -92,6 +89,12 @@
拒绝 拒绝
</el-button> </el-button>
</div> </div>
<div
v-if="searchParams.isAudit === AuditStatusEnum.AGREED"
class="flex items-center text-gray-400 text-sm ml-4"
>
<el-button type="info" link @click="handleView(item)">查看</el-button>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -120,11 +123,13 @@ ...@@ -120,11 +123,13 @@
import { Document } from '@element-plus/icons-vue' import { Document } from '@element-plus/icons-vue'
import { getAuditList, auditArticle } from '@/api' import { getAuditList, auditArticle } from '@/api'
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { auditTypeListOptions } from '@/constants/options' import { auditTypeListOptions, articleTypeListOptions } from '@/constants/options'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { AuditStatusEnum } from '@/constants' import { AuditStatusEnum } from '@/constants'
import type { AuditArticleDto } from '@/api' import type { AuditArticleDto } from '@/api'
import type { TabPaneName } from 'element-plus' import type { TabPaneName } from 'element-plus'
import { ArticleTypeEnum } from '@/constants'
import type { ArticleItemDto } from '@/api'
const router = useRouter() const router = useRouter()
...@@ -160,4 +165,12 @@ const handleAudit = async (data: AuditArticleDto) => { ...@@ -160,4 +165,12 @@ const handleAudit = async (data: AuditArticleDto) => {
refresh() refresh()
} }
const handleView = (item: ArticleItemDto) => {
if (item.type === ArticleTypeEnum.VIDEO) {
router.push(`/videoDetail/${item.id}`)
} else {
router.push(`/articleDetail/${item.id}`)
}
}
</script> </script>
...@@ -3,15 +3,29 @@ ...@@ -3,15 +3,29 @@
<!-- List Container --> <!-- List Container -->
<div class="flex-1 p-4 pt-1"> <div class="flex-1 p-4 pt-1">
<div class="relative"> <div class="relative">
<el-tabs v-model="searchParams.messageType" @tab-change="toggleTab"> <el-tabs v-model="searchParams.type" @tab-change="toggleTab">
<el-tab-pane <el-tab-pane
v-for="tab in commentTypeListOptions" v-for="tab in articleTypeListOptions"
:key="tab.value" :key="tab.value"
:label="tab.label" :label="tab.label"
:name="tab.value" :name="tab.value"
/> />
</el-tabs> </el-tabs>
<div class="absolute right-0 top-2.5 z-1000">
<div class="absolute right-0 top-2.5 z-1000 flex items-center gap-2">
<el-select
v-model="searchParams.messageType"
@change="refresh"
class="w-25!"
size="small"
>
<el-option
v-for="tab in commentTypeListOptions"
:key="tab.value"
:label="tab.label"
:value="tab.value"
/>
</el-select>
<el-icon <el-icon
size="15" size="15"
class="cursor-pointer hover:rotate-180 transition-all duration-300" class="cursor-pointer hover:rotate-180 transition-all duration-300"
...@@ -35,22 +49,19 @@ ...@@ -35,22 +49,19 @@
class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer" class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer"
> >
<!-- Content --> <!-- Content -->
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0 space-y-1.5">
<div class="text-gray-900 font-medium truncate">{{ item.title }}</div> <div class="text-gray-900 font-medium text-base line-clamp-2">
<div class="text-gray-500 text-sm mt-1 truncate"> {{ item.title }}
<span class="mr-2"> </div>
{{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') }} <div class="text-gray-600 text-sm line-clamp-2">评论内容:{{ item.content }}</div>
</span> <div class="text-gray-400 text-xs">
<!-- <span class="mr-2">浏览 {{ item.viewCount }}</span> {{ dayjs(item.createTime * 1000).format('YYYY-MM-DD HH:mm') }}
<span class="mr-2">点赞 {{ item.replyCount }}</span>
<span class="mr-2">评论 {{ item.collectionCount }}</span>
<span class="mr-2">收藏 {{ item.praiseCount }}</span> -->
</div> </div>
</div> </div>
<!-- Meta Info --> <!-- Meta Info -->
<div class="flex items-center text-gray-400 text-sm ml-4"> <div class="flex items-center text-gray-400 text-sm ml-4">
<el-button type="primary" link>编辑</el-button> <el-button type="primary" link @click="handleView(item)">查看</el-button>
<el-button type="danger" link @click="handleDelete(item.id)">删除</el-button> <el-button type="danger" link @click="handleDelete(item.id)">删除</el-button>
</div> </div>
</div> </div>
...@@ -81,21 +92,27 @@ ...@@ -81,21 +92,27 @@
import { Document, Refresh } from '@element-plus/icons-vue' import { Document, Refresh } from '@element-plus/icons-vue'
import { getSelfCommentList, deleteComment } from '@/api' import { getSelfCommentList, deleteComment } from '@/api'
import { usePageSearch } from '@/hooks' import { usePageSearch } from '@/hooks'
import { commentTypeListOptions } from '@/constants/options' import { commentTypeListOptions, articleTypeListOptions } from '@/constants/options'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { CommentTypeEnum } from '@/constants/enums' import { CommentTypeEnum, ArticleTypeEnum } from '@/constants/enums'
import type { TabPaneName } from 'element-plus' import type { TabPaneName } from 'element-plus'
import type { SelfCommentItemDto } from '@/api/user/types'
const route = useRoute()
const router = useRouter()
const toggleTab = (key: TabPaneName) => { const toggleTab = (key: TabPaneName) => {
searchParams.value.messageType = key as CommentTypeEnum searchParams.value.type = key as ArticleTypeEnum
refresh() refresh()
} }
const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch( const { list, loading, searchParams, total, refresh, goToPage, changePageSize } = usePageSearch(
getSelfCommentList, getSelfCommentList,
{ {
immediate: false,
defaultParams: { defaultParams: {
messageType: CommentTypeEnum.COMMNET_TO_OTHER, messageType: CommentTypeEnum.COMMNET_TO_OTHER,
type: ArticleTypeEnum.POST,
}, },
}, },
) )
...@@ -110,4 +127,22 @@ const handleDelete = async (id: number) => { ...@@ -110,4 +127,22 @@ const handleDelete = async (id: number) => {
ElMessage.success('删除成功') ElMessage.success('删除成功')
refresh() refresh()
} }
const handleView = (item: SelfCommentItemDto) => {
if (item.type === ArticleTypeEnum.VIDEO) {
router.push(`/videoDetail/${item.articleId}`)
} else {
router.push(`/articleDetail/${item.articleId}`)
}
}
onActivated(() => {
if (route.query.type) {
searchParams.value.type = route.query.type as ArticleTypeEnum
}
if (route.query.messageType) {
searchParams.value.messageType = Number(route.query.messageType)
}
refresh()
})
</script> </script>
...@@ -51,10 +51,11 @@ ...@@ -51,10 +51,11 @@
</div> </div>
<div class="flex items-center text-gray-400 text-sm ml-4"> <div class="flex items-center text-gray-400 text-sm ml-4">
<el-button v-if="item.currentCount === item.limitCount" type="info" link disabled <!-- <el-button v-if="item.currentCount === item.limitCount" type="info" link disabled
>已完成</el-button >已完成</el-button
> > -->
<el-button v-else type="primary" link>去完成</el-button> <!-- <el-button v-else type="primary" link @click="handleTask(item)">去完成</el-button> -->
<el-button type="primary" link @click="handleTask(item)">去完成</el-button>
</div> </div>
</div> </div>
</div> </div>
...@@ -89,7 +90,10 @@ import { taskTypeListOptions } from '@/constants/options' ...@@ -89,7 +90,10 @@ import { taskTypeListOptions } from '@/constants/options'
import { TaskTypeEnum } from '@/constants/enums' import { TaskTypeEnum } from '@/constants/enums'
import type { TabPaneName } from 'element-plus' import type { TabPaneName } from 'element-plus'
import { TaskDateLimitTypeText } from '@/constants' import { TaskDateLimitTypeText } from '@/constants'
import type { TaskItemDto } from '@/api'
import { useRouter } from 'vue-router'
const router = useRouter()
const toggleTab = (key: TabPaneName) => { const toggleTab = (key: TabPaneName) => {
searchParams.value.taskType = key as TaskTypeEnum searchParams.value.taskType = key as TaskTypeEnum
refresh() refresh()
...@@ -103,4 +107,25 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize } ...@@ -103,4 +107,25 @@ const { list, loading, searchParams, total, refresh, goToPage, changePageSize }
}, },
}, },
) )
const handleTask = async (item: TaskItemDto) => {
console.log(item)
if (item.svgName === 'daily_sign') {
router.push(`/homePage/homeTab#levelContainerRef`)
} else if (item.svgName === 'valid_comments') {
// 发布评论
ElMessage.info('快去文章评论区去发表评论吧~')
} else if (item.svgName === 'topic_publish') {
router.push(`/homePage/askTab#tabsRef`)
} else if (item.svgName === 'answer_ask') {
// 回答问题
router.push('/userPage/selfAnswer')
} else if (item.svgName === 'video_publish') {
// 视频发布
router.push('/publishVideo')
} else if (item.svgName === 'practice_publish') {
// 个人实践/投稿
router.push('/publishCase')
}
}
</script> </script>
...@@ -28,9 +28,8 @@ ...@@ -28,9 +28,8 @@
@click="handleBackUser" @click="handleBackUser"
>返回个人账号</el-button >返回个人账号</el-button
> >
<el-button v-if="userInfo.isAdmin" type="primary" plain size="small" @click="handleAdmin" <!-- v-if="userInfo.isAdmin" 暂时不加权限 -->
>后台管理</el-button <el-button type="primary" plain size="small" @click="handleAdmin">后台管理</el-button>
>
</div> </div>
</div> </div>
...@@ -62,11 +61,11 @@ ...@@ -62,11 +61,11 @@
<div class="bg-white rounded-lg shadow-sm mb-4"> <div class="bg-white rounded-lg shadow-sm mb-4">
<div <div
v-for="item in menuUserItems" v-for="item in menuUserItems"
:key="item.key" :key="item.path"
@click="activeMenu = item.key" @click="changeMenu(item.path)"
:class="[ :class="[
'flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-gray-100 last:border-b-0', 'flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-gray-100 last:border-b-0',
activeMenu === item.key activeMenu === item.path
? 'bg-blue-50 text-blue-600 border-r-3 border-r-blue-600' ? 'bg-blue-50 text-blue-600 border-r-3 border-r-blue-600'
: 'text-gray-700 hover:bg-gray-50', : 'text-gray-700 hover:bg-gray-50',
]" ]"
...@@ -81,11 +80,11 @@ ...@@ -81,11 +80,11 @@
<div class="bg-white rounded-lg shadow-sm"> <div class="bg-white rounded-lg shadow-sm">
<div <div
v-for="item in menuOfficialItems" v-for="item in menuOfficialItems"
:key="item.key" :key="item.path"
@click="activeMenu = item.key" @click="changeMenu(item.path)"
:class="[ :class="[
'flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-gray-100 last:border-b-0', 'flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors border-b border-gray-100 last:border-b-0',
activeMenu === item.key activeMenu === item.path
? 'bg-blue-50 text-blue-600 border-r-3 border-r-blue-600' ? 'bg-blue-50 text-blue-600 border-r-3 border-r-blue-600'
: 'text-gray-700 hover:bg-gray-50', : 'text-gray-700 hover:bg-gray-50',
]" ]"
...@@ -101,11 +100,13 @@ ...@@ -101,11 +100,13 @@
<!-- 右侧内容区域 --> <!-- 右侧内容区域 -->
<div class="flex-1"> <div class="flex-1">
<div class="bg-white rounded-lg shadow-sm min-h-500px"> <div class="bg-white rounded-lg shadow-sm min-h-500px">
<router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<keep-alive> <keep-alive>
<component :is="currentComponent" ref="currentComponentRef" /> <component :is="Component" :key="getThirdLevelKey(route)" />
</keep-alive> </keep-alive>
</transition> </transition>
</router-view>
</div> </div>
</div> </div>
</div> </div>
...@@ -123,6 +124,8 @@ import { ...@@ -123,6 +124,8 @@ import {
Pointer, Pointer,
Collection, Collection,
Finished, Finished,
ChatLineSquare,
Warning,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
...@@ -135,48 +138,73 @@ import SelfCase from './components/selfCase.vue' ...@@ -135,48 +138,73 @@ import SelfCase from './components/selfCase.vue'
import SelfTask from './components/selfTask.vue' import SelfTask from './components/selfTask.vue'
import SelfComment from './components/selfComment.vue' import SelfComment from './components/selfComment.vue'
import SelfAudit from './components/selfAudit.vue' import SelfAudit from './components/selfAudit.vue'
import SelfAnswer from './components/selfAnswer.vue'
import SelfComplaint from './components/selfComplaint.vue'
import { generateLoginKey, hasOfficialAccount } from '@/api' import { generateLoginKey, hasOfficialAccount } from '@/api'
import type { OfficialAccountItemDto } from '@/api/user/types' import type { OfficialAccountItemDto } from '@/api/user/types'
import { wxLogin } from '@/utils/wxUtil' import { wxLogin } from '@/utils/wxUtil'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
import type { TabPaneName } from 'element-plus'
const router = useRouter()
const route = useRoute()
// 当前激活的菜单 用计算属性 好办法!
const activeMenu = computed(() => {
const path = route.path
if (path.includes('userPage')) {
return path.split('/').at(-1) || 'selfPublish'
}
return 'selfPublish'
})
const getThirdLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
console.log(route.fullPath, '三级路由用户信息页面')
return route.path
}
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)
const route = useRoute()
const key = route.query.key as string
// 当前激活的菜单
const activeMenu = ref(key || 'posts')
// 左侧普通用户菜单 // 左侧普通用户菜单
const menuUserItems = [ const menuUserItems = [
{ key: 'posts', label: '我的帖子', icon: User, component: SelfPublish, tab: '发布' }, { path: 'selfPublish', label: '我的帖子', icon: User, component: SelfPublish, tab: '发布' },
{ key: 'activity', label: '我的草稿', icon: Document, component: SelfDraft, tab: '草稿' }, { path: 'selfDraft', label: '我的草稿', icon: Document, component: SelfDraft, tab: '草稿' },
{ key: 'favorites', label: '我的收藏', icon: Star, component: SelfCollect, tab: '收藏' }, { path: 'selfCollect', label: '我的收藏', icon: Star, component: SelfCollect, tab: '收藏' },
{ key: 'praise', label: '我的点赞', icon: Pointer, component: SelfPraise, tab: '点赞' }, { path: 'selfPraise', label: '我的点赞', icon: Pointer, component: SelfPraise, tab: '点赞' },
{ key: 'case', label: '我的案例库', icon: Collection, component: SelfCase, tab: '案例库' }, { path: 'selfCase', label: '我的案例库', icon: Collection, component: SelfCase, tab: '案例库' },
{ key: 'task', label: '我的任务', icon: Finished, component: SelfTask, tab: '任务' }, { path: 'selfTask', label: '我的任务', icon: Finished, component: SelfTask, tab: '任务' },
{ {
key: 'comment', path: 'selfComment',
label: '评论回复', label: '评论回复',
icon: ChatDotRound, icon: ChatDotRound,
component: SelfComment, component: SelfComment,
tab: '评论回复', tab: '评论回复',
}, },
{
path: 'selfAnswer',
label: '回答问题(问吧)',
icon: ChatLineSquare,
component: SelfAnswer,
tab: '回答问题',
},
] ]
// 左侧官方账号菜单 // 左侧官方账号菜单
const menuOfficialItems = [ const menuOfficialItems = [
{ key: 'audit', label: '审核列表', icon: User, component: SelfAudit, tab: '审核列表' }, { path: 'selfAudit', label: '审核列表', icon: User, component: SelfAudit, tab: '审核列表' },
{
path: 'selfComplaint',
label: '举报列表',
icon: Warning,
component: SelfComplaint,
tab: '举报列表',
},
] ]
const currentComponentRef = useTemplateRef<InstanceType<typeof SelfAudit>>('currentComponentRef') const changeMenu = (key: TabPaneName) => {
const currentComponent = computed( router.push(`/userPage/${key}`)
() => }
[...menuUserItems, ...menuOfficialItems].find((item) => item.key === activeMenu.value)
?.component,
)
const handleEdit = () => { const handleEdit = () => {
console.log('修改资料') console.log('修改资料')
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { addOrCancelArticleReward, getYaBiData } from '@/api' import { addOrCancelArticleReward, getYaBiData } from '@/api'
const rewardNum = defineModel<number>('rewardNum', { required: true }) const rewardNum = defineModel<number>('rewardNum', { required: true, default: 0 })
interface RewardOption { interface RewardOption {
amount: number amount: number
icon: string icon: string
......
<template> <template>
<!-- 整体页面容器:浅灰背景 -->
<div> <div>
<!-- 一定要保证过度里面的内容是 只有一个根节点!!!! 纯字符串也不行 -->
<!-- 整体页面容器:浅灰背景 -->
<!-- 卡片1: 视频播放器区域 --> <!-- 卡片1: 视频播放器区域 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden"> <div class="bg-white rounded-lg shadow-sm overflow-hidden">
<!-- 标题区 --> <!-- 标题区 -->
<div class="p-4 pb-3"> <div class="p-4 pb-3">
<h1 class="text-xl md:text-2xl font-medium text-gray-900 mb-2 leading-snug"> <h1
class="text-xl md:text-2xl font-medium text-gray-900 mb-2 leading-snug flex items-center justify-between"
>
{{ videoDetail?.title }} {{ videoDetail?.title }}
<ActionMore :articleDetail="videoDetail" />
</h1> </h1>
<div class="flex items-center text-14px text-gray-500 gap-4 flex-wrap"> <div class="flex items-center text-14px text-gray-500 gap-4 flex-wrap">
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
...@@ -20,7 +24,6 @@ ...@@ -20,7 +24,6 @@
</div> </div>
</div> </div>
<!-- 视频播放器 -->
<div class="w-full bg-black aspect-video"> <div class="w-full bg-black aspect-video">
<video <video
ref="videoRef" ref="videoRef"
...@@ -238,6 +241,7 @@ import type { ArticleItemDto } from '@/api/article/types' ...@@ -238,6 +241,7 @@ import type { ArticleItemDto } from '@/api/article/types'
import Comment from '@/components/common/Comment/index.vue' import Comment from '@/components/common/Comment/index.vue'
import RewardDialog from './components/rewardDialog.vue' import RewardDialog from './components/rewardDialog.vue'
import ActionMore from '@/components/common/ActionMore/index.vue'
const route = useRoute() const route = useRoute()
......
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