StatsSection.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. <template>
  2. <section class="mb-stats-section">
  3. <!-- 顶部数据卡片 -->
  4. <div class="mb-stats-cards">
  5. <div :style="{ background: item.background }" class="mb-card" v-for="(item, index) in statsList" :key="index">
  6. <div class="mb-card-icon">
  7. <img width="24" :src="item.icon" :alt="item.label" />
  8. </div>
  9. <div class="mb-card-content">
  10. <div :style="{ background: item.color }" class="mb-card-bg"></div>
  11. <div class="mb-value">{{ formatNumber(item.value) }}</div>
  12. <div class="mb-label">{{ item.label }}</div>
  13. </div>
  14. </div>
  15. </div>
  16. <!-- 底部趋势图 -->
  17. <div class="mb-chart-section">
  18. <h3 class="mb-chart-title">24 小时攻击趋势图</h3>
  19. <div class="mb-chart-container">
  20. <client-only>
  21. <v-chart class="mb-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: 22844, icon: iconDDoS, color: '#7D46FF', background: `url('/images/home/card-bg1.png') no-repeat center` },
  64. { label: '今日 CC 攻击次数', value: 19009, icon: iconCC, color: '#6971FF', background: `url('/images/home/card-bg2.png') no-repeat center` },
  65. { label: '今日 WAF 拦截次数', value: 56870, icon: iconWAF, color: '#9466FF', background: `url('/images/home/card-bg1.png') no-repeat center` }
  66. ])
  67. // 启动自动增长
  68. // onMounted(() => {
  69. // statsStore.startAutoIncrement()
  70. // })
  71. // 生成 24 小时 Mock 数据
  72. const generateData = () => {
  73. const hours = []
  74. for (let i = 0; i < 24; i++) {
  75. hours.push(`${i.toString().padStart(2, '0')}:00`)
  76. }
  77. const seriesData1 = [] // DDoS
  78. const seriesData2 = [] // CC
  79. const seriesData3 = [] // WAF
  80. for (let i = 0; i < hours.length; i++) {
  81. // 使用正弦波 + 随机数模拟波浪效果
  82. seriesData1.push(Math.floor(30000 + Math.sin(i / 2) * 10000 + Math.random() * 5000))
  83. seriesData2.push(Math.floor(40000 + Math.sin(i / 2 + 1) * 15000 + Math.random() * 5000))
  84. seriesData3.push(Math.floor(20000 + Math.sin(i / 2 + 2) * 8000 + Math.random() * 5000))
  85. }
  86. return { hours, seriesData1, seriesData2, seriesData3 }
  87. }
  88. const { hours, seriesData1, seriesData2, seriesData3 } = generateData()
  89. // ECharts 配置
  90. const chartOption = computed(() => ({
  91. backgroundColor: 'transparent',
  92. tooltip: {
  93. trigger: 'axis',
  94. backgroundColor: 'transparent', // 必须设置为透明,否则会覆盖 extraCssText 的背景
  95. borderWidth: 0, // 移除默认边框,使用 CSS 处理
  96. padding: 0, // 移除内边距,完全由 CSS 控制
  97. 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;',
  98. textStyle: {
  99. color: '#fff',
  100. align: 'left'
  101. },
  102. axisPointer: {
  103. type: 'line',
  104. lineStyle: {
  105. color: '#ffffff',
  106. type: 'dashed',
  107. opacity: 0.5
  108. }
  109. }
  110. },
  111. grid: {
  112. left: '3%',
  113. right: '4%',
  114. bottom: '3%',
  115. containLabel: true
  116. },
  117. xAxis: {
  118. type: 'category',
  119. boundaryGap: false,
  120. data: hours,
  121. axisLine: {
  122. lineStyle: {
  123. color: 'rgba(255, 255, 255, 0.2)'
  124. }
  125. },
  126. axisLabel: {
  127. color: 'rgba(255, 255, 255, 0.5)',
  128. fontSize: 10,
  129. rotate: 45,
  130. interval: 1
  131. }
  132. },
  133. yAxis: {
  134. type: 'value',
  135. splitLine: {
  136. lineStyle: {
  137. color: 'rgba(255, 255, 255, 0.05)',
  138. type: 'dashed'
  139. }
  140. },
  141. axisLabel: {
  142. color: 'rgba(255, 255, 255, 0.5)'
  143. }
  144. },
  145. series: [
  146. {
  147. name: 'DDoS 攻击',
  148. type: 'line',
  149. smooth: true,
  150. showSymbol: false,
  151. lineStyle: {
  152. width: 3,
  153. color: '#7D46FF'
  154. },
  155. itemStyle: {
  156. color: '#7D46FF'
  157. },
  158. areaStyle: {
  159. opacity: 0.3,
  160. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  161. { offset: 0, color: 'rgba(125, 70, 255, 0.5)' },
  162. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  163. ])
  164. },
  165. data: seriesData1
  166. },
  167. {
  168. name: 'CC 攻击',
  169. type: 'line',
  170. smooth: true,
  171. showSymbol: false,
  172. lineStyle: {
  173. width: 3,
  174. color: '#4B54FF'
  175. },
  176. itemStyle: {
  177. color: '#4B54FF'
  178. },
  179. areaStyle: {
  180. opacity: 0.3,
  181. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  182. { offset: 0, color: 'rgba(75, 84, 255, 0.5)' },
  183. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  184. ])
  185. },
  186. data: seriesData2
  187. },
  188. {
  189. name: 'WAF 拦截',
  190. type: 'line',
  191. smooth: true,
  192. showSymbol: false,
  193. lineStyle: {
  194. width: 3,
  195. color: '#AE8CFF'
  196. },
  197. itemStyle: {
  198. color: '#AE8CFF'
  199. },
  200. areaStyle: {
  201. opacity: 0.3,
  202. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  203. { offset: 0, color: 'rgba(174, 140, 255, 0.5)' },
  204. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  205. ])
  206. },
  207. data: seriesData3
  208. }
  209. ]
  210. }))
  211. </script>
  212. <style scoped lang="scss">
  213. .mb-stats-section {
  214. width: 100%;
  215. margin: 30px auto;
  216. color: #fff;
  217. }
  218. .mb-stats-cards {
  219. display: flex;
  220. justify-content: space-between;
  221. box-sizing: border-box;
  222. gap: 10px;
  223. margin-bottom: 22px;
  224. .mb-card {
  225. flex: 1;
  226. position: relative;
  227. width: 108px;
  228. height: 62px;
  229. background: rgba(255, 255, 255, 0.03);
  230. border-radius: 6px;
  231. transition: transform 0.3s ease;
  232. backdrop-filter: blur(1px);
  233. background: linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%);
  234. .mb-card-bg {
  235. position: absolute;
  236. left: 50%;
  237. top: 0;
  238. transform: translate(-50%, -50%);
  239. width: 43px;
  240. height: 30px;
  241. filter: blur(8px);
  242. border-radius: 50%;
  243. }
  244. .mb-card-content {
  245. position: relative;
  246. z-index: 1;
  247. height: 100%;
  248. display: flex;
  249. flex-direction: column;
  250. align-items: center;
  251. justify-content: center;
  252. text-align: center;
  253. overflow: hidden;
  254. }
  255. .mb-card-icon {
  256. position: absolute;
  257. height: 24px;
  258. width: 24px;
  259. left: 50%;
  260. top: 0;
  261. transform: translate(-50%, -50%);
  262. display: flex;
  263. align-items: center;
  264. justify-content: center;
  265. z-index: 2;
  266. img {
  267. display: block;
  268. width: 100%;
  269. height: 100%;
  270. object-fit: contain;
  271. }
  272. }
  273. .mb-value {
  274. color: #FFF;
  275. font-size: 18px;
  276. font-weight: 700;
  277. }
  278. .mb-label {
  279. color: #C6BAFF;
  280. font-size: 9px;
  281. font-weight: 400;
  282. }
  283. }
  284. }
  285. .mb-chart-section {
  286. text-align: center;
  287. .mb-chart-title {
  288. color: #FFF;
  289. font-size: 14px;
  290. font-weight: 400;
  291. }
  292. .mb-chart-container {
  293. width: 100%;
  294. height: 220px;
  295. position: relative;
  296. .mb-chart {
  297. width: 100%;
  298. height: 100%;
  299. }
  300. }
  301. }
  302. </style>