Bläddra i källkod

feat(mobile): 新增安全洞见统计组件并优化流程图样式

- 添加 StatsSection 组件展示 DDoS/CC/WAF 攻击数据和24小时趋势图
- 在移动端首页添加安全洞见标题并引入统计组件
- 优化 Flowchart 组件样式:移除固定高度、调整间距和注释
- 为流程图箭头 SVG 添加流动光效动画
reaper 1 månad sedan
förälder
incheckning
8cd4e8b412

+ 41 - 0
app/assets/svg/home/arrow5.svg

@@ -1,7 +1,48 @@
 <svg width="15" height="61" viewBox="0 0 15 61" fill="none" xmlns="http://www.w3.org/2000/svg">
+<style>
+  .flowCore,
+  .flowGlow {
+    stroke-linecap: round;
+    stroke-linejoin: round;
+  }
+
+  .flowCore {
+    stroke: rgba(255, 255, 255, 0.9);
+  }
+
+  .flowGlow {
+    stroke: rgba(150, 215, 255, 0.32);
+    filter: url(#flowBlur);
+  }
+
+  @media (prefers-reduced-motion: reduce) {
+    .flowGroup {
+      display: none;
+    }
+  }
+</style>
 <path d="M7.17773 0V58.4254" stroke="url(#paint0_linear_1096_3535)" stroke-width="2.87077"/>
+<g class="flowGroup" mask="url(#flowMaskY)">
+<path class="flowGlow" d="M7.17773 0V58.4254" stroke-width="5"/>
+<path class="flowCore" d="M7.17773 0V58.4254" stroke-width="3"/>
+</g>
 <path d="M7.03339 60.1484L13.1245 54.3351H0.942295L7.03339 60.1484Z" fill="#B6B4FF"/>
 <defs>
+<linearGradient id="flowSweepY" x1="0" y1="0" x2="0" y2="1">
+<stop offset="0" stop-color="white" stop-opacity="0"/>
+<stop offset="0.42" stop-color="white" stop-opacity="0"/>
+<stop offset="0.5" stop-color="white" stop-opacity="1"/>
+<stop offset="0.58" stop-color="white" stop-opacity="0"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+<mask id="flowMaskY" maskUnits="userSpaceOnUse">
+<rect x="-30" y="-120" width="109" height="120" fill="url(#flowSweepY)">
+<animate attributeName="y" from="-120" to="61" dur="1.2s" repeatCount="indefinite"/>
+</rect>
+</mask>
+<filter id="flowBlur" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
+<feGaussianBlur stdDeviation="2"/>
+</filter>
 <linearGradient id="paint0_linear_1096_3535" x1="7.74133" y1="10.5382" x2="-14.6073" y2="58.9002" gradientUnits="userSpaceOnUse">
 <stop stop-color="#564EFF"/>
 <stop offset="1" stop-color="#D2D0FF"/>

+ 41 - 0
app/assets/svg/home/arrow6.svg

@@ -1,7 +1,48 @@
 <svg width="15" height="197" viewBox="0 0 15 197" fill="none" xmlns="http://www.w3.org/2000/svg">
+<style>
+  .flowCore,
+  .flowGlow {
+    stroke-linecap: round;
+    stroke-linejoin: round;
+  }
+
+  .flowCore {
+    stroke: rgba(255, 255, 255, 0.9);
+  }
+
+  .flowGlow {
+    stroke: rgba(150, 215, 255, 0.32);
+    filter: url(#flowBlur);
+  }
+
+  @media (prefers-reduced-motion: reduce) {
+    .flowGroup {
+      display: none;
+    }
+  }
+</style>
 <path d="M6.7168 0V193.395" stroke="url(#paint0_linear_1096_3541)" stroke-width="2.87077"/>
+<g class="flowGroup" mask="url(#flowMaskY)">
+<path class="flowGlow" d="M6.7168 0V193.395" stroke-width="5"/>
+<path class="flowCore" d="M6.7168 0V193.395" stroke-width="3"/>
+</g>
 <path d="M7.03339 196.645L13.1245 190.831H0.942295L7.03339 196.645Z" fill="#B6B4FF"/>
 <defs>
+<linearGradient id="flowSweepY" x1="0" y1="0" x2="0" y2="1">
+<stop offset="0" stop-color="white" stop-opacity="0"/>
+<stop offset="0.42" stop-color="white" stop-opacity="0"/>
+<stop offset="0.5" stop-color="white" stop-opacity="1"/>
+<stop offset="0.58" stop-color="white" stop-opacity="0"/>
+<stop offset="1" stop-color="white" stop-opacity="0"/>
+</linearGradient>
+<mask id="flowMaskY" maskUnits="userSpaceOnUse">
+<rect x="-30" y="-300" width="109" height="300" fill="url(#flowSweepY)">
+<animate attributeName="y" from="-300" to="197" dur="1.2s" repeatCount="indefinite"/>
+</rect>
+</mask>
+<filter id="flowBlur" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
+<feGaussianBlur stdDeviation="2"/>
+</filter>
 <linearGradient id="paint0_linear_1096_3541" x1="7.28039" y1="12.3803" x2="-15.0684" y2="60.7424" gradientUnits="userSpaceOnUse">
 <stop stop-color="#564EFF"/>
 <stop offset="1" stop-color="#D2D0FF"/>

+ 1 - 5
app/components/mobile/home/Flowchart.vue

@@ -239,7 +239,6 @@ import user4Tail from '~/assets/svg/home/user4-tail.svg'
         display: flex;
         align-items: center;
         justify-content: space-around;
-        // gap: 76PX;
 
         .ads-text {
           display: flex;
@@ -277,7 +276,6 @@ import user4Tail from '~/assets/svg/home/user4-tail.svg'
         display: flex;
         flex-direction: column;
         width: 160PX;
-        height: 70PX;
         padding: 6PX;
         box-sizing: border-box;
         position: relative;
@@ -379,14 +377,12 @@ import user4Tail from '~/assets/svg/home/user4-tail.svg'
         .center-item {
           display: flex;
           width: 236PX;
-          height: 54PX;
           background: rgba(139, 116, 255, 0.50);
           border-radius: 10PX;
-          align-items: center;
           flex-direction: column;
           justify-content: center;
           box-sizing: border-box;
-          padding: 0 6PX;
+          padding:2PX 6PX;
           gap: 2PX;
 
           &.half {

+ 317 - 0
app/components/mobile/home/StatsSection.vue

@@ -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>

+ 28 - 0
app/pages/mobile/index.vue

@@ -18,6 +18,11 @@
       <p>从基础设施、数据引擎、服务中台到业务应用,层层协同,将算力、数据与能力统一管理</p>
     </section>
     <Flowchart class="mb-flowchart" />
+    <section class="mb-insight-header">
+      <h2 class="mb-insight-title">安全洞见&nbsp;&nbsp;&nbsp;全网感知</h2>
+      <p class="mb-insight-subtitle">实时攻防态势数据</p>
+    </section>
+    <StatsSection />
   </section>
 </template>
 
@@ -26,6 +31,7 @@ import homeVideo from '~/assets/video/home.webm'
 import ProductTabs from '~/components/mobile/home/ProductTabs.vue'
 import PlansSection from '~/components/mobile/home/PlansSection.vue'
 import Flowchart from '~/components/mobile/home/Flowchart.vue'
+import StatsSection from '~/components/mobile/home/StatsSection.vue';
 </script>
 
 <style scoped lang="scss">
@@ -128,5 +134,27 @@ import Flowchart from '~/components/mobile/home/Flowchart.vue'
     margin-top: 24px;
     padding-bottom: 32px;
   }
+
+  .mb-insight-header {
+    width: 174px;
+    margin: 0 auto;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 8px;
+
+    .mb-insight-title {
+      color: #FFF;
+      font-size: 20px;
+      font-weight: 400;
+      line-height: 24px;
+    }
+
+    .mb-insight-subtitle {
+      color: #A39DFF;
+      font-size: 12px;
+      font-weight: 400;
+    }
+  }
 }
 </style>