feat: 新增首页中控台
This commit is contained in:
parent
9e50f95feb
commit
90dd5d0658
79
src/components/IndexComponents/DataVitals.vue
Normal file
79
src/components/IndexComponents/DataVitals.vue
Normal 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>
|
117
src/components/IndexComponents/TodayVisitTrend.vue
Normal file
117
src/components/IndexComponents/TodayVisitTrend.vue
Normal 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>
|
83
src/components/IndexComponents/TypeVitals.vue
Normal file
83
src/components/IndexComponents/TypeVitals.vue
Normal 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>
|
102
src/components/IndexComponents/YearVisit.vue
Normal file
102
src/components/IndexComponents/YearVisit.vue
Normal 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>
|
@ -1,147 +1,33 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const {proxy} = getCurrentInstance();
|
||||
|
||||
const showDialogBtnRef = ref()
|
||||
|
||||
const form = ref({})
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
import TodayVisitTrend from '@/components/IndexComponents/TodayVisitTrend.vue';
|
||||
import DataVitals from '@/components/IndexComponents/DataVitals.vue';
|
||||
import YearVisit from '@/components/IndexComponents/YearVisit.vue';
|
||||
import TypeVitals from '@/components/IndexComponents/TypeVitals.vue';
|
||||
</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">
|
||||
.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 scoped>
|
||||
/* :deep() .el-button{display: none;} */
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user