StatsSection.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <template>
  2. <section class="stats-section">
  3. <!-- 顶部数据卡片 -->
  4. <div class="stats-cards">
  5. <div :style="{ background: item.background }" class="card" v-for="(item, index) in statsList" :key="index">
  6. <div class="card-icon">
  7. <img width="76" :src="item.icon" :alt="item.label" />
  8. </div>
  9. <div class="card-content">
  10. <div :style="{ background: item.color }" class="card-bg"></div>
  11. <div class="value">{{ formatNumber(item.value) }}</div>
  12. <div class="label">{{ item.label }}</div>
  13. </div>
  14. </div>
  15. </div>
  16. <!-- 底部趋势图 -->
  17. <div class="chart-section">
  18. <h3 class="chart-title">24小时攻击趋势图</h3>
  19. <div class="chart-container">
  20. <client-only>
  21. <v-chart class="chart" :option="chartOption" autoresize />
  22. </client-only>
  23. </div>
  24. </div>
  25. </section>
  26. </template>
  27. <script setup>
  28. import { computed, ref, onMounted } from 'vue'
  29. import { useStatsStore } from '~/stores/stats'
  30. import { storeToRefs } from 'pinia'
  31. import { use } from 'echarts/core'
  32. import { CanvasRenderer } from 'echarts/renderers'
  33. import { LineChart } from 'echarts/charts'
  34. import {
  35. GridComponent,
  36. TooltipComponent,
  37. LegendComponent,
  38. TitleComponent
  39. } from 'echarts/components'
  40. import VChart from 'vue-echarts'
  41. import * as echarts from 'echarts/core'
  42. // 注册 ECharts 组件
  43. use([
  44. CanvasRenderer,
  45. LineChart,
  46. GridComponent,
  47. TooltipComponent,
  48. LegendComponent,
  49. TitleComponent
  50. ])
  51. // 图标资源 (使用 import 引入以确保 Vite 正确处理路径)
  52. import iconDDoS from '~/assets/svg/home/an1.svg'
  53. import iconCC from '~/assets/svg/home/an2.svg'
  54. import iconWAF from '~/assets/svg/home/an3.svg'
  55. const statsStore = useStatsStore()
  56. const { stats } = storeToRefs(statsStore)
  57. // 格式化数字
  58. const formatNumber = (num) => {
  59. return num?.toLocaleString() || '0'
  60. }
  61. // 统计数据列表
  62. const statsList = computed(() => [
  63. { 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%)' },
  64. { 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%)' },
  65. { 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%)' }
  66. ])
  67. // 启动自动增长
  68. onMounted(() => {
  69. statsStore.startAutoIncrement()
  70. })
  71. // 生成 24小时 Mock 数据
  72. const generateData = () => {
  73. 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']
  74. const seriesData1 = [] // DDoS
  75. const seriesData2 = [] // CC
  76. const seriesData3 = [] // WAF
  77. for (let i = 0; i < hours.length; i++) {
  78. // 使用正弦波+随机数模拟波浪效果
  79. seriesData1.push(Math.floor(30000 + Math.sin(i / 2) * 10000 + Math.random() * 5000))
  80. seriesData2.push(Math.floor(40000 + Math.sin(i / 2 + 1) * 15000 + Math.random() * 5000))
  81. seriesData3.push(Math.floor(20000 + Math.sin(i / 2 + 2) * 8000 + Math.random() * 5000))
  82. }
  83. return { hours, seriesData1, seriesData2, seriesData3 }
  84. }
  85. const { hours, seriesData1, seriesData2, seriesData3 } = generateData()
  86. // ECharts 配置
  87. const chartOption = computed(() => ({
  88. backgroundColor: 'transparent',
  89. tooltip: {
  90. trigger: 'axis',
  91. backgroundColor: 'linear-gradient(181deg, rgba(101, 70, 255, 0.40) -42.03%, rgba(101, 70, 255, 0.10) 107.46%)',
  92. borderColor: '#C6BAFF',
  93. borderRadius: 24,
  94. textStyle: {
  95. color: '#fff'
  96. },
  97. axisPointer: {
  98. type: 'line',
  99. lineStyle: {
  100. color: '#ffffff',
  101. type: 'dashed',
  102. opacity: 0.5
  103. }
  104. }
  105. },
  106. grid: {
  107. left: '3%',
  108. right: '4%',
  109. bottom: '3%',
  110. containLabel: true
  111. },
  112. xAxis: {
  113. type: 'category',
  114. boundaryGap: false,
  115. data: hours,
  116. axisLine: {
  117. lineStyle: {
  118. color: 'rgba(255, 255, 255, 0.2)'
  119. }
  120. },
  121. axisLabel: {
  122. color: 'rgba(255, 255, 255, 0.5)',
  123. fontSize: 12
  124. }
  125. },
  126. yAxis: {
  127. type: 'value',
  128. splitLine: {
  129. lineStyle: {
  130. color: 'rgba(255, 255, 255, 0.05)',
  131. type: 'dashed'
  132. }
  133. },
  134. axisLabel: {
  135. color: 'rgba(255, 255, 255, 0.5)'
  136. }
  137. },
  138. series: [
  139. {
  140. name: 'DDoS攻击',
  141. type: 'line',
  142. smooth: true,
  143. showSymbol: false,
  144. lineStyle: {
  145. width: 3,
  146. color: '#7D46FF'
  147. },
  148. itemStyle: {
  149. color: '#7D46FF'
  150. },
  151. areaStyle: {
  152. opacity: 0.3,
  153. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  154. { offset: 0, color: 'rgba(125, 70, 255, 0.5)' },
  155. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  156. ])
  157. },
  158. data: seriesData1
  159. },
  160. {
  161. name: 'CC攻击',
  162. type: 'line',
  163. smooth: true,
  164. showSymbol: false,
  165. lineStyle: {
  166. width: 3,
  167. color: '#4B54FF'
  168. },
  169. itemStyle: {
  170. color: '#4B54FF'
  171. },
  172. areaStyle: {
  173. opacity: 0.3,
  174. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  175. { offset: 0, color: 'rgba(75, 84, 255, 0.5)' },
  176. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  177. ])
  178. },
  179. data: seriesData2
  180. },
  181. {
  182. name: 'WAF拦截',
  183. type: 'line',
  184. smooth: true,
  185. showSymbol: false,
  186. lineStyle: {
  187. width: 3,
  188. color: '#AE8CFF'
  189. },
  190. itemStyle: {
  191. color: '#AE8CFF'
  192. },
  193. areaStyle: {
  194. opacity: 0.3,
  195. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  196. { offset: 0, color: 'rgba(174, 140, 255, 0.5)' },
  197. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  198. ])
  199. },
  200. data: seriesData3
  201. }
  202. ]
  203. }))
  204. </script>
  205. <style scoped lang="scss">
  206. .stats-section {
  207. width: 100%;
  208. max-width: 1200px;
  209. margin: 60px auto;
  210. padding: 0 20px;
  211. color: #fff;
  212. }
  213. .stats-cards {
  214. display: flex;
  215. justify-content: space-between;
  216. max-width: 1200px;
  217. box-sizing: border-box;
  218. gap: 30px;
  219. margin-bottom: 60px;
  220. .card {
  221. flex: 1;
  222. position: relative;
  223. width: 380px;
  224. height: 180px;
  225. background: rgba(255, 255, 255, 0.03);
  226. border-radius: 20px;
  227. transition: transform 0.3s ease;
  228. backdrop-filter: blur(1px);
  229. background: linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%);
  230. .card-bg {
  231. position: absolute;
  232. left: 50%;
  233. top: 0;
  234. transform: translate(-50%, -50%);
  235. width: 152px;
  236. height: 106px;
  237. // background: #9466FF; // 已通过内联样式动态绑定
  238. filter: blur(25px);
  239. border-radius: 50%;
  240. }
  241. .card-content {
  242. position: relative;
  243. z-index: 1;
  244. height: 100%;
  245. display: flex;
  246. flex-direction: column;
  247. align-items: center;
  248. justify-content: center;
  249. text-align: center;
  250. overflow: hidden;
  251. }
  252. .card-icon {
  253. position: absolute;
  254. left: 50%;
  255. top: 0;
  256. transform: translate(-50%, -50%);
  257. border-radius: 100px;
  258. border: 1px solid rgba(64, 64, 64, 0.50);
  259. background: rgba(255, 255, 255, 0.10);
  260. 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);
  261. backdrop-filter: blur(7.5px);
  262. z-index: 2;
  263. }
  264. .value {
  265. color: #FFF;
  266. font-size: 60px;
  267. font-weight: 700;
  268. line-height: 60px;
  269. }
  270. .label {
  271. margin-top: 20px;
  272. color: #C6BAFF;
  273. font-size: 24px;
  274. font-weight: 400;
  275. line-height: 24px;
  276. }
  277. }
  278. }
  279. .chart-section {
  280. text-align: center;
  281. .chart-title {
  282. font-size: 30px;
  283. font-weight: 400;
  284. margin-bottom: 40px;
  285. letter-spacing: 1px;
  286. color: #FFF;
  287. }
  288. .chart-container {
  289. width: 100%;
  290. height: 400px;
  291. position: relative;
  292. .chart {
  293. width: 100%;
  294. height: 100%;
  295. }
  296. }
  297. }
  298. </style>