StatsSection.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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="24" :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: 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 = ['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: 'transparent', // 必须设置为透明,否则会覆盖 extraCssText 的背景
  92. borderWidth: 0, // 移除默认边框,使用 CSS 处理
  93. padding: 0, // 移除内边距,完全由 CSS 控制
  94. 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;',
  95. textStyle: {
  96. color: '#fff',
  97. align: 'left'
  98. },
  99. axisPointer: {
  100. type: 'line',
  101. lineStyle: {
  102. color: '#ffffff',
  103. type: 'dashed',
  104. opacity: 0.5
  105. }
  106. }
  107. },
  108. grid: {
  109. left: '3%',
  110. right: '4%',
  111. bottom: '3%',
  112. containLabel: true
  113. },
  114. xAxis: {
  115. type: 'category',
  116. boundaryGap: false,
  117. data: hours,
  118. axisLine: {
  119. lineStyle: {
  120. color: 'rgba(255, 255, 255, 0.2)'
  121. }
  122. },
  123. axisLabel: {
  124. color: 'rgba(255, 255, 255, 0.5)',
  125. fontSize: 12
  126. }
  127. },
  128. yAxis: {
  129. type: 'value',
  130. splitLine: {
  131. lineStyle: {
  132. color: 'rgba(255, 255, 255, 0.05)',
  133. type: 'dashed'
  134. }
  135. },
  136. axisLabel: {
  137. color: 'rgba(255, 255, 255, 0.5)'
  138. }
  139. },
  140. series: [
  141. {
  142. name: 'DDoS攻击',
  143. type: 'line',
  144. smooth: true,
  145. showSymbol: false,
  146. lineStyle: {
  147. width: 3,
  148. color: '#7D46FF'
  149. },
  150. itemStyle: {
  151. color: '#7D46FF'
  152. },
  153. areaStyle: {
  154. opacity: 0.3,
  155. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  156. { offset: 0, color: 'rgba(125, 70, 255, 0.5)' },
  157. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  158. ])
  159. },
  160. data: seriesData1
  161. },
  162. {
  163. name: 'CC攻击',
  164. type: 'line',
  165. smooth: true,
  166. showSymbol: false,
  167. lineStyle: {
  168. width: 3,
  169. color: '#4B54FF'
  170. },
  171. itemStyle: {
  172. color: '#4B54FF'
  173. },
  174. areaStyle: {
  175. opacity: 0.3,
  176. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  177. { offset: 0, color: 'rgba(75, 84, 255, 0.5)' },
  178. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  179. ])
  180. },
  181. data: seriesData2
  182. },
  183. {
  184. name: 'WAF拦截',
  185. type: 'line',
  186. smooth: true,
  187. showSymbol: false,
  188. lineStyle: {
  189. width: 3,
  190. color: '#AE8CFF'
  191. },
  192. itemStyle: {
  193. color: '#AE8CFF'
  194. },
  195. areaStyle: {
  196. opacity: 0.3,
  197. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  198. { offset: 0, color: 'rgba(174, 140, 255, 0.5)' },
  199. { offset: 1, color: 'rgba(3, 0, 20, 0)' }
  200. ])
  201. },
  202. data: seriesData3
  203. }
  204. ]
  205. }))
  206. </script>
  207. <style scoped lang="scss">
  208. .stats-section {
  209. width: 100%;
  210. margin: 30px auto;
  211. color: #fff;
  212. }
  213. .stats-cards {
  214. display: flex;
  215. justify-content: space-between;
  216. box-sizing: border-box;
  217. gap: 10px;
  218. margin-bottom: 60px;
  219. .card {
  220. flex: 1;
  221. position: relative;
  222. width: 108px;
  223. height: 62px;
  224. background: rgba(255, 255, 255, 0.03);
  225. border-radius: 6px;
  226. transition: transform 0.3s ease;
  227. backdrop-filter: blur(1px);
  228. background: linear-gradient(181deg, rgba(130, 77, 255, 0.60) -50.14%, rgba(164, 125, 255, 0.00) 81.35%);
  229. .card-bg {
  230. position: absolute;
  231. left: 50%;
  232. top: 0;
  233. transform: translate(-50%, -50%);
  234. width: 43px;
  235. height: 30px;
  236. filter: blur(8px);
  237. border-radius: 50%;
  238. }
  239. .card-content {
  240. position: relative;
  241. z-index: 1;
  242. height: 100%;
  243. display: flex;
  244. flex-direction: column;
  245. align-items: center;
  246. justify-content: center;
  247. text-align: center;
  248. overflow: hidden;
  249. }
  250. .card-icon {
  251. position: absolute;
  252. left: 50%;
  253. top: 0;
  254. transform: translate(-50%, -50%);
  255. // border-radius: 100px;
  256. // border: 1px solid rgba(64, 64, 64, 0.50);
  257. // background: rgba(255, 255, 255, 0.10);
  258. // 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);
  259. // backdrop-filter: blur(7.5px);
  260. z-index: 2;
  261. }
  262. .value {
  263. color: #FFF;
  264. font-size: 18px;
  265. font-weight: 700;
  266. }
  267. .label {
  268. color: #C6BAFF;
  269. font-size: 9px;
  270. font-weight: 400;
  271. }
  272. }
  273. }
  274. .chart-section {
  275. text-align: center;
  276. .chart-title {
  277. font-size: 30px;
  278. font-weight: 400;
  279. margin-bottom: 40px;
  280. letter-spacing: 1px;
  281. color: #FFF;
  282. }
  283. .chart-container {
  284. width: 100%;
  285. height: 400px;
  286. position: relative;
  287. .chart {
  288. width: 100%;
  289. height: 100%;
  290. }
  291. }
  292. }
  293. </style>