Compare commits
No commits in common. "9131148bc187f4f0e62601af332d01dac3b2f91d" and "83731771a1bab1e4b12d723fa3053a3b8c724372" have entirely different histories.
9131148bc1
...
83731771a1
@ -35,29 +35,24 @@ html, body, #app, .layout {
|
||||
border-right: 1px solid var(--el-border-color-light);
|
||||
|
||||
.el-menu {
|
||||
// CSS 变量覆盖
|
||||
--el-menu-bg-color: transparent;
|
||||
--el-menu-border-color: transparent;
|
||||
--el-menu-hover-bg-color: var(--el-fill-color);
|
||||
--el-menu-active-color: var(--el-color-primary);
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
// 圆角/间距/宽度无对应 CSS 变量,保留直接样式
|
||||
border-radius: var(--el-border-radius-base);
|
||||
margin: 2px 8px;
|
||||
width: calc(100% - 16px);
|
||||
|
||||
&.is-active {
|
||||
// 激活背景色无对应 CSS 变量,保留直接样式
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
font-weight: var(--el-font-weight-primary);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color);
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu__title:hover {
|
||||
// Element Plus 子菜单标题 hover 使用 --el-menu-bg-color 而非 --el-menu-hover-bg-color
|
||||
// 因此无法通过变量覆盖,保留直接样式
|
||||
background-color: var(--el-fill-color);
|
||||
}
|
||||
}
|
||||
@ -91,32 +86,11 @@ html, body, #app, .layout {
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 页面布局(用于带分页的表格页面)===== */
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ===== 表格美化 ===== */
|
||||
.table-container {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-table {
|
||||
// CSS 变量覆盖:表头背景/文字色、行悬停色、边框色、正文色
|
||||
--el-table-header-bg-color: var(--el-fill-color-light);
|
||||
--el-table-header-text-color: var(--el-text-color-regular);
|
||||
--el-table-row-hover-bg-color: var(--el-color-primary-light-9);
|
||||
--el-table-border-color: var(--el-border-color-lighter);
|
||||
--el-table-text-color: var(--el-text-color-primary);
|
||||
|
||||
// 固定高度,让表头固定,tbody滚动
|
||||
flex: 1;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
@ -124,18 +98,24 @@ html, body, #app, .layout {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th.el-table__cell {
|
||||
background: var(--el-fill-color-light);
|
||||
color: var(--el-text-color-regular);
|
||||
font-weight: var(--el-font-weight-primary);
|
||||
font-size: var(--el-font-size-small);
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
td.el-table__cell {
|
||||
// 行分隔线比表头边框更浅,与 --el-table-border-color 不同,需直接覆盖
|
||||
border-bottom-color: var(--el-fill-color);
|
||||
border-bottom: 1px solid var(--el-fill-color);
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: var(--el-font-size-small);
|
||||
}
|
||||
|
||||
.el-table__row:hover > td {
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
.el-table__row--striped > td {
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
.el-button.is-link {
|
||||
font-size: 16px;
|
||||
padding: 4px 6px;
|
||||
@ -165,12 +145,9 @@ html, body, #app, .layout {
|
||||
|
||||
/* ===== 表单弹窗 ===== */
|
||||
.el-dialog {
|
||||
// CSS 变量覆盖:圆角、清除 element-plus 2.x 新增的根元素 padding
|
||||
--el-dialog-border-radius: 12px;
|
||||
--el-dialog-padding-primary: 0;
|
||||
|
||||
// overflow:hidden 无对应 CSS 变量,保留直接样式(使圆角裁剪生效)
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
|
||||
.el-dialog__header {
|
||||
background: var(--el-fill-color-light);
|
||||
|
||||
@ -31,8 +31,7 @@ import menus from '../config/menu'
|
||||
<style lang="less" scoped>
|
||||
.welcome-page {
|
||||
padding: 32px;
|
||||
max-width: 1000px;
|
||||
box-sizing: border-box;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.welcome-header {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-form inline :model="search">
|
||||
<el-form-item label="内容">
|
||||
<el-input v-model="search.content" />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-form inline :model="search">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="search.name" />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-alert type="info" show-icon :closable="false" style="margin-bottom: 10px" >
|
||||
<template #title>上传要求</template>
|
||||
图片格式为{{allowUploadExt.join('、')}},文件大小不超过10MB。
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-alert type="info" show-icon :closable="false" >
|
||||
<template #title>上传要求</template>
|
||||
图片格式为{{allowUploadExt.join('、')}},文件大小不超过10MB。
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<div class="search-panel">
|
||||
<el-form inline :model="search">
|
||||
<el-form-item label="标题">
|
||||
@ -52,13 +52,13 @@
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-row style="flex: 1; overflow: hidden;">
|
||||
<el-col :span="4" style="height: 100%; overflow: auto;">
|
||||
<el-row>
|
||||
<el-col :span="4" style="height: 520px;overflow: auto;">
|
||||
<el-tree :props="treeProps" :load="loadTreeData" lazy highlight-current @node-click="articlePreview" />
|
||||
</el-col>
|
||||
<el-col :span="20" style="height: 100%; display: flex; flex-direction: column;">
|
||||
<div class="table-container" style="margin-bottom: 0;">
|
||||
<el-table :data="articleData" v-loading="loading" stripe @selection-change="dataSelect" height="100%">
|
||||
<el-col :span="20">
|
||||
<div class="table-container">
|
||||
<el-table :data="articleData" v-loading="loading" stripe @selection-change="dataSelect">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="title" label="标题" />
|
||||
<el-table-column prop="path" label="路径" >
|
||||
@ -91,7 +91,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="page-container" style="margin-top: 12px; padding: 0;">
|
||||
<div class="page-container">
|
||||
<el-pagination background
|
||||
:page-sizes="store.state.pageSizeOpts"
|
||||
:layout="store.state.pageLayout"
|
||||
|
||||
@ -1,91 +1,16 @@
|
||||
<template>
|
||||
<div class="statistics-page">
|
||||
<!-- 顶部统计卡片 -->
|
||||
<div class="stat-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">文章总数</div>
|
||||
<div class="stat-value">{{ totalArticles }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background-image: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">分类数量</div>
|
||||
<div class="stat-value">{{ totalCategories }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background-image: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">最早发布</div>
|
||||
<div class="stat-value">{{ earliestDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background-image: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-label">高频词汇</div>
|
||||
<div class="stat-value">{{ topKeyword }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="charts-grid">
|
||||
<!-- 文章分类 -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
<el-icon><PieChart /></el-icon>
|
||||
<span>文章分类分布</span>
|
||||
</div>
|
||||
<div class="chart-subtitle">各类别文章数量占比</div>
|
||||
</div>
|
||||
<div ref="categoriesChart" v-loading="categoriesChartLoading" class="chart-body"></div>
|
||||
</div>
|
||||
|
||||
<!-- 发布时间趋势 -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
<span>文章发布趋势</span>
|
||||
</div>
|
||||
<div class="chart-subtitle">各时间段文章发布数量变化</div>
|
||||
</div>
|
||||
<div ref="publishDatesChart" v-loading="publishDatesChartLoading" class="chart-body"></div>
|
||||
</div>
|
||||
|
||||
<!-- 年度高频词汇 -->
|
||||
<div class="chart-card chart-card-full">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">
|
||||
<el-icon><Histogram /></el-icon>
|
||||
<span>年度高频词汇分析</span>
|
||||
</div>
|
||||
<div class="chart-subtitle">基于文章分词结果的年度关键词统计</div>
|
||||
</div>
|
||||
<div ref="timelineWordsChart" v-loading="timelineWordsChartLoading" class="chart-body timeline-chart-body"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="echarts-container">
|
||||
<div ref="categoriesChart" v-loading="categoriesChartLoading"></div>
|
||||
<div ref="publishDatesChart" v-loading="publishDatesChartLoading"></div>
|
||||
<div class="timeline-chart" ref="timelineWordsChart" v-loading="timelineWordsChartLoading"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import http from '@/utils/http'
|
||||
import { Document, FolderOpened, Calendar, TrendCharts, PieChart, Histogram } from '@element-plus/icons-vue'
|
||||
|
||||
const categoriesChart = ref<HTMLElement>()
|
||||
const publishDatesChart = ref<HTMLElement>()
|
||||
@ -95,115 +20,70 @@ const categoriesChartLoading = ref(false)
|
||||
const publishDatesChartLoading = ref(false)
|
||||
const timelineWordsChartLoading = ref(false)
|
||||
|
||||
// 统计卡片数据
|
||||
const totalArticles = ref(0)
|
||||
const totalCategories = ref(0)
|
||||
const earliestDate = ref('-')
|
||||
const topKeyword = ref('-')
|
||||
|
||||
const categoriesChartOption: any = {
|
||||
title: {
|
||||
text: '文章分类',
|
||||
x: 'center',
|
||||
top: 10
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: "{b}: {c}篇 ({d}%)"
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 40,
|
||||
top: 50,
|
||||
bottom: 20,
|
||||
textStyle: {
|
||||
color: '#606266'
|
||||
}
|
||||
data: [],
|
||||
},
|
||||
series: {
|
||||
name: '类别',
|
||||
type: 'pie',
|
||||
radius: ['45%', '75%'],
|
||||
center: ['35%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
itemStyle: {
|
||||
borderRadius: 8,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
borderRadius: 5,
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 1
|
||||
},
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
const publishDatesChartOption: any = {
|
||||
title: {
|
||||
left: 'center',
|
||||
text: '文章发布时间',
|
||||
top: 10
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: '#e4e7ed',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#606266'
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: '#409eff',
|
||||
width: 2
|
||||
type: 'cross',
|
||||
animation: false,
|
||||
label: {
|
||||
backgroundColor: '#ccc',
|
||||
borderColor: '#aaa',
|
||||
borderWidth: 1,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 0,
|
||||
color: '#222'
|
||||
}
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
top: '10%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
name: '发布时间',
|
||||
nameTextStyle: {
|
||||
color: '#909399'
|
||||
},
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#dcdfe6'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#606266'
|
||||
}
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
name: '文章数量',
|
||||
nameTextStyle: {
|
||||
color: '#909399'
|
||||
},
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#606266'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#ebeef5'
|
||||
}
|
||||
max: function (value: {max: number}) {
|
||||
return value.max + 10
|
||||
}
|
||||
},
|
||||
dataZoom: [{
|
||||
@ -212,156 +92,69 @@ const publishDatesChartOption: any = {
|
||||
end: 100
|
||||
}, {
|
||||
start: 0,
|
||||
end: 100,
|
||||
height: 20,
|
||||
bottom: 10,
|
||||
handleSize: '100%',
|
||||
handleStyle: {
|
||||
color: '#409eff'
|
||||
}
|
||||
end: 10
|
||||
}],
|
||||
series: {
|
||||
name: '文章数量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
symbol: 'none',
|
||||
sampling: 'average',
|
||||
itemStyle: {
|
||||
color: '#409eff'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
shadowColor: 'rgba(64, 158, 255, 0.3)',
|
||||
shadowBlur: 10,
|
||||
shadowOffsetY: 5
|
||||
color: 'rgb(255, 70, 131)'
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(64, 158, 255, 0.4)'
|
||||
color: 'rgb(255, 158, 68)'
|
||||
}, {
|
||||
offset: 1,
|
||||
color: 'rgba(64, 158, 255, 0.05)'
|
||||
color: 'rgb(255, 70, 131)'
|
||||
}
|
||||
])
|
||||
},
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
const timelineWordsChartOption: any = {
|
||||
baseOption: {
|
||||
timeline: {
|
||||
axisType: 'category',
|
||||
autoPlay: false,
|
||||
playInterval: 1500,
|
||||
data: [],
|
||||
bottom: 10,
|
||||
left: 50,
|
||||
right: 50,
|
||||
lineStyle: {
|
||||
color: '#dcdfe6'
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#c0c4cc'
|
||||
},
|
||||
checkpointStyle: {
|
||||
color: '#409eff',
|
||||
borderColor: '#409eff'
|
||||
},
|
||||
controlStyle: {
|
||||
itemSize: 20,
|
||||
color: '#409eff',
|
||||
borderColor: '#409eff'
|
||||
},
|
||||
label: {
|
||||
color: '#606266'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
left: 'center',
|
||||
top: 10,
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
color: '#303133'
|
||||
}
|
||||
},
|
||||
calculable: true,
|
||||
grid: {
|
||||
top: 60,
|
||||
bottom: 80,
|
||||
left: 60,
|
||||
right: 40
|
||||
},
|
||||
xAxis: {
|
||||
name: '高频词汇',
|
||||
nameTextStyle: {
|
||||
color: '#909399'
|
||||
},
|
||||
type: 'category',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#dcdfe6'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#606266',
|
||||
interval: 0,
|
||||
rotate: 30
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
name: '出现次数',
|
||||
nameTextStyle: {
|
||||
color: '#909399'
|
||||
},
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#606266'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#ebeef5'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: '#e4e7ed',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#606266'
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
shadowStyle: {
|
||||
color: 'rgba(64, 158, 255, 0.1)'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: {
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
borderRadius: [6, 6, 0, 0],
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{ offset: 0, color: '#409eff' },
|
||||
{ offset: 1, color: '#67c23a' }
|
||||
])
|
||||
},
|
||||
barWidth: '50%'
|
||||
options: [],
|
||||
timeline: {
|
||||
axisType: 'category',
|
||||
autoPlay: false,
|
||||
playInterval: 1000,
|
||||
data: [],
|
||||
},
|
||||
title: {
|
||||
left: 'center',
|
||||
subtext: '数据来自文章分词结果'
|
||||
},
|
||||
calculable: true,
|
||||
grid: {
|
||||
top: 80,
|
||||
bottom: 80
|
||||
},
|
||||
xAxis: {
|
||||
name: '高频词汇',
|
||||
type: 'category',
|
||||
splitLine: {show: false}
|
||||
},
|
||||
yAxis: {
|
||||
name: '词汇出现次数',
|
||||
type: 'value'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
options: []
|
||||
series: {
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
borderRadius: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@ -369,196 +162,49 @@ onMounted(async () => {
|
||||
publishDatesChartLoading.value = true
|
||||
timelineWordsChartLoading.value = true
|
||||
|
||||
// 加载基础统计数据
|
||||
const articleData = await http.get<{params:{type:string}}, any>('/api/v1/article/statistics', {params: {type: 'normal'}})
|
||||
|
||||
// 更新统计卡片
|
||||
totalArticles.value = articleData.categories.reduce((sum: number, item: any) => sum + item.cnt, 0)
|
||||
totalCategories.value = articleData.categories.length
|
||||
|
||||
if (articleData.publishDates.length > 0) {
|
||||
earliestDate.value = articleData.publishDates[0]._id
|
||||
}
|
||||
|
||||
// 分类饼图
|
||||
categoriesChartOption.series.data = articleData.categories.map((item: any) => ({
|
||||
name: item._id,
|
||||
value: item.cnt
|
||||
}))
|
||||
const categoriesChartInstance = echarts.init(categoriesChart.value as HTMLElement)
|
||||
categoriesChartInstance.setOption(categoriesChartOption)
|
||||
categoriesChartLoading.value = false
|
||||
|
||||
// 发布趋势图
|
||||
categoriesChartOption.legend.data = articleData.categories.map((item: any) => item._id)
|
||||
categoriesChartOption.series.data = articleData.categories.map((item: any) => {
|
||||
return {name: item._id, value: item.cnt}
|
||||
})
|
||||
publishDatesChartOption.xAxis.data = articleData.publishDates.map((item: any) => item._id)
|
||||
publishDatesChartOption.series.data = articleData.publishDates.map((item: any) => item.cnt)
|
||||
|
||||
const categoriesChartInstance = echarts.init(categoriesChart.value as HTMLElement)
|
||||
categoriesChartInstance.setOption(categoriesChartOption)
|
||||
const publishDatesChartInstance = echarts.init(publishDatesChart.value as HTMLElement)
|
||||
publishDatesChartInstance.setOption(publishDatesChartOption)
|
||||
categoriesChartLoading.value = false
|
||||
publishDatesChartLoading.value = false
|
||||
|
||||
// 时间轴词汇统计
|
||||
const timelineData = await http.get<{params:{type:string}}, any>('/api/v1/article/statistics', {params: {type: 'timelineWords'}})
|
||||
|
||||
if (timelineData.timelineWords.length > 0 && timelineData.timelineWords[0].keys.length > 0) {
|
||||
topKeyword.value = timelineData.timelineWords[0].keys[0].key
|
||||
}
|
||||
|
||||
timelineWordsChartOption.baseOption.timeline.data = timelineData.timelineWords.map((item: any) => item._id)
|
||||
timelineWordsChartOption.options = timelineData.timelineWords.map((item: any) => ({
|
||||
title: {text: `${item._id}年发布的文章 - 高频词汇TOP10`},
|
||||
xAxis: {data: item.keys.map((keyItem: any) => keyItem.key)},
|
||||
series: {data: item.keys.map((keyItem: any) => keyItem.total)}
|
||||
}))
|
||||
timelineWordsChartOption.timeline.data = timelineData.timelineWords.map((item: any) => item._id)
|
||||
timelineData.timelineWords.forEach((item: any) => {
|
||||
timelineWordsChartOption.options.push({
|
||||
title: {text: `${item._id}年发布的文章`},
|
||||
xAxis: {data: item.keys.map((keyItem: any) => keyItem.key)},
|
||||
series: {data: item.keys.map((keyItem: any) => keyItem.total)}
|
||||
})
|
||||
})
|
||||
const timelineWordsChartInstance = echarts.init(timelineWordsChart.value as HTMLElement)
|
||||
timelineWordsChartInstance.setOption(timelineWordsChartOption)
|
||||
timelineWordsChartLoading.value = false
|
||||
|
||||
// 窗口大小变化时重绘
|
||||
window.addEventListener('resize', () => {
|
||||
categoriesChartInstance.resize()
|
||||
publishDatesChartInstance.resize()
|
||||
timelineWordsChartInstance.resize()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.statistics-page {
|
||||
padding: 20px;
|
||||
.echarts-container {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: 50% 50%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 顶部统计卡片
|
||||
.stat-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: var(--el-border-radius-large);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
> .echarts {
|
||||
border: 1px solid #ccc;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
// 图表区域
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: auto auto;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: var(--el-border-radius-large);
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-full {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 4px;
|
||||
|
||||
.el-icon {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.timeline-chart-body {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
// 响应式
|
||||
@media (max-width: 1200px) {
|
||||
.stat-cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.charts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-card-full {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stat-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.timeline-chart {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-form inline :model="search" @submit.prevent>
|
||||
<el-form-item label="角色名称/描述">
|
||||
<el-input v-model="search.name" />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div>
|
||||
<el-form inline :model="search" @submit.prevent>
|
||||
<el-form-item label="用户名/昵称">
|
||||
<el-input v-model="search.username" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user