Files
Situation-Awareness-Platfor…/src/views/Jyh/security/index.vue

1789 lines
54 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="index-module__NV_5cW__container">
<!-- 头部区域标题副标题武汉核心数据统计 -->
<div class="index-module__NV_5cW__header">
<div class="index-module__NV_5cW__floatingElements">
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
<div class="index-module__NV_5cW__floatingElement"></div>
</div>
<div class="index-module__NV_5cW__titleSection">
<div class="index-module__NV_5cW__titleContainer">
<h1 class="index-module__NV_5cW__title">全球开源风险态势感知监控中心</h1>
<div class="index-module__NV_5cW__titleBadge">WOS</div>
</div>
<!-- <div class="index-module__NV_5cW__subtitle">-->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"-->
<!-- stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"-->
<!-- class="lucide lucide-users" aria-hidden="true">-->
<!-- <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>-->
<!-- <path d="M16 3.128a4 4 0 0 1 0 7.744"></path>-->
<!-- <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>-->
<!-- <circle cx="9" cy="7" r="4"></circle>-->
<!-- </svg>-->
<!-- <span>汇聚武汉开源力量共建城市开源生态</span>-->
<!-- </div>-->
<!-- 武汉4项核心开源数据统计 -->
<div class="index-module__NV_5cW__statsBar">
<div class="index-module__NV_5cW__stat">
<span class="index-module__NV_5cW__statNumber">120+</span>
<span class="index-module__NV_5cW__statLabel">开源企业</span>
</div>
<div class="index-module__NV_5cW__statDivider"></div>
<div class="index-module__NV_5cW__stat">
<span class="index-module__NV_5cW__statNumber">32+</span>
<span class="index-module__NV_5cW__statLabel">高校参与</span>
</div>
<div class="index-module__NV_5cW__statDivider"></div>
<div class="index-module__NV_5cW__stat">
<span class="index-module__NV_5cW__statNumber">300+</span>
<span class="index-module__NV_5cW__statLabel">社区活动</span>
</div>
<div class="index-module__NV_5cW__statDivider"></div>
<div class="index-module__NV_5cW__stat">
<span class="index-module__NV_5cW__statNumber">200+</span>
<span class="index-module__NV_5cW__statLabel">开源项目</span>
</div>
</div>
</div>
</div>
<!-- 内容区域ECharts图表模块 + 项目列表 -->
<div class="index-module__NV_5cW__content">
<!-- ECharts图表区域 -->
<div class="index-module__NV_5cW__chartsSection">
<div class="index-module__NV_5cW__sectionHeader">
<h2 class="index-module__NV_5cW__sectionTitle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-chart-pie" aria-hidden="true">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<span>全球开源风险态势感知监控中心数据可视化</span>
</h2>
</div>
<!-- 图表网格布局新增多个模块 -->
<div class="index-module__NV_5cW__chartsGrid">
<!-- 1. 武汉优秀开源项目列表展示 -->
<!-- <div class="index-module__NV_5cW__chartCard list-card"> -->
<!-- <h3 class="chartCard__title">武汉优秀开源项目</h3> -->
<!-- 新增ref + 鼠标事件 -->
<!-- <div class="chartCard__container list-container"
ref="scrollContainer1"
@mouseenter="pauseScroll(1)"
@mouseleave="resumeScroll(1)">
<div class="project-list">
<div class="project-item" v-for="(item, index) in excellentProjects" :key="index">
<span class="project-rank">{{ index + 1 }}</span>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<span class="project-org">{{ item.org }}</span>
</div>
</div>
</div>
</div> -->
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">情报流</h3>
<!-- 新增ref + 鼠标事件 -->
<div class="chartCard__container list-container"
ref="scrollContainer1"
@mouseenter="pauseScroll(1)"
@mouseleave="resumeScroll(1)">
<div class="project-list">
<div class="project-item" v-for="(item, index) in feedList" :key="index">
<span class="project-rank" :class="item.type">{{ item.type }}</span>
<div class="project-info">
<span class="project-name">{{ item.content }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 2. 高校开源参与分布饼图-->
<!-- <div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">高校开源参与分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.university"></div>
<div v-else id="universityChart" class="chart"></div>
</div>
</div> -->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">行业分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.university"></div>
<div v-else id="cweAttackSurfaceRadar" class="chart"></div>
</div>
</div>
<!-- 3. 开源社区区域分布柱状图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">开源社区区域分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.community"></div>
<div v-else id="communityChart" class="chart"></div>
</div>
</div>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">高危组件 Top 10</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.community"></div>
<div v-else id="highRiskComponentRank" class="chart"></div>
</div>
</div>
<!-- 4. OSS Compass开源项目排行横向柱状图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">OSS Compass 开源项目排行</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.ossCompass"></div>
<div v-else id="ossCompassChart" class="chart"></div>
</div>
</div>
<!-- 5. 武汉开源开发者排行榜横向柱状图-->
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">武汉开源开发者排行榜</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.developer"></div>
<div v-else id="developerChart" class="chart"></div>
</div>
</div>
<!-- 6. 2025武汉市优秀开源软件项目列表-->
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">2025武汉市优秀开源软件项目</h3>
<!-- 新增ref + 鼠标事件 -->
<div class="chartCard__container list-container"
ref="scrollContainer2"
@mouseenter="pauseScroll(2)"
@mouseleave="resumeScroll(2)">
<div class="project-list">
<div class="project-item" v-for="(item, index) in excellentProjects2025" :key="index">
<span class="project-rank">{{ index + 1 }}</span>
<div class="project-info">
<span class="project-name">{{ item.name }}</span>
<span class="project-desc">{{ item.desc }}</span>
</div>
<span class="project-org">{{ item.org }}</span>
</div>
</div>
</div>
</div>
<!-- 7. 武汉市语言与技术竞争趋势折线图近五年
<div class="index-module__NV_5cW__chartCard chartCard__wide">
<h3 class="chartCard__title">武汉市语言与技术竞争趋势2021-2025</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.languageTech"></div>
<div v-else id="languageTechChart" class="chart"></div>
</div>
</div> -->
<div class="index-module__NV_5cW__chartCard chartCard__wide">
<div class="chartCard__header">
<h3 class="chartCard__title">CVE 月度趋势</h3>
<!-- 视图切换按钮用于切换全球和区域视图 -->
<div class="chartCard__actions">
<button
class="chartCard__actionBtn"
:class="{ 'chartCard__actionBtn--active': cveView === 'global' }"
@click="switchCveView('global')"
>
全球
</button>
<button
class="chartCard__actionBtn"
:class="{ 'chartCard__actionBtn--active': cveView === 'regional' }"
@click="switchCveView('regional')"
>
区域
</button>
</div>
</div>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.cveData"></div>
<div v-else id="CVEChart" class="chart"></div>
</div>
</div>
</div>
</div>
<!-- 加入武汉开源社区行动区 -->
<div class="index-module__NV_5cW__actionSection">
<div class="index-module__NV_5cW__actionBackground"></div>
<div class="index-module__NV_5cW__actionContent">
<div class="index-module__NV_5cW__actionIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-sparkles" aria-hidden="true">
<path
d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"></path>
<path d="M20 3v4"></path>
<path d="M22 5h-4"></path>
<path d="M4 17v2"></path>
<path d="M5 18H3"></path>
</svg>
</div>
<h3 class="index-module__NV_5cW__actionTitle">加入武汉开源社区</h3>
<p class="index-module__NV_5cW__actionDescription">
与武汉本地开发者企业高校携手共建开源生态共享开源成果参与社区活动与项目协作
</p>
<div class="index-module__NV_5cW__actionFeatures">
<div class="index-module__NV_5cW__feature">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-users" aria-hidden="true">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<path d="M16 3.128a4 4 0 0 1 0 7.744"></path>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<circle cx="9" cy="7" r="4"></circle>
</svg>
<span>本地技术交流</span>
</div>
<div class="index-module__NV_5cW__feature">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-building2" aria-hidden="true">
<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path>
<path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path>
<path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path>
<path d="M10 6h4"></path>
<path d="M10 10h4"></path>
<path d="M10 14h4"></path>
<path d="M10 18h4"></path>
</svg>
<span>企业项目协作</span>
</div>
<div class="index-module__NV_5cW__feature">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-graduation-cap" aria-hidden="true">
<path d="M22 10v6M2 10l10-5 10 5-10 5z"></path>
<path d="M6 12v5c3 3 9 3 12 0v-5"></path>
</svg>
<span>高校产学研对接</span>
</div>
</div>
<button type="button"
class="ant-btn css-aa1tt5 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-lg index-module__NV_5cW__createButton">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-user-plus" aria-hidden="true">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.12a4 4 0 0 1 0 7.76"></path>
</svg>
<span>加入武汉开源社区</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-arrow-right" aria-hidden="true">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
// 页面尺寸响应式
const { widthType } = usePageResize();
// 1. 武汉优秀开源项目数据
const excellentProjects = ref([
{ name: '调问DWSurvey', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' },
{ name: 'deepin', desc: '深度操作系统', org: '武汉深之度科技有限公司' },
{ name: 'RustSBI', desc: '安全引导程序', org: '华中科技大学' },
{ name: 'NextDenovo', desc: '高效且精确的长读长纠错与组装工具', org: '武汉希望组生物科技有限公司' }
]);
// 情报流数据
const feedList = ref([
{
type: 'CISA',
content: 'CISA KEV新增: WordPress 的 OneClick Chat to Order 插件,存在不安全的直接对象引用漏洞 (CVE-2025-13526) '
},
{
type: '投毒',
content: '投毒监控: npm 基建级包大规模投毒事件影响chalk, debug, ansi-styles 等 18 个包'
},
{
type: 'CNVD',
content: 'CNVD预警: Google Chrome信息泄露漏洞CNVD-2025-3038304'
},
{
type: 'GitHub',
content: 'GitHub安全通告: MovableType 软件的 ContentType 页面 "编辑分类集" 功能存在存储型跨站脚本漏洞'
},
{
type: '修复',
content: '漏洞修复: Node.js 发布 v20.11.0 版本,修复 3 个高危漏洞'
},
{
type: '供应链',
content: '供应链预警: Maven 中央仓库发现 12 个伪造开源组件,包含后门程序'
}
]);
// 2. 2025武汉市优秀开源软件项目
const excellentProjects2025 = ref([
{ name: '调问DWSurvey', desc: '问卷表单', org: '极推科技(武汉)有限公司' },
{ name: 'Eova', desc: '低代码开发平台', org: '武汉简码科技有限公司' },
{ name: 'ClickVisual', desc: '轻量级日志查询、分析、报警可视化平台', org: '武汉初心科技有限公司' },
{ name: 'meta2d.js', desc: '2D图元组成的可视化引擎', org: '武汉乐吾乐科技有限责任公司' },
{ name: 'Juno', desc: '分布式微服务管理系统', org: '武汉斗鱼鱼乐网络科技有限公司' },
{ name: 'Opentalent 开源北极星', desc: '开源人才评价系统', org: '湖北大学' },
{ name: 'fountain', desc: '仓颉编程语言实现的JAVA Spring框架的国产替代', org: '江汉大学' }
]);
// 3. 图表数据定义
const chartData = ref({
// 高校开源参与分布(包含华中科大、武大、湖北大学、江汉大学等)
university: [
{ value: 30, name: '华中科技大学' },
{ value: 25, name: '武汉大学' },
{ value: 15, name: '湖北大学' },
{ value: 10, name: '江汉大学' },
{ value: 8, name: '武汉理工大学' },
{ value: 7, name: '华中师范大学' },
{ value: 5, name: '其他高校' }
],
// 开源社区区域分布
community: {
xAxis: ['武昌区', '洪山区', '东湖高新区', '江汉区', '汉阳区', '青山区'],
data: [28, 35, 42, 18, 15, 12]
},
// OSS Compass开源项目排行提取OpenRank总分左边数值
ossCompass: {
yAxis: [
'openharmony/docs',
'mindspore/mindspore',
'openharmony/arkui_ace_engine',
'apache/doris',
'openharmony/interface_sdk-js',
'anolis-challenge/activity-school',
'PaddlePaddle/Paddle',
'StarRocks/starrocks',
'openharmony/xts_acts',
'ant-design/ant-design'
],
data: [91.55, 67.35, 62.07, 48.82, 44.06, 39.77, 39.55, 34.2, 30.39, 30.37],
// 完整分数数据用于tooltip
fullData: [
{ openRank: '91.55 | 30.86', quality: '92.06 | 8.905', wxb: '70.69 | -14.8', score: '84.77 | 8.32' },
{ openRank: '67.35 | 27.41', quality: '96.92 | 7.17', wxb: '98.915 | 7.1', score: '87.73 | 13.89' },
{ openRank: '62.07 | 17.98', quality: '71.69 | 2.885', wxb: '76.06 | 10.9', score: '69.94 | 10.59' },
{ openRank: '48.82 | 10.14', quality: '67.26 | 0.573', wxb: '30.5 | 7.3', score: '48.86 | 6.00' },
{ openRank: '44.06 | 11.49', quality: '32.5 | 3.14', wxb: '67.55 | 16.7', score: '48.04 | 10.44' },
{ openRank: '39.77 | 37.64', quality: '31.05 | 0.215', wxb: '24.798 | -18.9', score: '31.87 | 6.32' },
{ openRank: '39.55 | 7.25', quality: '68.55 | -2.937', wxb: '68.26 | -17.3', score: '58.79 | -4.33' },
{ openRank: '34.2 | 2.08', quality: '49.07 | -0.877', wxb: '47.07 | 9.9', score: '43.45 | 3.70' },
{ openRank: '30.39 | 10.97', quality: '29.8 | 5.633', wxb: '27.045 | 4.3', score: '29.08 | 6.97' },
{ openRank: '30.37 | 8.57', quality: '37.1 | 4.295', wxb: '35.097 | 6.8', score: '34.19 | 6.55' }
]
},
// 武汉开源开发者排行榜模拟数据前10名按贡献值排序
developer: {
yAxis: ['张XX', '李XX', '王XX', '刘XX', '陈XX', '杨XX', '黄XX', '赵XX', '吴XX', '周XX'],
data: [980, 850, 720, 650, 580, 520, 480, 420, 380, 350]
},
// 武汉市语言与技术竞争趋势2021-2025多系列
languageTech: {
xAxis: ['2021', '2022', '2023', '2024', '2025'],
series: [
{ name: 'Java', data: [85, 82, 78, 75, 72] },
{ name: 'Python', data: [78, 80, 85, 88, 90] },
{ name: 'Rust', data: [20, 35, 45, 60, 75] },
{ name: '仓颉', data: [0, 5, 15, 30, 45] },
{ name: 'Go', data: [60, 65, 70, 72, 75] }
]
},
// 高危组件数据(基于真实开源项目使用情况和漏洞统计)
highRiskComponent: [
{ name: 'Log4j2', cvss: 9.8, vulnCount: 15, usage: '1200万+', desc: 'CVE-2021-44228等严重RCE漏洞' },
{ name: 'Apache Struts2', cvss: 9.7, vulnCount: 12, usage: '850万+', desc: 'S2系列远程代码执行漏洞' },
{ name: 'OpenSSL', cvss: 9.6, vulnCount: 18, usage: '2500万+', desc: '心脏出血、幽灵猫等高危漏洞' },
{ name: 'Spring Framework', cvss: 9.5, vulnCount: 9, usage: '1800万+', desc: 'Spring4Shell等RCE漏洞' },
{ name: 'Jackson', cvss: 9.4, vulnCount: 11, usage: '1000万+', desc: '反序列化漏洞影响Java生态' },
{ name: 'Fastjson', cvss: 9.3, vulnCount: 14, usage: '600万+', desc: '多个反序列化RCE漏洞' },
{ name: 'Apache Commons', cvss: 9.2, vulnCount: 8, usage: '1500万+', desc: 'Collections反序列化漏洞' },
{ name: 'jQuery', cvss: 9.1, vulnCount: 6, usage: '3000万+', desc: 'XSS和原型污染漏洞' },
{ name: 'Lodash', cvss: 9.0, vulnCount: 5, usage: '2200万+', desc: '原型污染漏洞影响Node.js生态' },
{ name: 'Moment.js', cvss: 9.0, vulnCount: 4, usage: '1100万+', desc: 'ReDoS和原型污染漏洞' }
],
// 行业分布数据
industryDistribution: [
{ value: 25.8, name: '人工智能' },
{ value: 18.4, name: '云计算' },
{ value: 15.6, name: '大数据' },
{ value: 12.2, name: '物联网' },
{ value: 9.6, name: '网络安全' },
{ value: 7.1, name: '区块链' },
{ value: 6.4, name: '工业互联网' },
{ value: 4.9, name: '移动开发' }
],
// 科技感配色方案
industryColorPalette: [
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
'#ff5100', '#c0232a', '#a262f2', '#379bff'
],
// CVE图表数据
cveData: {
// 全球CVE2024年实际数据基于Excel数据
global: {
cveCount: [98402, 418, 7025, 3711, 5115, 3228, 3044, 2957, 2592, 1455, 4826, 4661],
acceleration: [0, -99.58, 1580.62, -47.17, 37.83, -36.89, -5.70, -2.86, -12.34, -43.87, 231.68, -3.42] // 环比增长率(%
},
// 中国区域CVE2024年数据约占全球30%
regional: {
cveCount: [29521, 125, 2108, 1113, 1535, 968, 913, 887, 778, 437, 1448, 1398],
acceleration: [0, -99.58, 1580.62, -47.17, 37.83, -36.89, -5.70, -2.86, -12.34, -43.87, 231.68, -3.42] // 环比增长率(%
}
}
});
// 视图切换状态变量global: 全球, regional: 区域)
const cveView = ref('global');
// 空数据标识
const isEmpty = ref({
university: false,
community: false,
ossCompass: false,
developer: false,
languageTech: false,
highRiskComponent: false,
industryDistribution: false,
cveData: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
universityChart: null,
communityChart: null,
ossCompassChart: null,
developerChart: null,
languageTechChart: null,
highRiskComponentRank: null,
cweAttackSurfaceRadar: null,
CVEChart: null
});
// ===================== 新增:列表自动滚动逻辑 =====================
// 1. 定义滚动容器的ref
const scrollContainer1 = ref<HTMLDivElement | null>(null); // 第一个列表容器
const scrollContainer2 = ref<HTMLDivElement | null>(null); // 第二个列表容器
// 2. 存储定时器1/2定时器ID
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = {
1: null,
2: null
};
// 3. 滚动配置
const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅)
const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅)
// 4. 通用自动滚动函数
const autoScroll = (containerKey: number) => {
// 获取对应容器
const container = containerKey === 1 ? scrollContainer1.value : scrollContainer2.value;
if (!container) return;
// 获取列表内容元素project-list
const projectList = container.querySelector('.project-list') as HTMLDivElement;
if (!projectList) return;
// 若内容高度 ≤ 容器高度,不执行滚动
if (projectList.scrollHeight <= container.clientHeight) {
// 清除现有定时器
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
scrollTimers[containerKey] = null;
}
return;
}
// 清除现有定时器(避免重复)
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
}
// 启动定时器滚动
scrollTimers[containerKey] = setInterval(() => {
// 向下滚动步长
container.scrollTop += scrollStep;
// 滚动到底部时重置为0循环滚动
// 加1是为了兼容像素精度问题
if (projectList.scrollHeight - container.scrollTop <= container.clientHeight + 1) {
container.scrollTop = 0; // 直接重置也可以用平滑滚动container.scrollTo({ top: 0, behavior: 'smooth' })
}
}, scrollInterval);
};
// 5. 暂停滚动
const pauseScroll = (containerKey: number) => {
if (scrollTimers[containerKey]) {
clearInterval(scrollTimers[containerKey]!);
scrollTimers[containerKey] = null;
}
};
// 6. 恢复滚动
const resumeScroll = (containerKey: number) => {
autoScroll(containerKey);
};
// 7. 组件卸载时清除所有定时器
onUnmounted(() => {
Object.keys(scrollTimers).forEach(key => {
const k = Number(key);
if (scrollTimers[k]) {
clearInterval(scrollTimers[k]!);
scrollTimers[k] = null;
}
});
});
// CVE视图切换方法
const switchCveView = (view: 'global' | 'regional') => {
cveView.value = view;
nextTick(() => {
initCVEChart();
});
};
// 初始化CVE图表柱状图+折线图)
const initCVEChart = () => {
const el = document.getElementById('CVEChart');
if (!el) return;
if (chartInstances.value.CVEChart) {
chartInstances.value.CVEChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.CVEChart = myChart;
// 获取当前视图的数据
const currentData = chartData.value.cveData[cveView.value === 'global' ? 'global' : 'regional'];
const cveCount = currentData.cveCount;
const acceleration = currentData.acceleration;
// 月份数据
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
},
formatter: (params: any) => {
let res = `<div>${params[0].name}</div>`;
params.forEach((p: any) => {
if (p.seriesName === 'CVE数量') {
res += `<div>${p.marker}${p.seriesName}: ${p.value}</div>`;
} else {
res += `<div>${p.marker}${p.seriesName}: ${p.value}%</div>`;
}
});
return res;
}
},
legend: {
top: 0,
textStyle: { fontSize: 12 },
data: ['CVE数量', '环比增长率']
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: [
{
type: 'category',
data: months,
axisPointer: {
type: 'shadow'
},
axisLabel: { fontSize: 12 }
}
],
yAxis: [
{
type: 'value',
name: 'CVE数量',
nameTextStyle: { fontSize: 12, color: '#333' },
axisLabel: { fontSize: 12, color: '#666' },
position: 'left',
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
}
},
{
type: 'value',
name: '环比增长率(%)',
nameTextStyle: { fontSize: 12, color: '#333' },
axisLabel: { fontSize: 12, color: '#666' },
position: 'right',
splitLine: {
show: false
}
}
],
series: [
{
name: 'CVE数量',
type: 'bar',
data: cveCount,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#3b82f6' },
{ offset: 1, color: '#60a5fa' }
])
},
barWidth: 20,
label: {
show: true,
position: 'top',
formatter: '{c}',
fontSize: 10,
color: '#333'
}
},
{
name: '环比增长率',
type: 'line',
yAxisIndex: 1,
data: acceleration,
itemStyle: {
color: '#ef4444',
borderWidth: 2
},
lineStyle: {
width: 2,
color: '#ef4444'
},
symbol: 'circle',
symbolSize: 6,
smooth: true,
label: {
show: true,
position: 'top',
formatter: (params: any) => `${params.value}%`,
fontSize: 10,
color: '#333'
}
}
]
});
};
// ===================== 新增结束 =====================
// 初始化高校参与分布饼图
const initUniversityChart = () => {
const el = document.getElementById('universityChart');
if (!el) return;
if (chartInstances.value.universityChart) {
chartInstances.value.universityChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.universityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'item',
formatter: (params: any) => `${params.name}: ${params.value}%`
},
legend: {
// orient: 'vertical',
// left: 'left',
bottom: '0',
textStyle: { fontSize: 12 },
itemWidth: 10,
itemHeight: 10
},
series: [
{
name: '参与度',
type: 'pie',
radius: ['40%', '70%'],
data: chartData.value.university,
label: {
show: true,
formatter: '{b}: {c}%',
fontSize: 10
},
color: ['#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#6366f1', '#14b8a6']
}
],
grid: { left: 0, bottom: 0, top: 0, right: 0 }
});
};
// 初始化社区区域分布柱状图
const initCommunityChart = () => {
const el = document.getElementById('communityChart');
if (!el) return;
if (chartInstances.value.communityChart) {
chartInstances.value.communityChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.communityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => `${params[0].name}: ${params[0].value}`
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: chartData.value.community.xAxis,
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'value',
name: '社区数量',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
},
series: [
{
name: '开源社区数量',
type: 'bar',
data: chartData.value.community.data,
itemStyle: {
color: (params: any) => {
const colorList = ['#3b82f6', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#6366f1'];
return colorList[params.dataIndex];
}
}
}
]
});
};
// 初始化OSS Compass开源项目排行横向柱状图
const initOssCompassChart = () => {
const el = document.getElementById('ossCompassChart');
if (!el) return;
if (chartInstances.value.ossCompassChart) {
chartInstances.value.ossCompassChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.ossCompassChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const index = params[0].dataIndex;
const data = chartData.value.ossCompass.fullData[index];
return `
<div>${params[0].name}</div>
<div>OpenRank: ${data.openRank}</div>
<div>代码内生质量: ${data.quality}</div>
<div>WXB分数: ${data.wxb}</div>
<div>综合分数: ${data.score}</div>
`;
}
},
grid: { left: '15%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: 'OpenRank总分',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'category',
data: chartData.value.ossCompass.yAxis,
axisLabel: { fontSize: 10, overflow: 'truncate', width: 120 }
},
series: [
{
name: 'OpenRank总分',
type: 'bar',
data: chartData.value.ossCompass.data,
itemStyle: { color: '#2951E0' },
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 10
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(41, 81, 224, 0.24)' },
{ offset: 1, color: 'rgba(41, 81, 224, 0.04)' }
])
}
}
]
});
};
// 初始化武汉开源开发者排行榜(横向柱状图)
const initDeveloperChart = () => {
const el = document.getElementById('developerChart');
if (!el) return;
if (chartInstances.value.developerChart) {
chartInstances.value.developerChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.developerChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => `${params[0].name}: ${params[0].value}贡献值`
},
grid: { left: '10%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: '贡献值',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'category',
data: chartData.value.developer.yAxis,
axisLabel: { fontSize: 12 }
},
series: [
{
name: '贡献值',
type: 'bar',
data: chartData.value.developer.data,
itemStyle: { color: '#8b5cf6' },
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 12
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(139, 92, 246, 0.24)' },
{ offset: 1, color: 'rgba(139, 92, 246, 0.04)' }
])
}
}
]
});
};
// 初始化行业分布图表(饼图)
const initIndustryDistributionChart = () => {
const el = document.getElementById('cweAttackSurfaceRadar');
if (!el) return;
if (chartInstances.value.cweAttackSurfaceRadar) {
chartInstances.value.cweAttackSurfaceRadar.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.cweAttackSurfaceRadar = myChart;
myChart.setOption({
tooltip: {
trigger: 'item',
formatter: (params: any) => `${params.name}: ${params.percent}% (${params.value}%)`
},
legend: {
bottom: '0',
textStyle: { fontSize: 12 },
itemWidth: 10,
itemHeight: 10
},
color: chartData.value.industryColorPalette,
series: [
{
name: '行业分布',
type: 'pie',
radius: ['40%', '70%'],
data: chartData.value.industryDistribution,
label: {
show: true,
formatter: '{b}: {d}%',
fontSize: 10
}
}
],
grid: { left: 0, bottom: 0, top: 0, right: 0 }
});
};
// 初始化高危组件Top 10图表横向柱状图
const initHighRiskComponentChart = () => {
const el = document.getElementById('highRiskComponentRank');
if (!el) return;
if (chartInstances.value.highRiskComponentRank) {
chartInstances.value.highRiskComponentRank.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.highRiskComponentRank = myChart;
// 提取组件名称和CVSS评分
const yAxisData = chartData.value.highRiskComponent.map(item => item.name);
const seriesData = chartData.value.highRiskComponent.map(item => ({
value: item.cvss,
vulnCount: item.vulnCount,
usage: item.usage,
desc: item.desc
}));
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const data = chartData.value.highRiskComponent[params[0].dataIndex];
return `
<div style="padding: 4px;">
<div style="font-weight: bold; font-size: 14px;">${data.name}</div>
<div>CVSS评分: ${data.cvss}</div>
<div>漏洞数量: ${data.vulnCount}个</div>
<div>使用量: ${data.usage}</div>
<div>漏洞描述: ${data.desc}</div>
</div>
`;
}
},
grid: { left: '15%', right: '4%', bottom: '3%', top: '3%', containLabel: true },
xAxis: {
type: 'value',
name: 'CVSS评分',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
max: 10
},
yAxis: {
type: 'category',
data: yAxisData,
axisLabel: { fontSize: 12 }
},
series: [
{
name: 'CVSS评分',
type: 'bar',
data: seriesData,
itemStyle: {
color: (params: any) => {
const value = params.value;
if (value >= 9.0) return '#ef4444'; // 红色 - 严重
if (value >= 7.0) return '#f97316'; // 橙色 - 高危
if (value >= 4.0) return '#eab308'; // 黄色 - 中危
return '#22c55e'; // 绿色 - 低危
}
},
label: {
show: true,
position: 'right',
formatter: '{c}',
fontSize: 12
}
}
]
});
};
// 初始化武汉市语言与技术竞争趋势(折线图)
const initLanguageTechChart = () => {
const el = document.getElementById('languageTechChart');
if (!el) return;
if (chartInstances.value.languageTechChart) {
chartInstances.value.languageTechChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.languageTechChart = myChart;
// 定义系列颜色
const colorList = ['#3b82f6', '#10b981', '#f59e0b', '#ec4899', '#8b5cf6'];
const series = chartData.value.languageTech.series.map((item, index) => ({
name: item.name,
type: 'line',
data: item.data,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: colorList[index], width: 2 },
itemStyle: { color: colorList[index] },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: `${colorList[index]}33` },
{ offset: 1, color: `${colorList[index]}08` }
])
}
}));
myChart.setOption({
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
let res = `<div>${params[0].axisValue}</div>`;
params.forEach((p: any) => {
res += `<div>${p.seriesName}: ${p.value}</div>`;
});
return res;
}
},
legend: {
top: 0,
textStyle: { fontSize: 12 }
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true },
xAxis: {
type: 'category',
data: chartData.value.languageTech.xAxis,
axisLabel: { fontSize: 12 }
},
yAxis: {
type: 'value',
name: '使用占比/热度',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
},
series
});
};
// 统一初始化图表
const initCharts = () => {
// 空数据判断
isEmpty.value = {
university: chartData.value.university.length === 0,
community: chartData.value.community.data.every(item => item === 0),
ossCompass: chartData.value.ossCompass.data.every(item => item === 0),
developer: chartData.value.developer.data.every(item => item === 0),
languageTech: chartData.value.languageTech.series.every(s => s.data.every(d => d === 0)),
highRiskComponent: chartData.value.highRiskComponent.length === 0,
industryDistribution: chartData.value.industryDistribution.length === 0,
cveData: chartData.value.cveData.global.cveCount.every(item => item === 0) &&
chartData.value.cveData.regional.cveCount.every(item => item === 0)
};
// 初始化各图表
if (!isEmpty.value.university) nextTick(() => initUniversityChart());
if (!isEmpty.value.community) nextTick(() => initCommunityChart());
if (!isEmpty.value.ossCompass) nextTick(() => initOssCompassChart());
if (!isEmpty.value.developer) nextTick(() => initDeveloperChart());
if (!isEmpty.value.languageTech) nextTick(() => initLanguageTechChart());
if (!isEmpty.value.highRiskComponent) nextTick(() => initHighRiskComponentChart());
if (!isEmpty.value.industryDistribution) nextTick(() => initIndustryDistributionChart());
if (!isEmpty.value.cveData) nextTick(() => initCVEChart());
};
// 页面resize时重绘图表
watch(widthType, () => {
initCharts();
// 重新启动滚动防止resize后滚动失效
autoScroll(1);
autoScroll(2);
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
initCharts();
// 启动列表自动滚动
autoScroll(1);
autoScroll(2);
}, 100);
});
</script>
<style lang="scss" scoped>
.index-module__NV_5cW__container {
background: linear-gradient(to bottom right, #faf5ff, #60a5fa);
min-height: 100vh;
padding: 2rem 1rem;
position: relative;
overflow-x: hidden;
}
.index-module__NV_5cW__header {
text-align: center;
margin-bottom: 3rem;
padding: 2rem 1rem 1rem;
position: relative;
}
.index-module__NV_5cW__floatingElements {
pointer-events: none;
opacity: 0.3;
position: absolute;
inset: 0;
overflow: hidden;
}
.index-module__NV_5cW__floatingElement {
background: linear-gradient(135deg, #3b82f60d, #9333ea0d);
border-radius: 50%;
width: 120px;
height: 120px;
animation: 30s linear infinite index-module__NV_5cW__float;
position: absolute;
}
.index-module__NV_5cW__floatingElement:first-child {
animation-delay: 0s;
top: 10%;
left: 10%;
}
.index-module__NV_5cW__floatingElement:nth-child(2) {
animation-delay: -7s;
top: 60%;
right: 10%;
}
.index-module__NV_5cW__floatingElement:nth-child(3) {
animation-delay: -14s;
bottom: 20%;
left: 50%;
}
@keyframes index-module__NV_5cW__float {
0% {
transform: translateY(0) rotate(0);
}
33% {
transform: translateY(-20px) rotate(120deg);
}
66% {
transform: translateY(10px) rotate(240deg);
}
to {
transform: translateY(0) rotate(360deg);
}
}
.index-module__NV_5cW__titleSection {
color: #333;
z-index: 2;
flex-direction: column;
align-items: center;
gap: 1rem;
display: flex;
position: relative;
}
.index-module__NV_5cW__titleContainer {
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
display: flex;
}
.index-module__NV_5cW__title {
background: #333;
-webkit-text-fill-color: transparent;
letter-spacing: -0.02em;
white-space: nowrap;
-webkit-background-clip: text;
background-clip: text;
margin: 0;
font-size: 2.8rem;
font-weight: 700;
}
.index-module__NV_5cW__titleBadge {
color: #fff;
letter-spacing: 0.05em;
white-space: nowrap;
background: #60a5fa;
border-radius: 1.5rem;
padding: 0.4rem 1rem;
font-size: 1rem;
font-weight: 600;
box-shadow: 0 2px 10px #3b82f64d;
}
.index-module__NV_5cW__subtitle {
color: #666;
text-align: center;
align-items: center;
gap: 0.5rem;
max-width: 500px;
font-size: 1.1rem;
font-weight: 400;
display: flex;
}
.index-module__NV_5cW__statsBar {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffff1a;
border: 1px solid #fff3;
border-radius: 1.5rem;
align-items: center;
gap: 1.5rem;
width: fit-content;
max-width: 90%;
margin-top: 0.75rem;
padding: 0.75rem 1.5rem;
display: flex;
}
.index-module__NV_5cW__stat {
flex-direction: column;
align-items: center;
gap: 0.25rem;
display: flex;
}
.index-module__NV_5cW__statNumber {
color: #60a5fa;
font-size: 1.5rem;
font-weight: 700;
}
.index-module__NV_5cW__statLabel {
color: #333;
font-size: 0.875rem;
font-weight: 500;
}
.index-module__NV_5cW__statDivider {
background: #fff3;
width: 1px;
height: 2rem;
}
.index-module__NV_5cW__content {
max-width: 1400px; /* 加宽容器,适配更多图表 */
margin: 0 auto;
padding: 0 1rem;
}
/* 图表区域样式适配示例的num-empty */
.index-module__NV_5cW__chartsSection {
margin-bottom: 3rem;
}
.index-module__NV_5cW__chartsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); /* 调整列宽,适配列表 */
gap: 1.5rem;
padding: 0;
}
.index-module__NV_5cW__chartCard {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 16px #00000014;
transition: all 0.3s;
}
/* 列表卡片特殊样式 */
.index-module__NV_5cW__chartCard.list-card {
padding: 1.5rem;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.chartCard__wide {
grid-column: 1 / -1; /* 跨列显示 */
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: center;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
/* 列表容器样式:隐藏原生滚动条(可选,视觉更美观) */
.chartCard__container.list-container {
height: auto;
max-height: 300px; /* 限制列表高度,超出滚动 */
overflow-y: auto;
padding: 0 0.5rem;
/* 隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
/* 隐藏webkit内核浏览器的滚动条 */
.chartCard__container.list-container::-webkit-scrollbar {
display: none;
}
/* 项目列表样式 */
.project-list {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.project-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.8rem;
background: #f8f9fa;
border-radius: 0.5rem;
border-left: 3px solid #2951E0;
transition: all 0.2s;
}
/* 鼠标悬停项目项时的样式(可选) */
.project-item:hover {
background: #f0f4ff;
transform: translateX(2px);
}
.project-rank {
min-width: 24px;
height: 24px;
border-radius: 4px;
background: #2951E0;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
padding: 0 4px;
}
.project-rank.cisa {
background: #ff0000;
}
.project-rank.poisoning {
background: #ffa500;
}
.project-rank.cnvd {
background: #ffff00;
color: #000;
}
.project-rank.github {
background: #0000ff;
}
.project-rank.fix {
background: #008000;
}
.project-rank.supply {
background: #800080;
}
.project-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.project-name {
font-size: 14px;
font-weight: 600;
color: #333;
}
.project-desc {
font-size: 12px;
color: #666;
font-style: italic;
}
.project-org {
font-size: 12px;
color: #888;
flex-shrink: 0;
white-space: nowrap;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
}
/* 与示例一致的空数据样式 */
.num-empty {
height: 10px;
border-top: 2px solid #2951E0;
width: 100%;
background: linear-gradient(180deg, rgba(41, 81, 224, 0.12) 0%, rgba(41, 81, 224, 0.04) 100%);
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.chart {
width: 100%;
height: 100%;
}
.index-module__NV_5cW__sectionHeader {
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
display: flex;
position: relative;
}
.index-module__NV_5cW__sectionTitle {
color: #333;
text-align: center;
justify-content: center;
align-items: center;
gap: 0.75rem;
width: 100%;
margin: 0;
font-size: 1.5rem;
font-weight: 600;
display: flex;
}
.index-module__NV_5cW__actionSection {
text-align: center;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1.5rem;
margin: 0;
padding: 2.5rem 2rem;
position: relative;
overflow: hidden;
box-shadow: 0 8px 24px #00000014;
}
.index-module__NV_5cW__actionBackground {
z-index: 1;
background: linear-gradient(135deg, #3b82f60d 0%, #9333ea0d 100%);
position: absolute;
inset: 0;
}
.index-module__NV_5cW__actionContent {
z-index: 2;
max-width: 600px;
margin: 0 auto;
position: relative;
}
.index-module__NV_5cW__actionIcon {
color: #fff;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
border-radius: 50%;
justify-content: center;
align-items: center;
width: 3rem;
height: 3rem;
margin: 0 auto 1.5rem;
display: flex;
box-shadow: 0 4px 12px #3b82f633;
}
.index-module__NV_5cW__actionTitle {
background: linear-gradient(135deg, #1e293b 0%, #3b82f6 100%);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
margin-bottom: 1rem;
font-size: 2rem;
font-weight: 700;
line-height: 1.2;
}
.index-module__NV_5cW__actionDescription {
color: #64748b;
margin-bottom: 2rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
.index-module__NV_5cW__actionFeatures {
flex-wrap: wrap;
justify-content: center;
gap: 1.5rem;
margin-bottom: 2rem;
display: flex;
}
.index-module__NV_5cW__feature {
color: #3b82f6;
background: #3b82f614;
border: 1px solid #3b82f626;
border-radius: 1.5rem;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
font-size: 0.85rem;
font-weight: 500;
display: flex;
}
.index-module__NV_5cW__createButton {
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
border: none;
border-radius: 1.5rem;
align-items: center;
gap: 0.5rem;
height: auto;
margin: 0 auto;
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s;
display: flex;
box-shadow: 0 4px 12px #3b82f64d;
}
.index-module__NV_5cW__createButton:hover {
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
transform: translateY(-2px);
box-shadow: 0 6px 18px #3b82f666;
}
/* 响应式适配 */
@media (max-width: 768px) {
.index-module__NV_5cW__titleContainer {
text-align: center;
flex-direction: column;
gap: 0.5rem;
}
.index-module__NV_5cW__title {
font-size: 2.5rem;
}
.index-module__NV_5cW__titleBadge {
padding: 0.4rem 1.2rem;
font-size: 1rem;
}
.index-module__NV_5cW__container {
padding: 1rem;
}
.index-module__NV_5cW__header {
margin-bottom: 2rem;
padding: 1.5rem 1rem;
}
.index-module__NV_5cW__statsBar {
flex-wrap: nowrap;
justify-content: space-between;
gap: 0.8rem;
width: 100%;
max-width: 100%;
padding: 0.8rem 1.2rem;
}
.index-module__NV_5cW__stat {
text-align: center;
flex: 1;
min-width: 60px;
}
.index-module__NV_5cW__statNumber {
font-size: 1.1rem;
}
.index-module__NV_5cW__statLabel {
white-space: nowrap;
font-size: 0.7rem;
}
.index-module__NV_5cW__statDivider {
width: 1px;
height: 1.2rem;
}
.index-module__NV_5cW__sectionTitle {
text-align: left;
flex: 1;
justify-content: flex-start;
gap: 0.5rem;
font-size: 1.3rem;
}
.index-module__NV_5cW__actionSection {
border-radius: 1rem;
margin: 0;
padding: 2rem 1.5rem;
}
.index-module__NV_5cW__actionTitle {
font-size: 1.6rem;
}
.index-module__NV_5cW__actionFeatures {
flex-direction: column;
align-items: center;
gap: 1rem;
}
.index-module__NV_5cW__createButton {
padding: 0.65rem 1.5rem;
font-size: 0.95rem;
}
.chartCard__container {
height: 250px; /* 移动端图表高度调整 */
}
/* 移动端列表样式调整 */
.project-item {
flex-wrap: wrap;
gap: 0.5rem;
}
.project-org {
width: 100%;
max-width: 100%;
margin-top: 0.2rem;
}
}
@media (max-width: 480px) {
.index-module__NV_5cW__title {
font-size: 1.8rem;
}
.index-module__NV_5cW__actionSection {
padding: 1.5rem 1rem;
}
.index-module__NV_5cW__statsBar {
gap: 0.6rem;
padding: 0.6rem 0.8rem;
}
.index-module__NV_5cW__statNumber {
font-size: 1rem;
}
.index-module__NV_5cW__statLabel {
font-size: 0.65rem;
}
.index-module__NV_5cW__sectionTitle {
gap: 0.4rem;
font-size: 1.2rem;
}
.chartCard__container {
height: 200px; /* 小屏移动端图表高度调整 */
}
.index-module__NV_5cW__chartsGrid {
grid-template-columns: 1fr;
}
/* 小屏列表高度调整 */
.chartCard__container.list-container {
max-height: 300px;
}
}
/* 图表卡片头部样式 */
.chartCard__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
/* 图表操作按钮样式 */
.chartCard__actions {
display: flex;
gap: 8px;
}
.chartCard__actionBtn {
padding: 4px 12px;
border: 1px solid #d1d5db;
background-color: #f9fafb;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.chartCard__actionBtn:hover {
background-color: #f3f4f6;
}
.chartCard__actionBtn--active {
background-color: #3b82f6;
color: white;
border-color: #3b82f6;
}
.chartCard__actionBtn--active:hover {
background-color: #2563eb;
}
</style>