|
|
@@ -0,0 +1,317 @@
|
|
|
+<template>
|
|
|
+ <section class="stats-section">
|
|
|
+ <!-- 顶部数据卡片 -->
|
|
|
+ <div class="stats-cards">
|
|
|
+ <div :style="{ background: item.background }" class="card" v-for="(item, index) in statsList" :key="index">
|
|
|
+ <div class="card-icon">
|
|
|
+ <img width="24" :src="item.icon" :alt="item.label" />
|
|
|
+ </div>
|
|
|
+ <div class="card-content">
|
|
|
+ <div :style="{ background: item.color }" class="card-bg"></div>
|
|
|
+ <div class="value">{{ formatNumber(item.value) }}</div>
|
|
|
+ <div class="label">{{ item.label }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部趋势图 -->
|
|
|
+ <div class="chart-section">
|
|
|
+ <h3 class="chart-title">24小时攻击趋势图</h3>
|
|
|
+ <div class="chart-container">
|
|
|
+ <client-only>
|
|
|
+ <v-chart class="chart" :option="chartOption" autoresize />
|
|
|
+ </client-only>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, ref, onMounted } from 'vue'
|
|
|
+import { useStatsStore } from '~/stores/stats'
|
|
|
+import { storeToRefs } from 'pinia'
|
|
|
+import { use } from 'echarts/core'
|
|
|
+import { CanvasRenderer } from 'echarts/renderers'
|
|
|
+import { LineChart } from 'echarts/charts'
|
|
|
+import {
|
|
|
+ GridComponent,
|
|
|
+ TooltipComponent,
|
|
|
+ LegendComponent,
|
|
|
+ TitleComponent
|
|
|
+} from 'echarts/components'
|
|
|
+import VChart from 'vue-echarts'
|
|
|
+import * as echarts from 'echarts/core'
|
|
|
+
|
|
|
+// 注册 ECharts 组件
|
|
|
+use([
|
|
|
+ CanvasRenderer,
|
|
|
+ LineChart,
|
|
|
+ GridComponent,
|
|
|
+ TooltipComponent,
|
|
|
+ LegendComponent,
|
|
|
+ TitleComponent
|
|
|
+])
|
|
|
+
|
|
|
+// 图标资源 (使用 import 引入以确保 Vite 正确处理路径)
|
|
|
+import iconDDoS from '~/assets/svg/home/an1.svg'
|
|
|
+import iconCC from '~/assets/svg/home/an2.svg'
|
|
|
+import iconWAF from '~/assets/svg/home/an3.svg'
|
|
|
+
|
|
|
+// const statsStore = useStatsStore()
|
|
|
+// const { stats } = storeToRefs(statsStore)
|
|
|
+
|
|
|
+// 格式化数字
|
|
|
+const formatNumber = (num) => {
|
|
|
+ return num?.toLocaleString() || '0'
|
|
|
+}
|
|
|
+
|
|
|
+// 统计数据列表
|
|
|
+const statsList = computed(() => [
|
|
|
+ { label: '今日 DDoS 攻击峰值', value: 22844, icon: iconDDoS, color: '#7D46FF', background: `url('/images/home/card-bg1.png') no-repeat center` },
|
|
|
+ { label: '今日 CC 攻击次数', value: 19009, icon: iconCC, color: '#6971FF', background: `url('/images/home/card-bg2.png') no-repeat center` },
|
|
|
+ { label: '今日 WAF 拦截次数', value: 56870, icon: iconWAF, color: '#9466FF', background: `url('/images/home/card-bg1.png') no-repeat center` }
|
|
|
+])
|
|
|
+
|
|
|
+// 启动自动增长
|
|
|
+// onMounted(() => {
|
|
|
+// statsStore.startAutoIncrement()
|
|
|
+// })
|
|
|
+
|
|
|
+// 生成 24小时 Mock 数据
|
|
|
+const generateData = () => {
|
|
|
+ const hours = ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00']
|
|
|
+ const seriesData1 = [] // DDoS
|
|
|
+ const seriesData2 = [] // CC
|
|
|
+ const seriesData3 = [] // WAF
|
|
|
+
|
|
|
+ for (let i = 0; i < hours.length; i++) {
|
|
|
+ // 使用正弦波+随机数模拟波浪效果
|
|
|
+ seriesData1.push(Math.floor(30000 + Math.sin(i / 2) * 10000 + Math.random() * 5000))
|
|
|
+ seriesData2.push(Math.floor(40000 + Math.sin(i / 2 + 1) * 15000 + Math.random() * 5000))
|
|
|
+ seriesData3.push(Math.floor(20000 + Math.sin(i / 2 + 2) * 8000 + Math.random() * 5000))
|
|
|
+ }
|
|
|
+ return { hours, seriesData1, seriesData2, seriesData3 }
|
|
|
+}
|
|
|
+
|
|
|
+const { hours, seriesData1, seriesData2, seriesData3 } = generateData()
|
|
|
+
|
|
|
+// ECharts 配置
|
|
|
+const chartOption = computed(() => ({
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'transparent', // 必须设置为透明,否则会覆盖 extraCssText 的背景
|
|
|
+ borderWidth: 0, // 移除默认边框,使用 CSS 处理
|
|
|
+ padding: 0, // 移除内边距,完全由 CSS 控制
|
|
|
+ extraCssText: 'background: linear-gradient(180deg, rgba(101, 70, 255, 0.40) 0.33%, rgba(101, 70, 255, 0.10) 93.25%) !important; border: 1px solid #C6BAFF; border-radius: 24px; backdrop-filter: blur(4px); padding: 10px 16px; text-align: left;',
|
|
|
+ textStyle: {
|
|
|
+ color: '#fff',
|
|
|
+ align: 'left'
|
|
|
+ },
|
|
|
+ axisPointer: {
|
|
|
+ type: 'line',
|
|
|
+ lineStyle: {
|
|
|
+ color: '#ffffff',
|
|
|
+ type: 'dashed',
|
|
|
+ opacity: 0.5
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: '3%',
|
|
|
+ right: '4%',
|
|
|
+ bottom: '3%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ data: hours,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.2)'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.5)',
|
|
|
+ fontSize: 12
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.05)',
|
|
|
+ type: 'dashed'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.5)'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: 'DDoS攻击',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ color: '#7D46FF'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#7D46FF'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.3,
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(125, 70, 255, 0.5)' },
|
|
|
+ { offset: 1, color: 'rgba(3, 0, 20, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ data: seriesData1
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'CC攻击',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ color: '#4B54FF'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#4B54FF'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.3,
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(75, 84, 255, 0.5)' },
|
|
|
+ { offset: 1, color: 'rgba(3, 0, 20, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ data: seriesData2
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'WAF拦截',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ color: '#AE8CFF'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#AE8CFF'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ opacity: 0.3,
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(174, 140, 255, 0.5)' },
|
|
|
+ { offset: 1, color: 'rgba(3, 0, 20, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ data: seriesData3
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}))
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.stats-section {
|
|
|
+ width: 100%;
|
|
|
+ margin: 30px auto;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-cards {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ box-sizing: border-box;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 60px;
|
|
|
+
|
|
|
+ .card {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ width: 108px;
|
|
|
+ height: 62px;
|
|
|
+ background: rgba(255, 255, 255, 0.03);
|
|
|
+ border-radius: 6px;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ backdrop-filter: blur(1px);
|
|
|
+ background: linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%);
|
|
|
+
|
|
|
+ .card-bg {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 0;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 43px;
|
|
|
+ height: 30px;
|
|
|
+ filter: blur(8px);
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ text-align: center;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-icon {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 0;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ // border-radius: 100px;
|
|
|
+ // border: 1px solid rgba(64, 64, 64, 0.50);
|
|
|
+ // background: rgba(255, 255, 255, 0.10);
|
|
|
+ // box-shadow: -20px 68px 20px 0 rgba(0, 0, 0, 0.00), -13px 43px 18px 0 rgba(0, 0, 0, 0.01), -7px 24px 15px 0 rgba(0, 0, 0, 0.04), -3px 11px 11px 0 rgba(0, 0, 0, 0.07), -1px 3px 6px 0 rgba(0, 0, 0, 0.08);
|
|
|
+ // backdrop-filter: blur(7.5px);
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ color: #FFF;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+
|
|
|
+ .label {
|
|
|
+ color: #C6BAFF;
|
|
|
+ font-size: 9px;
|
|
|
+ font-weight: 400;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.chart-section {
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .chart-title {
|
|
|
+ font-size: 30px;
|
|
|
+ font-weight: 400;
|
|
|
+ margin-bottom: 40px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ color: #FFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 400px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .chart {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|