Compare commits
5 Commits
5e42eb9930
...
83dedd9e1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83dedd9e1b | ||
|
|
77378e395b | ||
|
|
fe4c0df494 | ||
|
|
b5f6c04506 | ||
|
|
019234162f |
@ -53,6 +53,7 @@
|
||||
"@vueuse/core": "^13.3.0",
|
||||
"alova": "^3.3.2",
|
||||
"colord": "^2.9.3",
|
||||
"echarts": "6.0.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"radash": "^12.1.0",
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
:locale="naiveLocale.locale" :date-locale="naiveLocale.dateLocale" :theme-overrides="appStore.theme"
|
||||
>
|
||||
<naive-provider>
|
||||
<IconPreloader />
|
||||
<router-view />
|
||||
</naive-provider>
|
||||
</n-config-provider>
|
||||
|
||||
24
src/components/common/IconPreloader.vue
Normal file
24
src/components/common/IconPreloader.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<!-- 图标预加载组件 - 确保所有图标都被注册 -->
|
||||
<div style="display: none;">
|
||||
<icon-park-outline:palace />
|
||||
<icon-park-outline:peoples />
|
||||
<icon-park-outline:user />
|
||||
<icon-park-outline:calendar />
|
||||
<icon-park-outline:file-text />
|
||||
<icon-park-outline:comment />
|
||||
<icon-park-outline:like />
|
||||
<icon-park-outline:star />
|
||||
<icon-park-outline:search />
|
||||
<icon-park-outline:refresh />
|
||||
<icon-park-outline:check />
|
||||
<icon-park-outline:close />
|
||||
<icon-park-outline:delete />
|
||||
<icon-park-outline:preview-open />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 这个组件的唯一目的是让 unplugin-vue-components 扫描并注册这些图标
|
||||
// 组件本身不会被显示,只是用于触发图标组件的注册
|
||||
</script>
|
||||
68
src/service/api/heritage/comments/index.ts
Normal file
68
src/service/api/heritage/comments/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
CommentAdminBo,
|
||||
CommentAdminDetailVo,
|
||||
CommentAdminQueryBo,
|
||||
PageCommentAdminVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
CommentAdminBo,
|
||||
CommentAdminDetailVo,
|
||||
CommentAdminQueryBo,
|
||||
CommentAdminSearchForm,
|
||||
CommentAdminVo,
|
||||
PageCommentAdminVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询评论列表
|
||||
*/
|
||||
export function getCommentAdminList(params: CommentAdminQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageCommentAdminVo>>('/coder/admin/comment/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看评论详情
|
||||
*/
|
||||
export function getCommentAdminDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<CommentAdminDetailVo>>(`/coder/admin/comment/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核评论
|
||||
*/
|
||||
export function auditComment(data: CommentAdminBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/comment/audit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量审核评论
|
||||
*/
|
||||
export function batchAuditComments(ids: string[], status: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/comment/batchAudit', ids, {
|
||||
params: { status },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
export function deleteComment(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/comment/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除评论
|
||||
*/
|
||||
export function batchDeleteComments(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/comment/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待审核评论数量
|
||||
*/
|
||||
export function getPendingCommentCount() {
|
||||
return request.Get<Service.ResponseResult<number>>('/coder/admin/comment/pendingCount')
|
||||
}
|
||||
89
src/service/api/heritage/comments/types.ts
Normal file
89
src/service/api/heritage/comments/types.ts
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 评论管理查询参数类型
|
||||
*/
|
||||
export interface CommentAdminQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
userId?: string
|
||||
targetType?: string
|
||||
targetId?: string
|
||||
content?: string
|
||||
status?: number
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
userKeyword?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论管理列表VO
|
||||
*/
|
||||
export interface CommentAdminVo {
|
||||
id: string
|
||||
userId: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatar?: string
|
||||
targetType: string
|
||||
targetId: string
|
||||
targetTitle: string
|
||||
content: string
|
||||
rating?: number
|
||||
parentId: string
|
||||
likeCount: number
|
||||
status: number
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论管理详情VO
|
||||
*/
|
||||
export interface CommentAdminDetailVo {
|
||||
id: string
|
||||
userId: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatar?: string
|
||||
targetType: string
|
||||
targetId: string
|
||||
targetTitle: string
|
||||
content: string
|
||||
rating?: number
|
||||
parentId: string
|
||||
likeCount: number
|
||||
status: number
|
||||
createTime: string
|
||||
updateTime: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论管理BO(审核)
|
||||
*/
|
||||
export interface CommentAdminBo {
|
||||
id: string
|
||||
status: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageCommentAdminVo {
|
||||
records: CommentAdminVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 评论搜索表单类型
|
||||
*/
|
||||
export interface CommentAdminSearchForm {
|
||||
userId?: string
|
||||
targetType?: string | ''
|
||||
targetId?: string
|
||||
content?: string | ''
|
||||
status?: number | null
|
||||
userKeyword?: string | ''
|
||||
}
|
||||
77
src/service/api/heritage/events/index.ts
Normal file
77
src/service/api/heritage/events/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
EventAdminBo,
|
||||
EventAdminDetailVo,
|
||||
EventAdminQueryBo,
|
||||
PageEventAdminVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
EventAdminBo,
|
||||
EventAdminDetailVo,
|
||||
EventAdminQueryBo,
|
||||
EventAdminSearchForm,
|
||||
EventAdminVo,
|
||||
PageEventAdminVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询活动列表
|
||||
*/
|
||||
export function getEventAdminList(params: EventAdminQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageEventAdminVo>>('/coder/admin/event/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看活动详情
|
||||
*/
|
||||
export function getEventAdminDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<EventAdminDetailVo>>(`/coder/admin/event/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增活动
|
||||
*/
|
||||
export function addEventAdmin(data: EventAdminBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/admin/event/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑活动
|
||||
*/
|
||||
export function updateEventAdmin(data: EventAdminBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/event/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除活动
|
||||
*/
|
||||
export function deleteEventAdmin(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/event/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除活动
|
||||
*/
|
||||
export function batchDeleteEventAdmin(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/event/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改发布状态
|
||||
*/
|
||||
export function changeEventPublishStatus(id: string, publishStatus: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/event/changePublishStatus', null, {
|
||||
params: { id, publishStatus },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改活动状态
|
||||
*/
|
||||
export function changeEventStatus(id: string, status: string) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/event/changeEventStatus', null, {
|
||||
params: { id, status },
|
||||
})
|
||||
}
|
||||
106
src/service/api/heritage/events/types.ts
Normal file
106
src/service/api/heritage/events/types.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 活动管理查询参数类型
|
||||
*/
|
||||
export interface EventAdminQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
title?: string
|
||||
location?: string
|
||||
status?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
startTimeBegin?: string
|
||||
startTimeEnd?: string
|
||||
sortField?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 活动管理列表VO
|
||||
*/
|
||||
export interface EventAdminVo {
|
||||
id: string
|
||||
title: string
|
||||
summary?: string
|
||||
coverImage?: string
|
||||
location?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
maxParticipants?: number
|
||||
currentParticipants?: number
|
||||
registrationStart?: string
|
||||
registrationEnd?: string
|
||||
status: string
|
||||
viewCount?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 活动管理详情VO
|
||||
*/
|
||||
export interface EventAdminDetailVo {
|
||||
id: string
|
||||
title: string
|
||||
summary?: string
|
||||
content?: string
|
||||
coverImage?: string
|
||||
location?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
maxParticipants?: number
|
||||
currentParticipants?: number
|
||||
registrationStart?: string
|
||||
registrationEnd?: string
|
||||
status: string
|
||||
viewCount?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 活动管理BO(新增/编辑)
|
||||
*/
|
||||
export interface EventAdminBo {
|
||||
id?: string
|
||||
title: string
|
||||
summary?: string
|
||||
content?: string
|
||||
coverImage?: string
|
||||
location?: string
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
maxParticipants?: number
|
||||
registrationStart?: string
|
||||
registrationEnd?: string
|
||||
status?: string
|
||||
publishStatus?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageEventAdminVo {
|
||||
records: EventAdminVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 活动搜索表单类型
|
||||
*/
|
||||
export interface EventAdminSearchForm {
|
||||
title?: string
|
||||
location?: string
|
||||
status?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
}
|
||||
77
src/service/api/heritage/inheritors/index.ts
Normal file
77
src/service/api/heritage/inheritors/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
InheritorBo,
|
||||
InheritorDetailVo,
|
||||
InheritorQueryBo,
|
||||
PageInheritorVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
InheritorBo,
|
||||
InheritorDetailVo,
|
||||
InheritorQueryBo,
|
||||
InheritorSearchForm,
|
||||
InheritorVo,
|
||||
PageInheritorVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询传承人列表
|
||||
*/
|
||||
export function getInheritorList(params: InheritorQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageInheritorVo>>('/coder/admin/inheritor/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看传承人详情
|
||||
*/
|
||||
export function getInheritorDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<InheritorDetailVo>>(`/coder/admin/inheritor/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增传承人
|
||||
*/
|
||||
export function addInheritor(data: InheritorBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/admin/inheritor/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑传承人
|
||||
*/
|
||||
export function updateInheritor(data: InheritorBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/inheritor/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除传承人
|
||||
*/
|
||||
export function deleteInheritor(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/inheritor/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除传承人
|
||||
*/
|
||||
export function batchDeleteInheritors(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/inheritor/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改发布状态
|
||||
*/
|
||||
export function changePublishStatus(id: string, publishStatus: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/inheritor/changePublishStatus', null, {
|
||||
params: { id, publishStatus },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置精选状态
|
||||
*/
|
||||
export function setFeatured(id: string, isFeatured: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/inheritor/setFeatured', null, {
|
||||
params: { id, isFeatured },
|
||||
})
|
||||
}
|
||||
133
src/service/api/heritage/inheritors/types.ts
Normal file
133
src/service/api/heritage/inheritors/types.ts
Normal file
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 传承人管理查询参数类型
|
||||
*/
|
||||
export interface InheritorQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
name?: string
|
||||
nameEn?: string
|
||||
gender?: number
|
||||
heritageId?: string
|
||||
heritageName?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
isFeatured?: number
|
||||
sortField?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 传承人管理列表VO
|
||||
*/
|
||||
export interface InheritorVo {
|
||||
id: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
gender?: number
|
||||
birthYear?: number
|
||||
avatar?: string
|
||||
heritageId?: string
|
||||
heritageName?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
introduction?: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
isFeatured: number
|
||||
sortOrder?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 传承人管理详情VO
|
||||
*/
|
||||
export interface InheritorDetailVo {
|
||||
id: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
gender?: number
|
||||
birthYear?: number
|
||||
avatar?: string
|
||||
heritageId?: string
|
||||
heritageName?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
introduction?: string
|
||||
story?: string
|
||||
achievements?: string
|
||||
works?: string
|
||||
images?: string
|
||||
videoUrl?: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
isFeatured: number
|
||||
sortOrder?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 传承人管理BO(新增/编辑)
|
||||
*/
|
||||
export interface InheritorBo {
|
||||
id?: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
gender?: number
|
||||
birthYear?: number
|
||||
avatar?: string
|
||||
heritageId?: string
|
||||
heritageName?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
introduction?: string
|
||||
story?: string
|
||||
achievements?: string
|
||||
works?: string
|
||||
images?: string
|
||||
videoUrl?: string
|
||||
isFeatured?: number
|
||||
sortOrder?: number
|
||||
publishStatus?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageInheritorVo {
|
||||
records: InheritorVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 传承人搜索表单类型
|
||||
*/
|
||||
export interface InheritorSearchForm {
|
||||
name?: string
|
||||
nameEn?: string
|
||||
gender?: number
|
||||
heritageId?: string
|
||||
heritageName?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
isFeatured?: number
|
||||
}
|
||||
77
src/service/api/heritage/items/index.ts
Normal file
77
src/service/api/heritage/items/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
HeritageItemBo,
|
||||
HeritageItemDetailVo,
|
||||
HeritageItemQueryBo,
|
||||
PageHeritageItemVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
HeritageItemBo,
|
||||
HeritageItemDetailVo,
|
||||
HeritageItemQueryBo,
|
||||
HeritageItemSearchForm,
|
||||
HeritageItemVo,
|
||||
PageHeritageItemVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询非遗项目列表
|
||||
*/
|
||||
export function getHeritageItemList(params: HeritageItemQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageHeritageItemVo>>('/coder/admin/heritage/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看非遗项目详情
|
||||
*/
|
||||
export function getHeritageItemDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<HeritageItemDetailVo>>(`/coder/admin/heritage/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增非遗项目
|
||||
*/
|
||||
export function addHeritageItem(data: HeritageItemBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/admin/heritage/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑非遗项目
|
||||
*/
|
||||
export function updateHeritageItem(data: HeritageItemBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/heritage/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除非遗项目
|
||||
*/
|
||||
export function deleteHeritageItem(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/heritage/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除非遗项目
|
||||
*/
|
||||
export function batchDeleteHeritageItems(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/heritage/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改发布状态
|
||||
*/
|
||||
export function changePublishStatus(id: string, publishStatus: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/heritage/changePublishStatus', null, {
|
||||
params: { id, publishStatus },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置精选状态
|
||||
*/
|
||||
export function setFeatured(id: string, isFeatured: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/heritage/setFeatured', null, {
|
||||
params: { id, isFeatured },
|
||||
})
|
||||
}
|
||||
134
src/service/api/heritage/items/types.ts
Normal file
134
src/service/api/heritage/items/types.ts
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 非遗项目管理查询参数类型
|
||||
*/
|
||||
export interface HeritageItemQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
name?: string
|
||||
nameEn?: string
|
||||
category?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
status?: string
|
||||
tag?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
isFeatured?: number
|
||||
sortField?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 非遗项目管理列表VO
|
||||
*/
|
||||
export interface HeritageItemVo {
|
||||
id: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
category: string
|
||||
level: string
|
||||
province?: string
|
||||
city?: string
|
||||
description?: string
|
||||
coverImage?: string
|
||||
tags?: string
|
||||
status: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
favoriteCount?: number
|
||||
commentCount?: number
|
||||
isFeatured: number
|
||||
sortOrder?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 非遗项目管理详情VO
|
||||
*/
|
||||
export interface HeritageItemDetailVo {
|
||||
id: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
category: string
|
||||
level: string
|
||||
province?: string
|
||||
city?: string
|
||||
description?: string
|
||||
history?: string
|
||||
skills?: string
|
||||
significance?: string
|
||||
coverImage?: string
|
||||
images?: string
|
||||
videoUrl?: string
|
||||
tags?: string
|
||||
status: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
favoriteCount?: number
|
||||
commentCount?: number
|
||||
isFeatured: number
|
||||
sortOrder?: number
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 非遗项目管理BO(新增/编辑)
|
||||
*/
|
||||
export interface HeritageItemBo {
|
||||
id?: string
|
||||
name: string
|
||||
nameEn?: string
|
||||
category: string
|
||||
level: string
|
||||
province?: string
|
||||
city?: string
|
||||
description?: string
|
||||
history?: string
|
||||
skills?: string
|
||||
significance?: string
|
||||
coverImage?: string
|
||||
images?: string
|
||||
videoUrl?: string
|
||||
tags?: string
|
||||
status?: string
|
||||
isFeatured?: number
|
||||
sortOrder?: number
|
||||
publishStatus?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageHeritageItemVo {
|
||||
records: HeritageItemVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 非遗项目搜索表单类型
|
||||
*/
|
||||
export interface HeritageItemSearchForm {
|
||||
name?: string
|
||||
nameEn?: string
|
||||
category?: string
|
||||
level?: string
|
||||
province?: string
|
||||
city?: string
|
||||
status?: string
|
||||
tag?: string
|
||||
keyword?: string
|
||||
publishStatus?: number
|
||||
isFeatured?: number
|
||||
}
|
||||
77
src/service/api/heritage/news/index.ts
Normal file
77
src/service/api/heritage/news/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
NewsBo,
|
||||
NewsDetailVo,
|
||||
NewsQueryBo,
|
||||
PageNewsVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
NewsBo,
|
||||
NewsDetailVo,
|
||||
NewsQueryBo,
|
||||
NewsSearchForm,
|
||||
NewsVo,
|
||||
PageNewsVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询新闻资讯列表
|
||||
*/
|
||||
export function getNewsList(params: NewsQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageNewsVo>>('/coder/admin/news/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看新闻资讯详情
|
||||
*/
|
||||
export function getNewsDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<NewsDetailVo>>(`/coder/admin/news/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增新闻资讯
|
||||
*/
|
||||
export function addNews(data: NewsBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/admin/news/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑新闻资讯
|
||||
*/
|
||||
export function updateNews(data: NewsBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/news/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除新闻资讯
|
||||
*/
|
||||
export function deleteNews(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/news/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除新闻资讯
|
||||
*/
|
||||
export function batchDeleteNews(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/news/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改发布状态
|
||||
*/
|
||||
export function changePublishStatus(id: string, publishStatus: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/news/changePublishStatus', null, {
|
||||
params: { id, publishStatus },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置置顶状态
|
||||
*/
|
||||
export function setTop(id: string, isTop: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/news/setTop', null, {
|
||||
params: { id, isTop },
|
||||
})
|
||||
}
|
||||
111
src/service/api/heritage/news/types.ts
Normal file
111
src/service/api/heritage/news/types.ts
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 新闻资讯管理查询参数类型
|
||||
*/
|
||||
export interface NewsQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
title?: string
|
||||
author?: string
|
||||
source?: string
|
||||
category?: string
|
||||
tag?: string
|
||||
keyword?: string
|
||||
isTop?: number
|
||||
publishStatus?: number
|
||||
publishTimeStart?: string
|
||||
publishTimeEnd?: string
|
||||
sortField?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 新闻资讯管理列表VO
|
||||
*/
|
||||
export interface NewsVo {
|
||||
id: string
|
||||
title: string
|
||||
summary?: string
|
||||
coverImage?: string
|
||||
author?: string
|
||||
source?: string
|
||||
category?: string
|
||||
tags?: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
isTop: number
|
||||
publishTime?: string
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 新闻资讯管理详情VO
|
||||
*/
|
||||
export interface NewsDetailVo {
|
||||
id: string
|
||||
title: string
|
||||
summary?: string
|
||||
content?: string
|
||||
coverImage?: string
|
||||
author?: string
|
||||
source?: string
|
||||
category?: string
|
||||
tags?: string
|
||||
viewCount?: number
|
||||
likeCount?: number
|
||||
isTop: number
|
||||
publishTime?: string
|
||||
publishStatus: number
|
||||
createBy?: string
|
||||
createTime?: string
|
||||
updateBy?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 新闻资讯管理BO(新增/编辑)
|
||||
*/
|
||||
export interface NewsBo {
|
||||
id?: string
|
||||
title: string
|
||||
summary?: string
|
||||
content?: string
|
||||
coverImage?: string
|
||||
author?: string
|
||||
source?: string
|
||||
category?: string
|
||||
tags?: string
|
||||
isTop?: number
|
||||
publishTime?: string
|
||||
publishStatus?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageNewsVo {
|
||||
records: NewsVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 新闻资讯搜索表单类型
|
||||
*/
|
||||
export interface NewsSearchForm {
|
||||
title?: string
|
||||
author?: string
|
||||
source?: string
|
||||
category?: string
|
||||
tag?: string
|
||||
keyword?: string
|
||||
isTop?: number
|
||||
publishStatus?: number
|
||||
publishTimeStart?: string
|
||||
publishTimeEnd?: string
|
||||
}
|
||||
56
src/service/api/heritage/statistics/index.ts
Normal file
56
src/service/api/heritage/statistics/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
RankingVo,
|
||||
StatisticsVo,
|
||||
TrendVo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
RankingVo,
|
||||
StatisticsVo,
|
||||
TrendVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 获取核心统计数据
|
||||
*/
|
||||
export function getStatistics() {
|
||||
return request.Get<Service.ResponseResult<StatisticsVo>>('/coder/admin/statistics/core')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户增长趋势
|
||||
*/
|
||||
export function getUserTrend(days: number = 7) {
|
||||
return request.Get<Service.ResponseResult<TrendVo[]>>('/coder/admin/statistics/userTrend', {
|
||||
params: { days },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容发布趋势
|
||||
*/
|
||||
export function getContentTrend(days: number = 7) {
|
||||
return request.Get<Service.ResponseResult<TrendVo[]>>('/coder/admin/statistics/contentTrend', {
|
||||
params: { days },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取热门非遗项目排行榜
|
||||
*/
|
||||
export function getHeritageRanking(type: string = 'view', limit: number = 10) {
|
||||
return request.Get<Service.ResponseResult<RankingVo[]>>('/coder/admin/statistics/heritageRanking', {
|
||||
params: { type, limit },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃用户排行榜
|
||||
*/
|
||||
export function getActiveUserRanking(limit: number = 10) {
|
||||
return request.Get<Service.ResponseResult<RankingVo[]>>('/coder/admin/statistics/activeUserRanking', {
|
||||
params: { limit },
|
||||
})
|
||||
}
|
||||
66
src/service/api/heritage/statistics/types.ts
Normal file
66
src/service/api/heritage/statistics/types.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 统计数据VO
|
||||
*/
|
||||
export interface StatisticsVo {
|
||||
// 非遗项目统计
|
||||
heritageTotal: number
|
||||
heritageTodayCount: number
|
||||
|
||||
// 传承人统计
|
||||
inheritorTotal: number
|
||||
inheritorTodayCount: number
|
||||
|
||||
// 用户统计
|
||||
userTotal: number
|
||||
userTodayCount: number
|
||||
|
||||
// 活动统计
|
||||
eventTotal: number
|
||||
eventTodayCount: number
|
||||
|
||||
// 新闻统计
|
||||
newsTotal: number
|
||||
newsTodayCount: number
|
||||
|
||||
// 评论统计
|
||||
commentTotal: number
|
||||
commentTodayCount: number
|
||||
commentPendingCount: number
|
||||
|
||||
// 点赞统计
|
||||
likeTotal: number
|
||||
likeTodayCount: number
|
||||
|
||||
// 收藏统计
|
||||
favoriteTotal: number
|
||||
favoriteTodayCount: number
|
||||
|
||||
// 浏览统计
|
||||
viewTotal: number
|
||||
viewTodayCount: number
|
||||
|
||||
// 活动报名统计
|
||||
registrationTotal: number
|
||||
registrationTodayCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 趋势数据VO
|
||||
*/
|
||||
export interface TrendVo {
|
||||
date: string
|
||||
value: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 排行榜数据VO
|
||||
*/
|
||||
export interface RankingVo {
|
||||
rank: number
|
||||
id: string
|
||||
title: string
|
||||
type: string
|
||||
coverImage?: string
|
||||
value: number
|
||||
valueType: string
|
||||
}
|
||||
77
src/service/api/heritage/users/index.ts
Normal file
77
src/service/api/heritage/users/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { request } from '../../../http'
|
||||
import type {
|
||||
PageUserAdminVo,
|
||||
UserAdminBo,
|
||||
UserAdminDetailVo,
|
||||
UserAdminQueryBo,
|
||||
} from './types'
|
||||
|
||||
// 重新导出类型供外部使用
|
||||
export type {
|
||||
PageUserAdminVo,
|
||||
UserAdminBo,
|
||||
UserAdminDetailVo,
|
||||
UserAdminQueryBo,
|
||||
UserAdminSearchForm,
|
||||
UserAdminVo,
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 分页查询前台用户列表
|
||||
*/
|
||||
export function getUserAdminList(params: UserAdminQueryBo) {
|
||||
return request.Get<Service.ResponseResult<PageUserAdminVo>>('/coder/admin/user/listPage', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看前台用户详情
|
||||
*/
|
||||
export function getUserAdminDetail(id: string) {
|
||||
return request.Get<Service.ResponseResult<UserAdminDetailVo>>(`/coder/admin/user/detail/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增前台用户
|
||||
*/
|
||||
export function addUserAdmin(data: UserAdminBo) {
|
||||
return request.Post<Service.ResponseResult<string>>('/coder/admin/user/add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑前台用户
|
||||
*/
|
||||
export function updateUserAdmin(data: UserAdminBo) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/user/edit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除前台用户
|
||||
*/
|
||||
export function deleteUserAdmin(id: string) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>(`/coder/admin/user/delete/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除前台用户
|
||||
*/
|
||||
export function batchDeleteUserAdmin(ids: string[]) {
|
||||
return request.Delete<Service.ResponseResult<boolean>>('/coder/admin/user/batchDelete', { data: ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户状态
|
||||
*/
|
||||
export function changeUserStatus(id: string, status: number) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/user/changeStatus', null, {
|
||||
params: { id, status },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*/
|
||||
export function resetUserPassword(id: string, newPassword: string) {
|
||||
return request.Put<Service.ResponseResult<boolean>>('/coder/admin/user/resetPassword', null, {
|
||||
params: { id, newPassword },
|
||||
})
|
||||
}
|
||||
109
src/service/api/heritage/users/types.ts
Normal file
109
src/service/api/heritage/users/types.ts
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 前台用户管理查询参数类型
|
||||
*/
|
||||
export interface UserAdminQueryBo {
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
username?: string
|
||||
nickname?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
keyword?: string
|
||||
status?: number
|
||||
gender?: number
|
||||
province?: string
|
||||
city?: string
|
||||
createTimeBegin?: string
|
||||
createTimeEnd?: string
|
||||
sortField?: string
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台用户管理列表VO
|
||||
*/
|
||||
export interface UserAdminVo {
|
||||
id: string
|
||||
username: string
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
gender?: number
|
||||
birthday?: string
|
||||
province?: string
|
||||
city?: string
|
||||
status: number
|
||||
loginIp?: string
|
||||
loginTime?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台用户管理详情VO
|
||||
*/
|
||||
export interface UserAdminDetailVo {
|
||||
id: string
|
||||
username: string
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
gender?: number
|
||||
birthday?: string
|
||||
province?: string
|
||||
city?: string
|
||||
status: number
|
||||
loginIp?: string
|
||||
loginTime?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台用户管理BO(新增/编辑)
|
||||
*/
|
||||
export interface UserAdminBo {
|
||||
id?: string
|
||||
username: string
|
||||
password?: string
|
||||
nickname?: string
|
||||
avatar?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
gender?: number
|
||||
birthday?: string
|
||||
province?: string
|
||||
city?: string
|
||||
status?: number
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页结果类型
|
||||
*/
|
||||
export interface PageUserAdminVo {
|
||||
records: UserAdminVo[]
|
||||
total: number
|
||||
size: number
|
||||
current: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台用户搜索表单类型
|
||||
*/
|
||||
export interface UserAdminSearchForm {
|
||||
username?: string
|
||||
nickname?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
keyword?: string
|
||||
status?: number
|
||||
gender?: number
|
||||
province?: string
|
||||
city?: string
|
||||
}
|
||||
@ -62,17 +62,18 @@ export function batchDeleteSysPictures(ids: number[]) {
|
||||
|
||||
// 图片上传相关API
|
||||
|
||||
// 上传图片
|
||||
export function uploadPicture(file: File, pictureType = '9', fileSize = 2, storageType?: string) {
|
||||
// 上传图片到指定存储服务
|
||||
export function uploadPicture(file: File, folderName = 'heritage', fileParam = '2', fileSize = 2, storageType = 'minio') {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
// 如果指定了存储类型,添加到表单数据中
|
||||
// 添加存储类型参数
|
||||
if (storageType) {
|
||||
formData.append('storageType', storageType)
|
||||
}
|
||||
|
||||
return request.Post<PictureUploadResult>(`/coder/file/uploadFile/${fileSize}/pictures/${pictureType}`, formData)
|
||||
// 使用通用文件上传接口:/coder/file/uploadFile/{fileSize}/{folderName}/{fileParam}
|
||||
return request.Post<PictureUploadResult>(`/coder/file/uploadFile/${fileSize}/${folderName}/${fileParam}`, formData)
|
||||
}
|
||||
|
||||
// 匿名上传图片(无需登录)
|
||||
|
||||
1062
src/views/heritage/comments/index.vue
Normal file
1062
src/views/heritage/comments/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1235
src/views/heritage/events/index.vue
Normal file
1235
src/views/heritage/events/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1422
src/views/heritage/inheritors/index.vue
Normal file
1422
src/views/heritage/inheritors/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1462
src/views/heritage/items/index.vue
Normal file
1462
src/views/heritage/items/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1188
src/views/heritage/news/index.vue
Normal file
1188
src/views/heritage/news/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
715
src/views/heritage/statistics/index.vue
Normal file
715
src/views/heritage/statistics/index.vue
Normal file
@ -0,0 +1,715 @@
|
||||
<template>
|
||||
<div class="statistics-dashboard p-4 bg-gray-50 min-h-screen">
|
||||
<!-- 页面标题 -->
|
||||
<div class="mb-4">
|
||||
<h2 class="text-2xl font-semibold text-gray-800">
|
||||
统计分析
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
数据概览与趋势分析
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 核心统计卡片 -->
|
||||
<div class="statistics-cards mb-4">
|
||||
<NGrid :cols="4" :x-gap="16" :y-gap="16">
|
||||
<!-- 非遗项目统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
非遗项目
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.heritageTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.heritageTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-blue-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#3b82f6">
|
||||
<icon-park-outline:palace />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 传承人统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
传承人
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.inheritorTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.inheritorTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-purple-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#9333ea">
|
||||
<icon-park-outline:peoples />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 用户统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
注册用户
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.userTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.userTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-green-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#16a34a">
|
||||
<icon-park-outline:user />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 活动统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
活动总数
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.eventTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.eventTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-yellow-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#eab308">
|
||||
<icon-park-outline:calendar />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 新闻统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
新闻资讯
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.newsTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.newsTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-red-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#dc2626">
|
||||
<icon-park-outline:file-text />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 评论统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
评论总数
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.commentTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-orange-600">
|
||||
待审核 {{ statistics.commentPendingCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-indigo-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#6366f1">
|
||||
<icon-park-outline:comment />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 点赞统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
点赞总数
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.likeTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.likeTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-pink-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#ec4899">
|
||||
<icon-park-outline:like />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 收藏统计 -->
|
||||
<NGridItem>
|
||||
<div class="stat-card bg-white rounded-lg shadow-sm border border-gray-100 p-4 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-500 mb-1">
|
||||
收藏总数
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-gray-800 mb-1">
|
||||
{{ formatNumber(statistics.favoriteTotal) }}
|
||||
</p>
|
||||
<p class="text-xs text-green-600">
|
||||
今日新增 {{ statistics.favoriteTodayCount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon bg-amber-50 rounded-full p-3">
|
||||
<NIcon size="32" color="#f59e0b">
|
||||
<icon-park-outline:star />
|
||||
</NIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</div>
|
||||
|
||||
<!-- 趋势图表区域 -->
|
||||
<div class="charts-section mb-4">
|
||||
<NGrid :cols="2" :x-gap="16">
|
||||
<!-- 用户增长趋势 -->
|
||||
<NGridItem>
|
||||
<div class="chart-card bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">
|
||||
用户增长趋势
|
||||
</h3>
|
||||
<NRadioGroup v-model:value="userTrendDays" size="small" @update:value="loadUserTrend">
|
||||
<NRadioButton :value="7">
|
||||
近7天
|
||||
</NRadioButton>
|
||||
<NRadioButton :value="30">
|
||||
近30天
|
||||
</NRadioButton>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
<div ref="userTrendChartRef" class="chart-container" style="height: 300px;" />
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 内容发布趋势 -->
|
||||
<NGridItem>
|
||||
<div class="chart-card bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">
|
||||
内容发布趋势
|
||||
</h3>
|
||||
<NRadioGroup v-model:value="contentTrendDays" size="small" @update:value="loadContentTrend">
|
||||
<NRadioButton :value="7">
|
||||
近7天
|
||||
</NRadioButton>
|
||||
<NRadioButton :value="30">
|
||||
近30天
|
||||
</NRadioButton>
|
||||
</NRadioGroup>
|
||||
</div>
|
||||
<div ref="contentTrendChartRef" class="chart-container" style="height: 300px;" />
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</div>
|
||||
|
||||
<!-- 排行榜区域 -->
|
||||
<div class="rankings-section">
|
||||
<NGrid :cols="2" :x-gap="16">
|
||||
<!-- 热门非遗项目排行榜 -->
|
||||
<NGridItem>
|
||||
<div class="ranking-card bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">
|
||||
热门非遗项目排行榜
|
||||
</h3>
|
||||
<NSelect
|
||||
v-model:value="heritageRankingType"
|
||||
size="small"
|
||||
style="width: 120px"
|
||||
:options="rankingTypeOptions"
|
||||
@update:value="loadHeritageRanking"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="heritageRankingLoading" class="flex items-center justify-center py-12">
|
||||
<NSpin size="medium" />
|
||||
</div>
|
||||
<div v-else-if="heritageRanking.length > 0" class="ranking-list">
|
||||
<div
|
||||
v-for="(item, index) in heritageRanking"
|
||||
:key="item.id"
|
||||
class="ranking-item flex items-center py-3 border-b border-gray-100 last:border-b-0 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div class="ranking-number flex-shrink-0 w-8 h-8 flex items-center justify-center mr-3">
|
||||
<span
|
||||
:class="{
|
||||
'text-lg font-bold text-red-500': index === 0,
|
||||
'text-lg font-bold text-orange-500': index === 1,
|
||||
'text-lg font-bold text-yellow-500': index === 2,
|
||||
'text-sm text-gray-500': index > 2,
|
||||
}"
|
||||
>
|
||||
{{ item.rank }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.coverImage" class="ranking-cover flex-shrink-0 w-12 h-12 mr-3">
|
||||
<img :src="item.coverImage" :alt="item.title" class="w-full h-full object-cover rounded">
|
||||
</div>
|
||||
<div class="ranking-info flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-800 truncate">
|
||||
{{ item.title }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
<NTag type="primary" size="small">
|
||||
{{ item.type }}
|
||||
</NTag>
|
||||
</p>
|
||||
</div>
|
||||
<div class="ranking-value flex-shrink-0 ml-3 text-right">
|
||||
<p class="text-lg font-semibold text-blue-600">
|
||||
{{ formatNumber(item.value) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ getRankingValueLabel(item.valueType) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center py-12 text-gray-400">
|
||||
<span>暂无数据</span>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
|
||||
<!-- 活跃用户排行榜 -->
|
||||
<NGridItem>
|
||||
<div class="ranking-card bg-white rounded-lg shadow-sm border border-gray-100 p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-800">
|
||||
活跃用户排行榜
|
||||
</h3>
|
||||
</div>
|
||||
<div v-if="activeUserRankingLoading" class="flex items-center justify-center py-12">
|
||||
<NSpin size="medium" />
|
||||
</div>
|
||||
<div v-else-if="activeUserRanking.length > 0" class="ranking-list">
|
||||
<div
|
||||
v-for="(item, index) in activeUserRanking"
|
||||
:key="item.id"
|
||||
class="ranking-item flex items-center py-3 border-b border-gray-100 last:border-b-0 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<div class="ranking-number flex-shrink-0 w-8 h-8 flex items-center justify-center mr-3">
|
||||
<span
|
||||
:class="{
|
||||
'text-lg font-bold text-red-500': index === 0,
|
||||
'text-lg font-bold text-orange-500': index === 1,
|
||||
'text-lg font-bold text-yellow-500': index === 2,
|
||||
'text-sm text-gray-500': index > 2,
|
||||
}"
|
||||
>
|
||||
{{ item.rank }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ranking-info flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-800 truncate">
|
||||
{{ item.title }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ranking-value flex-shrink-0 ml-3 text-right">
|
||||
<p class="text-lg font-semibold text-green-600">
|
||||
{{ formatNumber(item.value) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ getRankingValueLabel(item.valueType) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center py-12 text-gray-400">
|
||||
<span>暂无数据</span>
|
||||
</div>
|
||||
</div>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { NGrid, NGridItem, NIcon, NRadioButton, NRadioGroup, NSelect, NSpin, NTag } from 'naive-ui'
|
||||
import * as echarts from 'echarts'
|
||||
import type { ECharts } from 'echarts'
|
||||
import {
|
||||
getActiveUserRanking,
|
||||
getContentTrend,
|
||||
getHeritageRanking,
|
||||
getStatistics,
|
||||
getUserTrend,
|
||||
} from '@/service/api/heritage/statistics'
|
||||
import type {
|
||||
RankingVo,
|
||||
StatisticsVo,
|
||||
TrendVo,
|
||||
} from '@/service/api/heritage/statistics'
|
||||
import { coiMsgError } from '@/utils/coi'
|
||||
|
||||
// 核心统计数据
|
||||
const statistics = ref<StatisticsVo>({
|
||||
heritageTotal: 0,
|
||||
heritageTodayCount: 0,
|
||||
inheritorTotal: 0,
|
||||
inheritorTodayCount: 0,
|
||||
userTotal: 0,
|
||||
userTodayCount: 0,
|
||||
eventTotal: 0,
|
||||
eventTodayCount: 0,
|
||||
newsTotal: 0,
|
||||
newsTodayCount: 0,
|
||||
commentTotal: 0,
|
||||
commentTodayCount: 0,
|
||||
commentPendingCount: 0,
|
||||
likeTotal: 0,
|
||||
likeTodayCount: 0,
|
||||
favoriteTotal: 0,
|
||||
favoriteTodayCount: 0,
|
||||
viewTotal: 0,
|
||||
viewTodayCount: 0,
|
||||
registrationTotal: 0,
|
||||
registrationTodayCount: 0,
|
||||
})
|
||||
|
||||
// 趋势图表
|
||||
const userTrendChartRef = ref<HTMLDivElement>()
|
||||
const contentTrendChartRef = ref<HTMLDivElement>()
|
||||
let userTrendChart: ECharts | null = null
|
||||
let contentTrendChart: ECharts | null = null
|
||||
|
||||
const userTrendDays = ref(7)
|
||||
const contentTrendDays = ref(7)
|
||||
|
||||
// 排行榜
|
||||
const heritageRankingType = ref('view')
|
||||
const heritageRanking = ref<RankingVo[]>([])
|
||||
const heritageRankingLoading = ref(false)
|
||||
|
||||
const activeUserRanking = ref<RankingVo[]>([])
|
||||
const activeUserRankingLoading = ref(false)
|
||||
|
||||
const rankingTypeOptions = [
|
||||
{ label: '浏览量', value: 'view' },
|
||||
{ label: '收藏量', value: 'favorite' },
|
||||
]
|
||||
|
||||
// 格式化数字
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 10000) {
|
||||
return `${(num / 10000).toFixed(1)}万`
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
// 获取排行榜数值标签
|
||||
function getRankingValueLabel(valueType: string): string {
|
||||
const labelMap: Record<string, string> = {
|
||||
view: '浏览量',
|
||||
favorite: '收藏量',
|
||||
activity: '活跃度',
|
||||
}
|
||||
return labelMap[valueType] || valueType
|
||||
}
|
||||
|
||||
// 加载核心统计数据
|
||||
async function loadStatistics() {
|
||||
try {
|
||||
const { data, isSuccess } = await getStatistics()
|
||||
if (isSuccess && data) {
|
||||
statistics.value = data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
coiMsgError('加载统计数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载用户增长趋势
|
||||
async function loadUserTrend() {
|
||||
try {
|
||||
const { data, isSuccess } = await getUserTrend(userTrendDays.value)
|
||||
if (isSuccess && data) {
|
||||
renderUserTrendChart(data)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
coiMsgError('加载用户增长趋势失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载内容发布趋势
|
||||
async function loadContentTrend() {
|
||||
try {
|
||||
const { data, isSuccess } = await getContentTrend(contentTrendDays.value)
|
||||
if (isSuccess && data) {
|
||||
renderContentTrendChart(data)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
coiMsgError('加载内容发布趋势失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 加载非遗项目排行榜
|
||||
async function loadHeritageRanking() {
|
||||
heritageRankingLoading.value = true
|
||||
try {
|
||||
const { data, isSuccess } = await getHeritageRanking(heritageRankingType.value, 10)
|
||||
if (isSuccess && data) {
|
||||
heritageRanking.value = data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
coiMsgError('加载非遗项目排行榜失败')
|
||||
}
|
||||
finally {
|
||||
heritageRankingLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载活跃用户排行榜
|
||||
async function loadActiveUserRanking() {
|
||||
activeUserRankingLoading.value = true
|
||||
try {
|
||||
const { data, isSuccess } = await getActiveUserRanking(10)
|
||||
if (isSuccess && data) {
|
||||
activeUserRanking.value = data
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
coiMsgError('加载活跃用户排行榜失败')
|
||||
}
|
||||
finally {
|
||||
activeUserRankingLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染用户增长趋势图表
|
||||
function renderUserTrendChart(data: TrendVo[]) {
|
||||
if (!userTrendChartRef.value)
|
||||
return
|
||||
|
||||
if (!userTrendChart) {
|
||||
userTrendChart = echarts.init(userTrendChartRef.value)
|
||||
}
|
||||
|
||||
const dates = data.map(item => item.date)
|
||||
const values = data.map(item => item.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(34, 197, 94, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(34, 197, 94, 0.05)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#22c55e',
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#22c55e',
|
||||
},
|
||||
data: values,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
userTrendChart.setOption(option)
|
||||
}
|
||||
|
||||
// 渲染内容发布趋势图表
|
||||
function renderContentTrendChart(data: TrendVo[]) {
|
||||
if (!contentTrendChartRef.value)
|
||||
return
|
||||
|
||||
if (!contentTrendChart) {
|
||||
contentTrendChart = echarts.init(contentTrendChartRef.value)
|
||||
}
|
||||
|
||||
const dates = data.map(item => item.date)
|
||||
const values = data.map(item => item.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
label: {
|
||||
backgroundColor: '#6a7985',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '发布内容',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(59, 130, 246, 0.05)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
color: '#3b82f6',
|
||||
width: 2,
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#3b82f6',
|
||||
},
|
||||
data: values,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
contentTrendChart.setOption(option)
|
||||
}
|
||||
|
||||
// 初始化页面数据
|
||||
onMounted(() => {
|
||||
loadStatistics()
|
||||
loadUserTrend()
|
||||
loadContentTrend()
|
||||
loadHeritageRanking()
|
||||
loadActiveUserRanking()
|
||||
|
||||
// 监听窗口大小变化,调整图表
|
||||
window.addEventListener('resize', () => {
|
||||
userTrendChart?.resize()
|
||||
contentTrendChart?.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.statistics-dashboard {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stat-card:hover .stat-icon {
|
||||
transform: scale(1.1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.chart-card,
|
||||
.ranking-card {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.ranking-item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ranking-cover img {
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
1240
src/views/heritage/users/index.vue
Normal file
1240
src/views/heritage/users/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user