|
|
@@ -1,158 +1,321 @@
|
|
|
<template>
|
|
|
- <div class="stats-container">
|
|
|
- <div class="stats-bg-wrapper">
|
|
|
- <img class="stats-bg" src="/images/home/home-bg.png" alt="首页背景" />
|
|
|
- <section class="stats-section">
|
|
|
- <NuxtImg width="154" class="stats-figure" src="/images/home/home-d.png" alt="游戏盾" />
|
|
|
- <div class="stats-row">
|
|
|
- <div class="stat-item">
|
|
|
- <div class="stat-label">今日攻击次数</div>
|
|
|
- <div class="stat-value">{{ formatNumber(stats.ccAttacks) }}</div>
|
|
|
- </div>
|
|
|
- <div class="stat-item">
|
|
|
- <div class="stat-label">今日拦截次数</div>
|
|
|
- <div class="stat-value">{{ formatNumber(stats.wafBlocks) }}</div>
|
|
|
- </div>
|
|
|
+ <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="76" :src="item.icon" :alt="item.label" />
|
|
|
</div>
|
|
|
- <div class="stat-item state-other">
|
|
|
- <div class="stat-label">今日DDoS攻击峰值</div>
|
|
|
- <div class="stat-value">{{ formatNumber(stats.ddosPeak) }}</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>
|
|
|
- </section>
|
|
|
+ </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 { onMounted, onUnmounted } from 'vue'
|
|
|
+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)
|
|
|
|
|
|
-let intervalId = null
|
|
|
+// 格式化数字
|
|
|
+const formatNumber = (num) => {
|
|
|
+ return num?.toLocaleString() || '0'
|
|
|
+}
|
|
|
+
|
|
|
+// 统计数据列表
|
|
|
+const statsList = computed(() => [
|
|
|
+ { label: '今日 DDoS 攻击峰值', value: stats.value.ddosPeak, icon: iconDDoS, color: '#7D46FF', background: 'linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%)' },
|
|
|
+ { label: '今日 CC 攻击次数', value: stats.value.ccAttacks, icon: iconCC, color: '#6971FF', background: 'linear-gradient(181deg, rgba(164, 169, 255, 0.60) -50.14%, rgba(56, 66, 255, 1.00) 81.35%)' },
|
|
|
+ { label: '今日 WAF 拦截次数', value: stats.value.wafBlocks, icon: iconWAF, color: '#9466FF', background: 'linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%)' }
|
|
|
+])
|
|
|
|
|
|
+// 启动自动增长
|
|
|
onMounted(() => {
|
|
|
- intervalId = statsStore.startAutoIncrement()
|
|
|
+ statsStore.startAutoIncrement()
|
|
|
})
|
|
|
|
|
|
-onUnmounted(() => {
|
|
|
- if (intervalId) {
|
|
|
- statsStore.stopAutoIncrement(intervalId)
|
|
|
- }
|
|
|
-})
|
|
|
+// 生成 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
|
|
|
|
|
|
-const formatNumber = (num) => {
|
|
|
- return num.toLocaleString()
|
|
|
+ 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: 'linear-gradient(181deg, rgba(101, 70, 255, 0.40) -42.03%, rgba(101, 70, 255, 0.10) 107.46%)',
|
|
|
+ borderColor: '#C6BAFF',
|
|
|
+ borderRadius: 24,
|
|
|
+ textStyle: {
|
|
|
+ color: '#fff'
|
|
|
+ },
|
|
|
+ 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-container {
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.stats-bg-wrapper {
|
|
|
- position: relative;
|
|
|
+.stats-section {
|
|
|
width: 100%;
|
|
|
- height: 824px;
|
|
|
-
|
|
|
- .stats-bg {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- width: 1400px;
|
|
|
- height: auto;
|
|
|
- }
|
|
|
+ max-width: 1200px;
|
|
|
+ margin: 60px auto;
|
|
|
+ padding: 0 20px;
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
|
|
|
-.stats-section {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
+.stats-cards {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- overflow: hidden;
|
|
|
-
|
|
|
- .stats-row {
|
|
|
- position: absolute;
|
|
|
- top: 40%;
|
|
|
- left: 50%;
|
|
|
- transform: translateX(-50%) translateY(-50%);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- z-index: 1;
|
|
|
- }
|
|
|
+ justify-content: space-between;
|
|
|
+ max-width: 1200px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ gap: 30px;
|
|
|
+ margin-bottom: 60px;
|
|
|
|
|
|
- .stats-figure {
|
|
|
- position: absolute;
|
|
|
- top: 40%;
|
|
|
- left: 50%;
|
|
|
- transform: translateX(-50%) translateY(-50%);
|
|
|
- object-fit: contain;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 0;
|
|
|
- will-change: transform;
|
|
|
- animation: float 3s ease-in-out infinite;
|
|
|
-
|
|
|
- :deep(img) {
|
|
|
- width: 180px;
|
|
|
- height: auto;
|
|
|
- }
|
|
|
- }
|
|
|
+ .card {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ width: 380px;
|
|
|
+ height: 180px;
|
|
|
+ background: rgba(255, 255, 255, 0.03);
|
|
|
+ border-radius: 20px;
|
|
|
+ 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%);
|
|
|
|
|
|
- @keyframes float {
|
|
|
- 0% {
|
|
|
- transform: translateX(-50%) translateY(-50%);
|
|
|
+ .card-bg {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 0;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 152px;
|
|
|
+ height: 106px;
|
|
|
+ // background: #9466FF; // 已通过内联样式动态绑定
|
|
|
+ filter: blur(25px);
|
|
|
+ border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
- 50% {
|
|
|
- transform: translateX(-50%) translateY(calc(-50% - 20px));
|
|
|
+ .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;
|
|
|
}
|
|
|
|
|
|
- 100% {
|
|
|
- transform: translateX(-50%) translateY(-50%);
|
|
|
+ .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;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- .state-other {
|
|
|
- position: absolute;
|
|
|
- top: 60px;
|
|
|
- }
|
|
|
|
|
|
- .stat-item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- margin: 0 300px;
|
|
|
- z-index: 1;
|
|
|
-
|
|
|
- .stat-label {
|
|
|
+ .value {
|
|
|
+ color: #FFF;
|
|
|
+ font-size: 60px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 60px;
|
|
|
+ }
|
|
|
|
|
|
+ .label {
|
|
|
+ margin-top: 20px;
|
|
|
+ color: #C6BAFF;
|
|
|
font-size: 24px;
|
|
|
font-weight: 400;
|
|
|
line-height: 24px;
|
|
|
- color: #ffffff;
|
|
|
- text-align: center;
|
|
|
- margin-bottom: 20px;
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- .stat-value {
|
|
|
+.chart-section {
|
|
|
+ text-align: center;
|
|
|
|
|
|
- font-size: 56px;
|
|
|
- font-weight: 700;
|
|
|
- line-height: 56px;
|
|
|
- color: #a182ff;
|
|
|
- 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%;
|
|
|
}
|
|
|
}
|
|
|
}
|