224 lines
6.4 KiB
Vue
224 lines
6.4 KiB
Vue
<template>
|
|
<div class="aksrt-page-container">
|
|
<PageHeader title="论坛概览" desc="从待审核主题、版主申请和版块状态三个视角观察论坛治理负载。" />
|
|
|
|
<div class="aksrt-forum-dashboard__stats">
|
|
<div v-for="card in statCards" :key="card.key" class="aksrt-forum-dashboard__stat-card">
|
|
<div class="aksrt-forum-dashboard__stat-icon" :style="{ background: card.color }">
|
|
<i :class="card.icon"></i>
|
|
</div>
|
|
<div class="aksrt-forum-dashboard__stat-info">
|
|
<div class="aksrt-forum-dashboard__stat-value">{{ stats[card.key] || 0 }}</div>
|
|
<div class="aksrt-forum-dashboard__stat-label">{{ card.label }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="aksrt-forum-dashboard__grid">
|
|
<n-card size="small" class="aksrt-forum-dashboard__section">
|
|
<template #header>
|
|
<div class="aksrt-forum-dashboard__section-head">
|
|
<strong>待审核主题</strong>
|
|
<n-button size="small" quaternary type="primary" @click="goTopics">进入主题治理</n-button>
|
|
</div>
|
|
</template>
|
|
|
|
<n-data-table
|
|
v-if="pendingTopics.length"
|
|
:columns="topicColumns"
|
|
:data="pendingTopics"
|
|
:bordered="false"
|
|
size="small"
|
|
/>
|
|
<n-empty v-else description="暂无待审核主题" />
|
|
</n-card>
|
|
|
|
<n-card size="small" class="aksrt-forum-dashboard__section">
|
|
<template #header>
|
|
<div class="aksrt-forum-dashboard__section-head">
|
|
<strong>待处理版主申请</strong>
|
|
<n-button size="small" quaternary type="primary" @click="goApplications">进入申请处理</n-button>
|
|
</div>
|
|
</template>
|
|
|
|
<n-data-table
|
|
v-if="pendingApplications.length"
|
|
:columns="applicationColumns"
|
|
:data="pendingApplications"
|
|
:bordered="false"
|
|
size="small"
|
|
/>
|
|
<n-empty v-else description="暂无待处理申请" />
|
|
</n-card>
|
|
</div>
|
|
|
|
<n-card size="small" class="aksrt-forum-dashboard__section">
|
|
<template #header>
|
|
<div class="aksrt-forum-dashboard__section-head">
|
|
<strong>版块概况</strong>
|
|
<n-button size="small" quaternary type="primary" @click="goBoards">进入版块管理</n-button>
|
|
</div>
|
|
</template>
|
|
|
|
<n-data-table
|
|
v-if="boards.length"
|
|
:columns="boardColumns"
|
|
:data="boards"
|
|
:bordered="false"
|
|
size="small"
|
|
/>
|
|
<n-empty v-else description="暂无版块" />
|
|
</n-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { h, onMounted, ref } from 'vue'
|
|
import { NButton, NTag, useMessage } from 'naive-ui'
|
|
import PageHeader from '@/components/PageHeader.vue'
|
|
import api from '@/api/index.js'
|
|
|
|
const message = useMessage()
|
|
const stats = ref({})
|
|
const boards = ref([])
|
|
const pendingTopics = ref([])
|
|
const pendingApplications = ref([])
|
|
|
|
const statCards = [
|
|
{ key: 'board_count', label: '版块数', icon: 'fas fa-th-large', color: '#dbeafe' },
|
|
{ key: 'topic_count', label: '公开主题', icon: 'fas fa-list-alt', color: '#dcfce7' },
|
|
{ key: 'pending_topic_count', label: '待审核主题', icon: 'fas fa-hourglass-half', color: '#fef3c7' },
|
|
{ key: 'pending_application_count', label: '待处理申请', icon: 'fas fa-user-shield', color: '#fee2e2' },
|
|
]
|
|
|
|
const topicColumns = [
|
|
{ title: '标题', key: 'title', minWidth: 260 },
|
|
{ title: '作者', key: 'author_name', width: 110, render: (row) => row.author?.display_name || '-' },
|
|
{ title: '版块', key: 'board_name', width: 120, render: (row) => row.boards?.[0]?.name || '-' },
|
|
{ title: '时间', key: 'date', width: 160 },
|
|
{
|
|
title: '操作',
|
|
key: 'actions',
|
|
width: 100,
|
|
render: (row) => h(NButton, { size: 'small', quaternary: true, type: 'info', onClick: () => goTopics() }, () => '治理'),
|
|
},
|
|
]
|
|
|
|
const applicationColumns = [
|
|
{ title: '版块', key: 'board_name', width: 140 },
|
|
{ title: '申请人', key: 'display_name', width: 140 },
|
|
{ title: '理由', key: 'reason', ellipsis: { tooltip: true } },
|
|
{ title: '时间', key: 'created_at', width: 160 },
|
|
{
|
|
title: '状态',
|
|
key: 'status',
|
|
width: 100,
|
|
render: (row) => h(NTag, { size: 'small', type: 'warning' }, () => row.status_label || row.status || '-'),
|
|
},
|
|
]
|
|
|
|
const boardColumns = [
|
|
{ title: '版块名称', key: 'name', minWidth: 180 },
|
|
{ title: '主题数', key: 'post_count', width: 90 },
|
|
{ title: '版主数', key: 'moderator_count', width: 90 },
|
|
{ title: '待处理申请', key: 'pending_application_count', width: 120 },
|
|
]
|
|
|
|
async function loadOverview() {
|
|
try {
|
|
const res = await api.request('/forum/overview')
|
|
const data = res?.data || {}
|
|
stats.value = data.stats || {}
|
|
boards.value = data.boards || []
|
|
pendingTopics.value = data.pending_topics || []
|
|
pendingApplications.value = data.pending_applications || []
|
|
} catch (error) {
|
|
message.error(error?.message || '加载论坛概览失败')
|
|
}
|
|
}
|
|
|
|
function goBoards() {
|
|
window.location.hash = '#/forum/boards'
|
|
}
|
|
|
|
function goTopics() {
|
|
window.location.hash = '#/forum/topics'
|
|
}
|
|
|
|
function goApplications() {
|
|
window.location.hash = '#/forum/moderator-applications'
|
|
}
|
|
|
|
onMounted(loadOverview)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.aksrt-forum-dashboard__stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__stat-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 18px;
|
|
border-radius: 12px;
|
|
background: #fff;
|
|
border: 1px solid #eef2f7;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__stat-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 10px;
|
|
color: #1d4ed8;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__stat-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__stat-value {
|
|
color: #111827;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__stat-label {
|
|
color: #6b7280;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__section {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.aksrt-forum-dashboard__section-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
@media (max-width: 1023px) {
|
|
.aksrt-forum-dashboard__grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|