feat: 新增首页中控台

This commit is contained in:
lonewolfyx 2025-01-13 14:34:44 +08:00
parent 9e50f95feb
commit 90dd5d0658
5 changed files with 403 additions and 136 deletions

View File

@ -0,0 +1,79 @@
<template>
<el-card shadow="never" class="data-vitals">
<div class="vitals">
<div class="vitals-item">
<div class="count"><span>{{ dataVitals.month_total }}</span></div>
<div class="title">本月来访</div>
</div>
<div class="vitals-item">
<div class="count"><span style="color:#FF9800;">{{ dataVitals.year_total }}</span></div>
<div class="title">总年度来访</div>
</div>
</div>
</el-card>
</template>
<script setup>
import {getVisitComprehensiveStatistics} from '@/api/RegistVisitApi/RegistVisitApi.js';
defineOptions({
name: 'DataVitals'
})
const dataVitals = ref({})
onMounted(() => {
getData()
})
const getData = async () => {
const res = await getVisitComprehensiveStatistics()
dataVitals.value = res.data
}
</script>
<style scoped lang="scss">
.data-vitals {
width: 100%;
height: 100%;
:deep(.el-card__body) {
height: 100%;
.vitals {
width: 100%;
height: 100%;
display: grid;
grid-template-rows: repeat(2, minmax(0, 1fr));
.vitals-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
.count {
font-size: 16px;
line-height: 24px;
font-weight: 700;
span {
color: #2196f3;
font-size: 36px;
line-height: 40px;
margin-right: 5px;
}
}
.title {
font-size: 16px;
line-height: 24px;
font-weight: 500;
letter-spacing: 2px;
}
}
}
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<el-card shadow="never" header="今日来访趋势">
<div class="TodayVisitTrend">
<Vechart :option="options"/>
</div>
</el-card>
</template>
<script setup>
import Vechart from 'vue-echarts'
import * as echarts from 'echarts/core';
import {
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from 'echarts/components';
import { LineChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
defineOptions({
name: 'TodayVisitTrend'
})
echarts.use([
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
CanvasRenderer,
UniversalTransition
]);
const times = Array.from({length: 24}, (_, i) => `${i}:00`)
const data = Array.from({length: 24}, (_, i) => Math.floor(Math.random() * 100))
const options = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
grid: {
top: '10%',
left: '3%',
right: '3%',
bottom: '10%',
},
xAxis: {
type: 'category',
data: times,
axisTick: {
show: false
},
axisLine: {
show: false
}
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Line 1',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#004564'
},
{
offset: 1,
color: '#00a0df'
}
])
},
emphasis: {
focus: 'series'
},
// name: '访',
data: data,
// type: 'line',
// smooth: true
// barWidth: '35%',
// itemStyle: {
// color: '#004564'
// }
}
]
}
</script>
<style scoped lang="scss">
.TodayVisitTrend {
position: relative;
overflow: hidden;
width: 100%;
height: 250px;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<el-card shadow="never" style="height: 100%" :body-style="{height:'100%'}">
<div class="TypeVitals">
<Vechart :option="options"/>
</div>
</el-card>
</template>
<script setup>
import Vechart from 'vue-echarts';
import * as echarts from 'echarts/core';
import {LegendComponent, TitleComponent, TooltipComponent} from 'echarts/components';
import {PieChart} from 'echarts/charts';
import {LabelLayout} from 'echarts/features';
import {CanvasRenderer} from 'echarts/renderers';
import {getVisitTypeStatistics} from '@/api/RegistVisitApi/RegistVisitApi.js';
echarts.use([
TitleComponent,
TooltipComponent,
LegendComponent,
PieChart,
CanvasRenderer,
LabelLayout
]);
defineOptions({
name: 'TypeVitals'
})
const options = ref({})
onMounted(() => {
getTypes()
})
// 访
const getTypes = async () => {
const res = await getVisitTypeStatistics()
const data = res.data.map(item => {
return {
...item,
value: item.count
}
})
setChartOptions(data)
}
const setChartOptions = (data) => {
options.value = {
color: ['#75dddd', '#508991', '#172a3a', '#004346', '#09bc8a', '#320d6d', '#ffbfb7', '#ffd447', '#700353'],
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '60%',
top: '20%',
data: data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
}
</script>
<style scoped lang="scss">
.TypeVitals {
position: relative;
overflow: hidden;
width: 100%;
height: 350px;
}
</style>

View File

@ -0,0 +1,102 @@
<template>
<el-card shadow="never" header="年度访问趋势">
<div class="VisitTrend">
<Vechart :option="options"/>
</div>
</el-card>
</template>
<script setup>
import Vechart from 'vue-echarts';
import {use} from 'echarts/core';
import {GridComponent, TooltipComponent} from 'echarts/components';
import {BarChart} from 'echarts/charts';
import {CanvasRenderer} from 'echarts/renderers';
import {getVisitTrendStatistics} from '@/api/RegistVisitApi/RegistVisitApi.js';
import dayjs from 'dayjs';
defineOptions({
name: 'YearVisit'
})
use([GridComponent, BarChart, CanvasRenderer, TooltipComponent]);
const options = ref({})
onMounted(() => {
getVisitTrend()
})
// 访
const getVisitTrend = async () => {
const res = await getVisitTrendStatistics(dayjs().format('YYYY'))
const year = res.data.map(item => `${item.month}`)
const data = res.data.map(item => {
return {
...item,
value: item.count,
name: item.month
}
})
setChartOptions(year, data)
}
const setChartOptions = (year, data) => {
options.value = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
grid: {
top: '10%',
left: '3%',
right: '3%',
bottom: '10%',
},
xAxis: {
type: 'category',
data: year,
axisTick: {
show: false
},
axisLine: {
show: false
},
axisPointer: {
type: 'shadow'
},
// axisLabel: {interval: 0, rotate: 30}
},
yAxis: {
type: 'value'
},
series: [
{
// name: 'Direct',
// type: 'bar',
// barWidth: '60%',
data: data,
type: 'bar',
barWidth: '35%',
itemStyle: {
color: '#007fb4'
}
}
]
}
}
</script>
<style scoped lang="scss">
.VisitTrend {
position: relative;
overflow: hidden;
width: 100%;
height: 350px;
}
</style>

View File

@ -1,147 +1,33 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-row :gutter="24" class="mb-2">
<el-col :span="5">
<DataVitals/>
</el-col>
<el-col :span="19">
<TodayVisitTrend/>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="16">
<YearVisit/>
</el-col>
<el-col :span="8">
<TypeVitals/>
</el-col>
</el-row>
</div> </div>
</template> </template>
<script setup> <script setup>
import TodayVisitTrend from '@/components/IndexComponents/TodayVisitTrend.vue';
const {proxy} = getCurrentInstance(); import DataVitals from '@/components/IndexComponents/DataVitals.vue';
import YearVisit from '@/components/IndexComponents/YearVisit.vue';
const showDialogBtnRef = ref() import TypeVitals from '@/components/IndexComponents/TypeVitals.vue';
const form = ref({})
onMounted(() => {
})
</script> </script>
<style lang="scss">
.app-container {
.statistics-section {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
display: grid;
grid-template-rows:1fr 1fr 1fr;
gap: var(--gap);
.statistics-section-item {
position: relative;
width: 100%;
//background: #004d8c;
height: calc((var(--container-height) * 1 / 3) - var(--gap) * 2);
--el-card-border-color: var(--el-border-color-light);
--el-card-border-radius: 4px;
--el-card-padding: 20px;
--el-card-bg-color: var(--el-fill-color-blank);
background-color: var(--el-card-bg-color);
border: 1px solid var(--el-card-border-color);
border-radius: var(--el-card-border-radius);
color: var(--el-text-color-primary);
overflow: hidden;
transition: var(--el-transition-duration);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.12);
}
}
.statistics-region {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
display: grid;
grid-template-rows:0.5fr 3fr 1fr;
gap: var(--gap);
.statistics-region-one {
position: relative;
width: 100%;
//height: calc(var(--container-height) * 0.65 / 4.5);
--el-card-border-color: var(--el-border-color-light);
--el-card-border-radius: 4px;
--el-card-padding: 20px;
--el-card-bg-color: var(--el-fill-color-blank);
background-color: var(--el-card-bg-color);
border: 1px solid var(--el-card-border-color);
border-radius: var(--el-card-border-radius);
color: var(--el-text-color-primary);
overflow: hidden;
transition: var(--el-transition-duration);
padding: 0 12px;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.12);
display: flex;
align-items: center;
justify-content: center;
}
.statistics-region-two {
position: relative;
width: 100%;
//height: calc(var(--container-height) * 2.5 / 4.5);
--el-card-border-color: var(--el-border-color-light);
--el-card-border-radius: 4px;
--el-card-padding: 20px;
--el-card-bg-color: var(--el-fill-color-blank);
background-color: var(--el-card-bg-color);
border: 1px solid var(--el-card-border-color);
border-radius: var(--el-card-border-radius);
color: var(--el-text-color-primary);
overflow: hidden;
transition: var(--el-transition-duration);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.12);
}
.statistics-region-three {
position: relative;
width: 100%;
//height: calc(var(--container-height) * 1 / 4.5);
--el-card-border-color: var(--el-border-color-light);
--el-card-border-radius: 4px;
--el-card-padding: 20px;
--el-card-bg-color: var(--el-fill-color-blank);
background-color: var(--el-card-bg-color);
border: 1px solid var(--el-card-border-color);
border-radius: var(--el-card-border-radius);
color: var(--el-text-color-primary);
overflow: hidden;
transition: var(--el-transition-duration);
padding: 12px;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.12);
}
}
.statistics-info {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
overflow-y: scroll;
}
}
.el-descriptions__content:not(.is-bordered-label) {
font-weight: bolder;
}
</style>
<style scoped lang="scss"> <style scoped lang="scss">
.app-container {
--gap: 10px;
--container-height: calc(100vh - 114px);
position: relative;
overflow: hidden;
width: 100%;
height: var(--container-height);
display: grid;
grid-template-columns: 1.8fr 1fr 1fr;
//grid-template-columns: 1fr;
gap: var(--gap);
padding: 20px !important;
}
</style> </style>
<style scoped>
/* :deep() .el-button{display: none;} */
</style>