feat: 适配访问趋势图表卡片

This commit is contained in:
2024-04-16 20:13:48 +08:00
parent dd971f75bc
commit 2e2927a189
7 changed files with 287 additions and 208 deletions

9
src/apis/common/home.ts Normal file
View File

@@ -0,0 +1,9 @@
import http from '@/utils/http'
import type * as Common from './type'
const BASE_URL = '/dashboard'
/** @desc 查询访问趋势 */
export function listAccessTrend(days: number) {
return http.get<Common.DashboardAccessTrendResp[]>(`${BASE_URL}/access/trend/${days}`)
}

View File

@@ -1,2 +1,3 @@
export * from './common'
export * from './captcha'
export * from './home'

View File

@@ -3,3 +3,10 @@ export interface ImageCaptchaResp {
uuid: string
img: string
}
/** 仪表盘访问趋势 */
export interface DashboardAccessTrendResp {
date: string
pvCount: number
ipCount: number
}

View File

@@ -198,6 +198,55 @@
}
}
// echarts 提示
.echarts-tooltip-diy {
background: linear-gradient(
304.17deg,
rgba(253, 254, 255, 0.6) -6.04%,
rgba(244, 247, 252, 0.6) 85.2%
) !important;
border: none !important;
backdrop-filter: blur(10px) !important;
/* Note: backdrop-filter has minimal browser support */
border-radius: 6px !important;
.content-panel {
display: flex;
justify-content: space-between;
padding: 0 9px;
background: rgba(255, 255, 255, 0.8);
width: 164px;
height: 32px;
line-height: 32px;
box-shadow: 6px 0 20px rgba(34, 87, 188, 0.1);
border-radius: 4px;
margin-bottom: 4px;
}
.tooltip-title {
margin: 0 0 10px 0;
}
p {
margin: 0;
}
.tooltip-title,
.tooltip-value {
font-size: 13px;
line-height: 15px;
display: flex;
align-items: center;
text-align: right;
color: #1d2129;
font-weight: bold;
}
.tooltip-item-icon {
display: inline-block;
margin-right: 8px;
width: 10px;
height: 10px;
border-radius: 50%;
}
}
// 通用卡片
.general-card {
height: 100%;

View File

@@ -1,206 +0,0 @@
<template>
<a-card title="访问趋势" :bordered="false" class="gi_card_title">
<template #extra>
<a-radio-group type="button" size="small" default-value="1">
<a-radio value="1">近7天</a-radio>
<a-radio value="2">近30天</a-radio>
</a-radio-group>
</template>
<VCharts :option="option" autoresize :style="{ height: '289px' }"></VCharts>
</a-card>
</template>
<script lang="ts" setup>
import VCharts from 'vue-echarts'
import { graphic } from 'echarts'
import { useChart } from '@/hooks'
// import { ToolTipFormatterParams } from '@/types/echarts';
const xData = ref<string[]>([])
const yData = ref<number[]>([])
function graphicFactory(side: AnyObject) {
return {
type: 'text',
bottom: '8',
...side,
style: {
text: '',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12
}
}
}
const graphicElements = ref([graphicFactory({ left: '2.6%' }), graphicFactory({ right: 0 })])
const { option } = useChart(() => {
return {
grid: {
left: '40',
right: '0',
top: '10',
bottom: '30'
},
xAxis: {
type: 'category',
offset: 2,
data: xData.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value: number, idx: number) {
if (idx === 0) return ''
if (idx === xData.value.length - 1) return ''
return `${value}`
}
},
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: true,
interval: (idx: number) => {
if (idx === 0) return false
if (idx === xData.value.length - 1) return false
return true
},
lineStyle: {
color: '#E5E8EF'
}
},
axisPointer: {
show: true,
lineStyle: {
color: '#23ADFF',
width: 2
}
}
},
yAxis: {
type: 'value',
axisLine: {
show: false
},
axisLabel: {
formatter(value: any, idx: number) {
if (idx === 0) return value
return `${value}k`
}
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: '#E5E8EF'
}
}
},
tooltip: {
trigger: 'axis',
formatter(params) {
const [firstElement] = params as any[]
return `<div>
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
Number(firstElement.value) * 10000
).toLocaleString()}</span></div>
</div>`
},
className: 'echarts-tooltip-diy'
},
graphic: {
elements: graphicElements.value
},
series: [
{
name: '浏览量(PV)',
data: yData.value,
type: 'line',
smooth: true,
// symbol: 'circle',
symbolSize: 12,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2
}
},
lineStyle: {
width: 3,
color: new graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: 'rgba(30, 231, 255, 1)'
},
{
offset: 0.5,
color: 'rgba(36, 154, 255, 1)'
},
{
offset: 1,
color: 'rgba(111, 66, 251, 1)'
}
])
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(17, 126, 255, 0.16)'
},
{
offset: 1,
color: 'rgba(17, 128, 255, 0)'
}
])
}
},
{
name: 'IP数',
data: yData.value,
type: 'line',
smooth: true,
color: '#00B2FF',
symbolSize: 12,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
borderColor: '#E2F2FF'
}
}
}
]
}
})
const getChartData = () => {
try {
const values = [100, 200, 150, 30, 100, 50, 10, 80]
const year = new Date().getFullYear()
const data = values.map((i, n) => {
const m = n + 1
const month = m >= 10 ? m : `0${m}`
return { y: i, x: `${year}-${month}` }
})
data.forEach((item: any, index: number) => {
xData.value.push(item.x)
yData.value.push(item.y)
if (index === 0) {
graphicElements.value[0].style.text = item.x
}
if (index === data.length - 1) {
graphicElements.value[1].style.text = item.x
}
})
} catch (err) {
// you can report use errorHandler or other
}
}
getChartData()
</script>

View File

@@ -0,0 +1,219 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card title="访问趋势" :bordered="false" class="gi_card_title">
<template #extra>
<a-radio-group v-model:model-value="dateRange" type="button" size="small" @change="onChange">
<a-radio :value="7">近7天</a-radio>
<a-radio :value="30">近30天</a-radio>
</a-radio-group>
</template>
<VCharts :option="option" autoresize :style="{ height: '326px' }"></VCharts>
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import { listAccessTrend, type DashboardAccessTrendResp } from '@/apis'
import VCharts from 'vue-echarts'
import { graphic } from 'echarts'
import { useChart } from '@/hooks'
const xData = ref<string[]>([])
const pvStatisticsData = ref<number[]>([])
const ipStatisticsData = ref<number[]>([])
const { option } = useChart((isDark) => {
return {
grid: {
left: '38',
right: '0',
top: '10',
bottom: '50'
},
legend: {
bottom: -3,
icon: 'circle',
textStyle: {
color: '#4E5969'
}
},
xAxis: {
type: 'category',
offset: 2,
data: xData.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value: number, idx: number) {
if (idx === 0) return ''
if (idx === xData.value.length - 1) return ''
return `${value}`
}
},
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: true,
interval: (idx: number) => {
if (idx === 0) return false
return idx !== xData.value.length - 1
},
lineStyle: {
color: isDark ? '#3F3F3F' : '#E5E8EF'
}
},
axisPointer: {
show: true,
lineStyle: {
color: '#23ADFF',
width: 2
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter(value: any, idx: number) {
if (idx === 0) return value
if (value >= 1000) {
return `${value / 1000}k`
}
return `${value}`
}
},
axisLine: {
show: false
},
splitLine: {
lineStyle: {
type: 'dashed',
color: isDark ? '#3F3F3F' : '#E5E8EF'
}
}
},
tooltip: {
show: true,
trigger: 'axis',
formatter(params) {
const [firstElement] = params
return `<div>
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
${tooltipItemsHtmlString(params)}
</div>`
},
className: 'echarts-tooltip-diy'
},
series: [
{
name: '浏览量(PV)',
data: pvStatisticsData.value,
type: 'line',
smooth: true,
showSymbol: false,
color: '#246EFF',
symbol: 'circle',
symbolSize: 10,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
borderColor: '#E0E3FF'
}
},
areaStyle: {
opacity: 0.8,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(17, 126, 255, 0.16)'
},
{
offset: 1,
color: 'rgba(17, 128, 255, 0)'
}
])
}
},
{
name: 'IP数',
data: ipStatisticsData.value,
type: 'line',
smooth: true,
showSymbol: false,
color: '#00B2FF',
symbol: 'circle',
symbolSize: 10,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
borderColor: '#E2F2FF'
}
},
areaStyle: {
opacity: 0.8,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(17, 126, 255, 0.16)'
},
{
offset: 1,
color: 'rgba(17, 128, 255, 0)'
}
])
}
}
]
}
})
const loading = ref(false)
const dateRange = ref(30)
// 查询图表数据
const getChartData = async (days: number) => {
try {
loading.value = true
xData.value = []
pvStatisticsData.value = []
ipStatisticsData.value = []
const { data: chartData } = await listAccessTrend(days)
chartData.forEach((el: DashboardAccessTrendResp) => {
xData.value.unshift(el.date)
pvStatisticsData.value.unshift(el.pvCount)
ipStatisticsData.value.unshift(el.ipCount)
})
} finally {
loading.value = false
}
}
// 切换
const onChange = (days: number) => {
getChartData(days)
}
// 提示框
const tooltipItemsHtmlString = (items) => {
return items
.map(
(el) => `<div class="content-panel">
<p>
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
<span>${el.seriesName}</span>
</p>
<span class="tooltip-value">
${el.value}
</span>
</div>`
)
.join('')
}
onMounted(() => {
getChartData(30)
})
</script>

View File

@@ -5,7 +5,7 @@
<a-row class="home__content">
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="18" :xxl="20">
<div class="home__item"><ProjectCard /></div>
<div class="home__item"><AccessTrend /></div>
<div class="home__item"><AccessTrendCard /></div>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" :xxl="4">
<div class="home__item"><FastCard /></div>
@@ -23,7 +23,7 @@
<script setup lang="ts">
import WorkCard from './components/WorkCard.vue'
import ProjectCard from './components/ProjectCard.vue'
import AccessTrend from './components/AccessTrend.vue'
import AccessTrendCard from './components/AccessTrendCard.vue'
import FastCard from './components/FastCard.vue'
import MessageCard from './components/MessageCard.vue'
import Sponsor from './components/Sponsor.vue'