first commit
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="content-stats-container">
|
||||
<el-row :gutter="20" class="stat-cards">
|
||||
<el-col :span="6" v-for="card in topCards" :key="card.label">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="label">{{ card.label }}</div>
|
||||
<div class="count">{{ card.count.toLocaleString() }}</div>
|
||||
<div class="sub-info" v-if="card.yesterday !== undefined">
|
||||
昨日 {{ card.yesterday || 0 }}
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="main-charts">
|
||||
<el-col :span="16">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>内容发布活跃度</span>
|
||||
<el-radio-group v-model="timeRange" size="small">
|
||||
<el-radio-button label="week">近一周</el-radio-button>
|
||||
<el-radio-button label="month">近一月</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="barChartRef" class="chart-box"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" header="内容分类占比">
|
||||
<div ref="categoryChartRef" class="chart-box"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" class="bottom-row">
|
||||
<el-col :span="24">
|
||||
<el-card shadow="hover" header="热门内容TOP 5">
|
||||
<el-table :data="hotContent" style="width: 100%" stripe>
|
||||
<el-table-column type="index" label="排名" width="80" />
|
||||
<el-table-column prop="title" label="标题" show-overflow-tooltip />
|
||||
<el-table-column prop="cate" label="分类" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ row.cate || '未分类' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="views" label="阅读量" sortable />
|
||||
<el-table-column prop="likes" label="点赞数" width="120" />
|
||||
<el-table-column prop="publish_date" label="发布时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.publish_date ? row.publish_date.slice(0, 16).replace('T', ' ') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, shallowRef } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
import { getContentStats } from "@/api/analytics";
|
||||
|
||||
interface TopCard {
|
||||
label: string;
|
||||
count: number;
|
||||
yesterday?: number;
|
||||
}
|
||||
|
||||
interface HotArticle {
|
||||
id: number;
|
||||
title: string;
|
||||
cate: string;
|
||||
views: number;
|
||||
likes: number;
|
||||
publish_date: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
const timeRange = ref("week");
|
||||
const barChartRef = ref<HTMLElement | null>(null);
|
||||
const categoryChartRef = ref<HTMLElement | null>(null);
|
||||
const barChart = shallowRef<echarts.ECharts | null>(null);
|
||||
const categoryChart = shallowRef<echarts.ECharts | null>(null);
|
||||
|
||||
const topCards = ref<TopCard[]>([
|
||||
{ label: "总发布量", count: 0 },
|
||||
{ label: "本月新增", count: 0 },
|
||||
{ label: "总点赞量", count: 0 },
|
||||
{ label: "总访问量", count: 0 },
|
||||
]);
|
||||
|
||||
const hotContent = ref<HotArticle[]>([]);
|
||||
|
||||
async function fetchContentStats() {
|
||||
const res = await getContentStats();
|
||||
if (res.code === 200 && res.data) {
|
||||
const { total_articles, month_articles, total_likes, total_views, hot_articles } = res.data;
|
||||
|
||||
topCards.value = [
|
||||
{ label: "总发布量", count: total_articles || 0 },
|
||||
{ label: "本月新增", count: month_articles || 0 },
|
||||
{ label: "总点赞量", count: total_likes || 0 },
|
||||
{ label: "总访问量", count: total_views || 0 },
|
||||
];
|
||||
|
||||
hotContent.value = hot_articles || [];
|
||||
}
|
||||
}
|
||||
|
||||
// --- 图表初始化 ---
|
||||
const initCharts = () => {
|
||||
// 柱状图:发布趋势
|
||||
if (barChartRef.value) {
|
||||
barChart.value = echarts.init(barChartRef.value);
|
||||
barChart.value.setOption({
|
||||
tooltip: { trigger: "axis" },
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
||||
axisLine: { lineStyle: { color: "#DCDFE6" } },
|
||||
},
|
||||
yAxis: { type: "value" },
|
||||
series: [
|
||||
{
|
||||
name: "发布篇数",
|
||||
data: [45, 52, 38, 65, 48, 23, 31],
|
||||
type: "bar",
|
||||
barWidth: "40%",
|
||||
itemStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: "#83bff6" },
|
||||
{ offset: 0.5, color: "#188df0" },
|
||||
{ offset: 1, color: "#188df0" },
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 环形图:分类占比
|
||||
if (categoryChartRef.value) {
|
||||
categoryChart.value = echarts.init(categoryChartRef.value);
|
||||
categoryChart.value.setOption({
|
||||
tooltip: { trigger: "item" },
|
||||
series: [
|
||||
{
|
||||
type: "pie",
|
||||
radius: ["50%", "70%"],
|
||||
data: [
|
||||
{ value: 40, name: "技术文章" },
|
||||
{ value: 30, name: "行业资讯" },
|
||||
{ value: 20, name: "视频教程" },
|
||||
{ value: 10, name: "资源分享" },
|
||||
],
|
||||
emphasis: {
|
||||
label: { show: true, fontSize: "16", fontWeight: "bold" },
|
||||
},
|
||||
label: { show: false, position: "center" },
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
barChart.value?.resize();
|
||||
categoryChart.value?.resize();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchContentStats();
|
||||
initCharts();
|
||||
window.addEventListener("resize", handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.content-stats-container {
|
||||
min-height: 100vh;
|
||||
|
||||
.stat-cards {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
.label {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.count {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.sub-info {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
.plus {
|
||||
color: #52c41a;
|
||||
}
|
||||
.minus {
|
||||
color: #f5222d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-charts {
|
||||
margin-bottom: 24px;
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.chart-box {
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user