Compare commits

..

207 Commits

Author SHA1 Message Date
d266377
a5c3b03eb8 态势感知平台-武汉市开源生态建设-城市开源人才数量对比接口对接 2026-02-04 10:38:51 +08:00
d266377
46eb668c31 态势感知平台-武汉市开源生态建设-2025武汉优秀开源项目接口对接 2026-02-04 09:51:45 +08:00
d266377
1aaefae7e8 态势感知平台-武汉市开源生态建设-2024武汉优秀开源项目接口对接 2026-02-03 17:34:46 +08:00
d266377
00652dab3b 态势感知平台-武汉市开源生态建设-中国城市开源项目数量对比接口对接 2026-02-03 16:08:25 +08:00
d266377
7553c7ebd0 态势感知平台-武汉市开源生态建设-武汉优质开源项目榜接口对接 2026-02-03 15:17:55 +08:00
d266377
4f58ace30c 态势感知平台-武汉市开源生态建设-湖北高校鸿蒙贡献者接口对接 2026-02-03 14:43:04 +08:00
d266377
40be92afc3 态势感知平台-武汉市开源生态建设-中国高校鸿蒙贡献分布接口对接 2026-02-03 14:24:52 +08:00
d266377
693fb32bb3 态势感知平台-武汉市开源生态建设-武汉企业贡献榜接口对接 2026-02-02 16:14:39 +08:00
d266377
4a6d0137a1 态势感知平台-武汉市开源生态建设-开源企业贡献榜接口对接 2026-02-02 15:57:50 +08:00
d266377
bbad6ce7e2 态势感知平台-武汉市开源生态建设-开源企业分布接口对接 2026-02-02 15:45:10 +08:00
d266377
d280d32326 态势感知平台-开源生态与贡献价值全景卡片样式修改 2026-02-02 15:02:45 +08:00
d266377
42d4f24c4b 态势感知平台-全球开源企业贡献榜和语言与技术竞争样式卡片样式修改 2026-02-02 14:49:49 +08:00
d266377
ce325e8518 态势感知平台-开源生态与贡献价值全景-明星开发者聚焦样式修改 2026-02-02 14:49:00 +08:00
d266377
157048054f 态势感知平台-开源生态与贡献价值全景-社区治理健康度对比样式修改 2026-02-02 14:48:12 +08:00
d266377
1fd3c46b42 态势感知平台-全球开源风险态势感知监控中心-行业分布样式修改 2026-02-02 14:14:04 +08:00
d266377
4b695b932a 态势感知平台-开源生态与贡献价值全景-基础设施覆盖率注释样式修改 2026-02-02 14:08:56 +08:00
d266377
483c6d5c6b 态势感知平台-首页概览的数据格式修改 2026-02-02 09:34:00 +08:00
d266377
3fc27a684b 态势感知平台-武汉市开源生态建设接口字段修改 2026-01-30 16:13:57 +08:00
d266377
b2b04763d3 态势感知平台-武汉市开源生态建设-武汉开发者贡献榜接口对接 2026-01-30 16:13:32 +08:00
d266377
3d487c8d6c 态势感知平台-武汉市开源生态建设-开发者贡献榜接口对接 2026-01-30 15:56:21 +08:00
d266377
613b801da8 态势感知平台-武汉市开源生态建设-开发者分布接口对接 2026-01-30 15:03:12 +08:00
d266377
2affb8296e 态势感知平台-开源生态与贡献价值全景-高校开源社团/俱乐部数量卡片样式修改 2026-01-30 14:51:52 +08:00
d266377
88612f25ef 态势感知平台-开源生态与贡献价值全景-社区治理健康度对比样式修改 2026-01-30 14:48:15 +08:00
d266377
520b030fab 态势感知平台-武汉市开源生态建设-明星开发者聚焦样式修改 2026-01-30 14:34:07 +08:00
d266377
ecc78c82b3 态势感知平台-全球开源风险态势感知监控中心-行业分布样式修改 2026-01-30 14:33:06 +08:00
d266377
82005a43a3 态势感知平台-开源生态与贡献价值全景-基础设施覆盖率标注样式修改 2026-01-30 12:45:00 +08:00
d266377
7b59d3ac9c 态势感知平台-开源生态与贡献价值全景接口字段修改 2026-01-30 11:08:30 +08:00
d266377
15b04e6788 态势感知平台-武汉市开源生态建设接口字段修改 2026-01-30 11:07:58 +08:00
d266377
4ddc7aa90f Merge branch 'SAP' of http://222.20.126.217:3000/JYHlab_ZLZXB/Situation-Awareness-Platform-Frontend into SAP 2026-01-30 10:23:05 +08:00
d266377
c09541afb7 态势感知平台-全球开源风险态势感知监控中心接口字段修改 2026-01-30 10:22:54 +08:00
fmk1023
5c0d46904b Merge remote-tracking branch 'origin/SAP' into SAP
# Conflicts:
#	src/views/Jyh/Community/index.vue
2026-01-30 10:08:44 +08:00
fmk1023
0fcbf2ba73 态势感知平台-自动化服务器部署配置 2026-01-29 18:14:50 +08:00
fmk1023
a832c64d41 态势感知平台-自动化服务器部署配置 2026-01-29 17:58:36 +08:00
d266377
f640eca25c 态势感知平台-武汉市开源生态建设- 武汉鸿蒙项目接口对接 2026-01-29 17:47:25 +08:00
d266377
97ab1a1e79 态势感知平台-武汉市开源生态建设-鸿蒙开源项目接口对接 2026-01-29 17:34:40 +08:00
d266377
33a617ef91 态势感知平台-武汉市开源生态建设-鸿蒙项目城市分布接口对接 2026-01-29 17:28:43 +08:00
d266377
9caa5322ba 态势感知平台-武汉市开源生态建设-统计概览2 接口对接 2026-01-29 17:15:56 +08:00
d266377
3eef874bb8 态势感知平台-武汉市开源生态建设-统计概览1 接口对接 2026-01-29 16:55:18 +08:00
d266377
e51e9a2176 态势感知平台-武汉市开源生态建设接口定义 2026-01-29 14:48:57 +08:00
d266377
e67c386cb7 态势感知平台-开源生态与贡献价值全景-语言与技术竞争接口对接 2026-01-29 11:42:57 +08:00
d266377
bd9decfed8 态势感知平台-开源生态与贡献价值全景接口字段修改 2026-01-29 11:40:58 +08:00
d266377
bf25fcfc92 态势感知平台-开源生态与贡献价值全景-全球开源企业贡献榜top30接口对接 2026-01-29 11:20:01 +08:00
d266377
05cce185e6 态势感知平台-开源生态与贡献价值全景-开源项目热力图接口对接 2026-01-29 10:56:13 +08:00
d266377
2876d23502 态势感知平台-开源生态与贡献价值全景接口字段修改 2026-01-29 10:54:13 +08:00
d266377
5ce6380dea 态势感知平台-开源生态与贡献价值全景-中国开源人才密度地图接口对接 2026-01-29 10:12:33 +08:00
d266377
424a0a1de6 态势感知平台-开源生态与贡献价值全景-开发者活力波形图接口对接 2026-01-29 09:52:43 +08:00
d266377
231d8bd14e 态势感知平台-开源生态与贡献价值全景-明星开发者聚焦接口对接 2026-01-28 17:44:33 +08:00
d266377
6f79cf1f9c 态势感知平台-开源生态与贡献价值全景-社区治理健康度对比接口对接 2026-01-28 17:20:44 +08:00
d266377
89b5a01446 态势感知平台-开源生态与贡献价值全景-高校开源社区/俱乐部数量接口对接 2026-01-28 17:14:33 +08:00
d266377
6c2a33996f 态势感知平台-开源生态与贡献价值全景-中国开源人才密度地图组件抽离 2026-01-28 16:42:01 +08:00
d266377
119b2f0ec5 态势感知平台-开源生态与贡献价值全景-基础设施覆盖率接口对接 2026-01-28 15:41:19 +08:00
d266377
1853de9c3a 态势感知平台-开源生态与贡献价值全景接口定义 2026-01-28 15:28:49 +08:00
d266377
bcab3014ac 态势感知平台-开源生态与贡献价值全景统计概览对接接口 2026-01-28 15:09:53 +08:00
d266377
e66ca622f5 态势感知平台-开源生态与贡献价值全景-明星开发者聚焦组件抽离 2026-01-28 14:54:02 +08:00
d266377
d2603e1a38 态势感知平台-开源生态与贡献价值全景-武汉4项开源数据统计组件抽离 2026-01-28 11:33:12 +08:00
d266377
03a099ea10 态势感知平台-开源生态与贡献价值全景-开发者活力波形图组件抽离 2026-01-28 11:20:44 +08:00
d266377
3ffe0716e6 态势感知平台-开源生态与贡献价值全景-社区治理健康度对比组件抽离 2026-01-28 10:12:57 +08:00
d266377
3ac1ef0d4d 态势感知平台-开源生态与贡献价值全景高校开源社区/俱乐部数量组件抽离 2026-01-28 09:46:59 +08:00
d266377
fa07a197f0 态势感知平台-开源生态与贡献价值全景基础设施覆盖率组件抽离 2026-01-27 17:50:35 +08:00
d266377
f36602bab3 态势感知平台-全球开源风险态势感知监控中心CVE月度趋势接口对接 2026-01-27 16:49:57 +08:00
d266377
e9d8e48514 态势感知平台-全球开源风险态势感知监控中心编程语言安全态势接口对接 2026-01-27 16:40:15 +08:00
d266377
e495e3ec9f 态势感知平台-全球开源风险态势感知监控中心漏洞等级分布对接后端 2026-01-27 16:21:48 +08:00
d266377
3dba3a2189 态势感知中心-全球开源风险态势感知监控中心漏洞语言分布对接接口 2026-01-27 15:49:05 +08:00
d266377
aae51d0b6a 态势感知中心-全球开源风险态势感知监控中心情报流对接接口 2026-01-27 15:27:16 +08:00
d266377
5c8f9273c4 态势感知中心-全球开源风险态势感知中心编程语言安全态势组件抽离 2026-01-27 14:42:50 +08:00
d266377
d0be493658 态势感知平台-全球开源风险态势感知监控中心高危漏洞图组件抽离 2026-01-27 14:40:47 +08:00
d266377
75085051ff 态势感知平台-全球开源风险态势感知监控中心漏洞语言分布图组件抽离 2026-01-27 14:18:30 +08:00
d266377
a8207b44ae 态势感知平台-全球开源风险态势感知监控中心CVE月度趋势图组件抽离 2026-01-27 11:24:49 +08:00
d266377
08ee73c225 Merge branch 'SAP' of http://222.20.126.217:3000/JYHlab_ZLZXB/Situation-Awareness-Platform-Frontend into SAP 2026-01-27 10:50:57 +08:00
d266377
1643ea6e63 态势感知平台-全球开源风险态势感知监控中心高危组件top10接口对接 2026-01-27 10:50:43 +08:00
fmk1023
9fd93c460c 态势感知平台-生产环境接口上下文修改 2026-01-26 18:24:09 +08:00
fmk1023
d62710e910 态势感知平台-生产环境接口上下文修改 2026-01-26 18:11:43 +08:00
fmk1023
f68ced16f8 Merge remote-tracking branch 'origin/SAP' into SAP 2026-01-26 18:03:55 +08:00
fmk1023
5d74295706 态势感知平台-武汉市开源生态建设开源鸿蒙态势分析模块调整 2026-01-26 18:03:44 +08:00
d266377
a70db39a08 态势感知平台-全球开源风险态势感知监控中心-全球威胁情报分析与检测4个接口对接 2026-01-26 17:34:30 +08:00
d266377
d9d6fbdba6 态势感知平台-全球开源风险态势感知监控中心情报流组件补充 2026-01-26 17:32:15 +08:00
d266377
43ca3c13a1 态势感知平台-全球开源风险态势感知监控中心抽离情报流组件 2026-01-26 17:30:14 +08:00
d266377
1085a8f8c8 态势感知中心-全球开源风险态势感知监控中心数据概览优化 2026-01-26 17:25:16 +08:00
d266377
21f068bc9d 态势感知平台-全球开源风险态势感知监控中心行业分布接口对接 2026-01-26 17:23:49 +08:00
fmk1023
a8d8773c63 Merge remote-tracking branch 'origin/SAP' into SAP
# Conflicts:
#	src/views/Jyh/security/index.vue
2026-01-26 15:47:32 +08:00
fmk1023
25f2c6d50a 态势感知平台-全球开源风险态势感知监控中心数据概览接口对接 2026-01-26 15:46:01 +08:00
d266377
181caeb231 Merge branch 'SAP' of http://222.20.126.217:3000/JYHlab_ZLZXB/Situation-Awareness-Platform-Frontend into SAP 2026-01-26 15:40:55 +08:00
d266377
a9e688a4b0 态势感知平台-全球开源风险态势感知监控中心抽离两个组件 2026-01-26 15:40:52 +08:00
fmk1023
666ca8c5a7 态势感知平台-增加武汉鸿蒙项目榜单、武汉开发者贡献榜、武汉企业贡献榜 2026-01-26 10:48:34 +08:00
fmk1023
9f41da5df8 态势感知平台-增加武汉鸿蒙项目榜单、武汉开发者贡献榜、武汉企业贡献榜 2026-01-23 19:08:28 +08:00
fmk1023
4d9f1034d7 Merge remote-tracking branch 'origin/SAP' into SAP
# Conflicts:
#	src/views/Jyh/Community/components/HarmonyEcoCharts.vue
2026-01-23 17:27:21 +08:00
fmk1023
e0281f3b7c 态势感知平台-增加武汉鸿蒙项目榜单 2026-01-23 17:25:10 +08:00
d266377
0aa47a7827 Merge branch 'SAP' of http://222.20.126.217:3000/JYHlab_ZLZXB/Situation-Awareness-Platform-Frontend into SAP 2026-01-23 17:18:14 +08:00
d266377
f113ea5582 态势感知平台-武汉开源城市生态建设页面增加两个榜单 2026-01-23 17:15:31 +08:00
fmk1023
b7f0ce70ae 态势感知平台-数据显示更新 2026-01-23 15:58:36 +08:00
fmk1023
1aef4f7d55 Merge remote-tracking branch 'origin/SAP' into SAP 2026-01-22 17:07:58 +08:00
fmk1023
336b4c2a47 态势感知平台-态势感知接口api定义文件 2026-01-22 17:07:40 +08:00
d266377
c8262ea17d 态势感知平台-态势感知接口api定义文件 2026-01-22 14:54:35 +08:00
fmk1023
944fb16bf4 态势感知平台-态势感知接口api定义文件 2026-01-22 11:37:20 +08:00
fmk1023
b2a702324a 态势感知平台-态势感知接口api定义文件 2026-01-22 11:32:13 +08:00
fmk1023
7e39d3164b Merge branch 'master' into SAP
# Conflicts:
#	src/views/Jyh/Community/index.vue
2026-01-22 10:15:44 +08:00
fmk1023
7b0e00bed5 态势感知平台-增加武汉市开源建设生态页面展示 2026-01-12 17:44:58 +08:00
fmk1023
5f22b7d30e 态势感知平台-武汉市开源建设生态页面数据搜集与修改 2026-01-12 09:20:10 +08:00
fmk1023
d63b9f32bd 态势感知平台-华科开放原子俱乐部Logo增加 2026-01-08 14:55:02 +08:00
fmk1023
cc1514d3c4 态势感知平台-武汉市开源生态建设页面修改,增加武汉开源项目列表、中国城市开源项目数量对比、开源创新联盟优秀项目、城市开源人才数量对比、城市社区数量对比、重点城市高校开源社团数量、各省市开源相关政策发布数量、开放原子校园行·城市活动场次、全国重点城市开源镜像站/节点数量对比等数据可视化展示 2026-01-07 14:05:28 +08:00
fmk1023
907a26cc94 态势感知平台-账号密码登录及验证码接口对接治理平台登录接口 2026-01-05 09:58:49 +08:00
fmk1023
4d9c8ed52c 态势感知平台-增加武汉市开源建设生态页面展示 2026-01-04 15:29:42 +08:00
fmk1023
31da7bdea9 态势感知平台-增加武汉市开源建设生态页面展示 2026-01-04 15:16:50 +08:00
fmk1023
e90006368a 态势感知平台-账号密码登录及验证码接口对接治理平台登录接口 2026-01-04 15:15:51 +08:00
fmk1023
f3b63fdfd9 使用Esbuild压缩、移除Legacy插件、增加optimizeDeps、取消chunk大小计算、提高超大静态资源临界值优化打包速度 2025-12-25 16:11:27 +08:00
fmk1023
29fa1a2199 SAP打包配置修改 2025-12-25 14:54:23 +08:00
fmk1023
b80ff647c9 态势感知平台-项目初始化搭建及菜单修改、增加tabs滚动吸顶效果 2025-12-25 14:36:22 +08:00
d0d9adfde9 Merge branch 'dev' of gitee.com:fumk/open-repo_-portal into dev 2025-12-23 16:14:05 +08:00
81e4f3693b Tcode运营平台改造升级—技术优势部分图片叠加 2025-12-23 16:13:51 +08:00
fmk1023
6fe0681152 Tcode运营平台改造升级-路由跳转增加自动滚动顶部功 2025-12-23 15:35:30 +08:00
e18b165979 Tcode运营平台改造升级—部分图片替换和优化 2025-12-23 10:24:51 +08:00
fmk1023
4edc5527b0 Tcode运营平台改造升级-部分图标替换 2025-12-19 13:48:38 +08:00
fmk1023
b54e84f62e Tcode运营平台改造升级-产品图片优化 2025-12-19 11:28:08 +08:00
fmk1023
8a021d0adc Tcode运营平台改造升级-中国地图配置 2025-12-18 21:14:36 +08:00
fmk1023
5a1b50ba7f Tcode运营平台改造升级-首页样式优化 2025-12-18 20:41:06 +08:00
fmk1023
dc1f4d30ad Tcode运营平台改造升级-态势感知页面样式优化 2025-12-18 20:23:34 +08:00
fmk1023
594e982469 Tcode运营平台改造升级-态势感知页面样式优化 2025-12-18 19:50:16 +08:00
fmk1023
75a2b30776 Tcode运营平台改造升级-态势感知页面样式优化 2025-12-18 19:47:06 +08:00
fmk1023
5e9c7686e5 Tcode运营平台改造升级-全球开源风险态势感知监控中心世界地图修改 2025-12-18 19:34:43 +08:00
fmk1023
abaaeff09e Tcode运营平台改造升级-智能终端应用安全监测平台图片替换 2025-12-18 18:57:56 +08:00
gcw_yTjNWok9
365ebe9964 Merge branch 'dev' of https://gitee.com/fumk/open-repo_-portal into dev 2025-12-18 17:47:31 +08:00
gcw_yTjNWok9
562eae4342 修改图片 2025-12-18 17:47:14 +08:00
djgj
ed608275ed qq 2025-12-18 17:43:34 +08:00
djgj
bd1c1902b0 链路平台 2025-12-18 17:39:12 +08:00
fmk1023
7c87be069f Merge remote-tracking branch 'origin/dev' into dev 2025-12-18 17:30:36 +08:00
fmk1023
785d783226 Tcode运营平台改造升级-Header组件改造 2025-12-18 17:30:10 +08:00
gcw_yTjNWok9
dda0aecfa7 Merge branch 'dev' of https://gitee.com/fumk/open-repo_-portal into dev 2025-12-18 17:15:57 +08:00
gcw_yTjNWok9
38f8b376af feat: 更新金银湖代码安全大模型架构图片和优化显示效果
- 添加新的大模型架构示意图 (1.png)
- 更新 product2.vue 中的图片引用
- 移除图片显示的3秒延迟动画,优化用户体验
- 添加相关的图片资源文件
2025-12-18 17:15:44 +08:00
0f0cbb9998 Tcode运营平台改造升级-文本样式修改 2025-12-18 17:13:53 +08:00
fmk1023
a7d802a40f Tcode运营平台改造升级-定价页面根据UI样式进行美化 2025-12-18 17:08:11 +08:00
798f98e194 Merge branch 'dev' of gitee.com:fumk/open-repo_-portal into dev 2025-12-18 17:05:18 +08:00
adc4a67221 Tcode平台改造升级-图片替换-可信开源态势感知平台 2025-12-18 17:05:02 +08:00
fmk1023
1714534d3b Tcode运营平台改造升级-产品服务页面根据UI样式进行美化 2025-12-18 17:02:53 +08:00
fmk1023
5c793a2759 Tcode运营平台改造升级-文字修改 2025-12-18 16:17:42 +08:00
fmk1023
d9baf96cce Tcode运营平台改造升级-Header组件改造 2025-12-18 16:05:12 +08:00
djgj
e698c4f166 完成ecosystem页面 2025-12-18 14:09:34 +08:00
gcw_yTjNWok9
e3ee78b9f4 更改文件路径 2025-12-18 14:02:38 +08:00
gcw_yTjNWok9
12cf936916 地图部分修改 2025-12-18 13:46:53 +08:00
gcw_yTjNWok9
42aa1eda2b 解决合并冲突,使用远程版本 2025-12-18 13:39:13 +08:00
gcw_yTjNWok9
7e6321a7b9 删除第一张风险监控地图(SecurityRiskMap)及相关代码 2025-12-18 13:37:44 +08:00
8cab400008 Tcode平台改造升级-编程语言安全态势图表 2025-12-18 11:31:18 +08:00
e79572cd34 Tcode平台改造升级-漏洞等级分布图表 2025-12-18 11:22:26 +08:00
678dd3842a Tcode平台改造升级-漏洞语言分布 2025-12-18 11:06:07 +08:00
be36557c0c Tcode平台改造升级-情报流图表 2025-12-18 10:51:53 +08:00
634726b75e Tcode平台改造升级-CVE 月度趋势图表 2025-12-18 10:36:08 +08:00
b51d981d83 Tcode平台改造升级-行业分布图表 2025-12-18 10:12:13 +08:00
db4ad5744c Tcode平台改造升级-高危组件 Top 10 图表 2025-12-18 10:06:41 +08:00
fmk1023
bb17c69ba9 Tcode平台改造升级-产品图片样式优化 2025-12-18 09:30:53 +08:00
fmk1023
58579b7ad3 Tcode平台改造升级-开源生态与贡献价值全景页面路由修改 2025-12-18 09:30:34 +08:00
fmk1023
8885af40c2 Tcode平台改造升级-全球开源风险态势感知监控中心页面路由修改 2025-12-18 09:22:15 +08:00
djgj
2906b515e4 更新了四个产品平台的内容和样式 2025-12-17 16:44:38 +08:00
ffab7ede4a Tcode文稿修改-产品服务详情页部分 2025-12-17 15:38:52 +08:00
fmk1023
43ab6ec1c3 Tcode平台改造升级-header组件修改 2025-12-16 14:14:38 +08:00
fmk1023
dc09f426b4 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	src/components/Header/index.vue
2025-12-16 14:14:22 +08:00
fmk1023
8f208d4b73 Tcode平台改造升级-header组件修改 2025-12-16 14:10:28 +08:00
djgj
a812a96d82 简化导航栏文字,合并某些服务详情 2025-12-16 10:20:00 +08:00
djgj
59d5988e2a Merge branch 'master' into dev
t merge master
2025-12-15 17:18:54 +08:00
djgj
168a885a7d 更新产品四(智能终端安全监测平台)的服务描述:极速扫描、深度分析、持续免疫、威胁情报四大服务 2025-12-15 17:12:33 +08:00
fmk1023
d8a8629c4b Merge remote-tracking branch 'origin/dev' into dev 2025-12-15 16:58:27 +08:00
fmk1023
ca7eee0fc2 Tcode平台改造升级-首页页轮播图固定一个图展示 2025-12-15 16:58:17 +08:00
722a4c2a30 Tcode文稿修改-智能终端应用安全监测平台(源端) 2025-12-15 16:46:40 +08:00
ea34772999 Tcode文稿修改-供应链安全检测系统(源链) 2025-12-15 16:41:00 +08:00
5114f2cfee Tcode文稿修改-金银湖代码安全大模型(源鉴) 2025-12-15 16:30:15 +08:00
a8b8192ed4 Tcode文稿修改-可信开源态势感知平台(源感) 2025-12-15 16:12:59 +08:00
fmk1023
68ef282e65 Tcode平台改造升级-首页菜单修改 2025-12-15 11:14:37 +08:00
fmk1023
1ee6a6c330 Tcode平台改造升级-登录跳转相关修改 2025-12-12 17:56:36 +08:00
fmk1023
ea2bd0aa5a Tcode平台改造升级-AI助手增加工具菜单 2025-12-12 15:14:50 +08:00
fmk1023
97dbec995f Tcode平台改造升级-AI助手增加工具菜单 2025-12-12 09:22:31 +08:00
fmk1023
51078acc1d Tcode平台改造升级-产品服务页面修改开发 2025-12-11 18:27:57 +08:00
fmk1023
d6b5456e43 Tcode平台改造升级-武汉市开源建设生态页面开发 2025-12-11 17:32:06 +08:00
fmk1023
0f84f23c63 Tcode平台改造升级-首屏样式修改开发 2025-12-11 15:28:46 +08:00
fmk1023
f13c2d06b3 Tcode平台改造升级-首屏样式修改开发 2025-12-11 15:22:27 +08:00
fmk1023
6bd1db5ed7 Tcode平台改造升级-态势感知指挥中心页面优化,使用滚动模式会使页面短暂白屏 2025-12-11 11:23:03 +08:00
fmk1023
6c477d923b Tcode平台改造升级-LinePage页面完成 2025-12-11 10:08:18 +08:00
fmk1023
a61dbfd821 Tcode平台改造升级-LinePage页面完成 2025-12-10 18:28:55 +08:00
fmk1023
7924714d0e Tcode平台改造升级-设置相关配置完成 2025-12-10 16:11:05 +08:00
fmk1023
a630f40b76 Tcode平台改造升级-删除多余备注 2025-12-10 15:46:18 +08:00
fmk1023
61f8e017de Tcode平台改造升级-用户工作台试用页面及支付页面开发 2025-12-10 15:25:35 +08:00
fmk1023
356810f316 Tcode平台改造升级-用户工作台页面样式优化及邀请你的团队功能开发 2025-12-10 14:34:34 +08:00
fmk1023
fd4f9bc73d Tcode平台改造升级-用户工作台页面样式优化及邀请你的团队功能开发 2025-12-10 14:34:29 +08:00
fmk1023
44467bc22a Tcode平台改造升级-产品服务详情页面开发 2025-12-10 10:27:52 +08:00
fmk1023
e8eabfab9f Tcode平台改造升级-产品服务详情页面开发 2025-12-10 10:26:48 +08:00
fmk1023
c249b9d70f Tcode平台改造升级-态势感知中心页面开发及菜单栏展示修改 2025-12-09 18:12:33 +08:00
fmk1023
d72546c81b Tcode平台改造升级-态势感知中心页面开发 2025-12-09 14:56:29 +08:00
fmk1023
87c2777923 Tcode平台改造升级-控制台登录组件开发及用户工作台页面开发 2025-12-09 14:31:11 +08:00
fmk1023
af7578ec2a Tcode平台改造升级-项目名称openRepoPortalV2改为tcode 2025-12-05 17:48:00 +08:00
fmk1023
1e2d8f775d Tcode平台改造升级-安全事件中心页面开发 2025-12-05 16:54:27 +08:00
fmk1023
e7150457c4 Tcode平台改造升级-漏洞数据库页面开发 2025-12-05 16:15:39 +08:00
fmk1023
5550b0fb7f Tcode平台改造升级-漏洞数据库页面开发 2025-12-05 16:08:59 +08:00
fmk1023
28ea0cc27f Tcode平台改造升级-开源服务专区菜单组件完成 2025-12-05 11:36:11 +08:00
fmk1023
18319e66c5 Tcode平台改造升级-关于我们页面开发 2025-12-05 10:54:13 +08:00
fmk1023
b032e2b074 Tcode平台改造升级-定价页面设计与前端开发 2025-12-04 17:29:35 +08:00
fmk1023
63d4751838 Tcode平台改造升级-解决方案页面开发 2025-12-04 14:58:14 +08:00
fmk1023
c7de332275 Tcode平台改造升级-底部页脚信息模块开发 2025-12-04 13:58:20 +08:00
fmk1023
fcb247395b Tcode平台改造升级-产品服务菜单修改 2025-12-04 11:22:28 +08:00
fmk1023
9e86d1e6bc Tcode平台改造升级-系统菜单框架完成、首页页面开发 2025-12-04 10:14:46 +08:00
fmk1023
e76110239d Tcode平台改造升级-header头组件开发完成 2025-12-03 11:21:49 +08:00
fmk1023
86d26f161a Tcode平台改造升级-AI助手菜单显示功能修改 2025-12-03 09:53:16 +08:00
fmk1023
e01a050f80 Tcode平台改造升级-页面字段展示优化及定价页面路由创建 2025-12-01 10:20:14 +08:00
fmk1023
4440333736 Tcode平台改造升级-页面字段展示优化 2025-11-28 17:56:28 +08:00
fmk1023
d7dfa9bf8d openRepoPortalV2打包命名修改 2025-11-28 14:57:04 +08:00
fmk1023
0ab4649909 安全检测样式优化 2025-11-28 14:48:13 +08:00
fmk1023
ae00eb3d6f 首页样式优化 2025-11-28 14:32:07 +08:00
fmk1023
cf25a6feb1 TODO:安全态势地址修改 2025-11-28 09:05:28 +08:00
fmk1023
0d266e8a03 可信代码库V2版-首页展示页面原型开发 2025-11-19 17:39:35 +08:00
fmk1023
f1f05c11f6 可信代码库V2版-首页展示页面原型开发 2025-11-11 14:48:43 +08:00
fmk1023
f5b59fadd9 可信代码库V2版-安全智库页面原型开发、安全检测中心页面原型开发、关于我们页面原型开发 2025-11-06 10:54:58 +08:00
41 changed files with 10357 additions and 2946 deletions

2
.env
View File

@@ -1,4 +1,4 @@
VITE_APP_TITLE = '可信开源代码库'
VITE_APP_TITLE = 'Tcode'
VITE_TEST_REPOID = 34
VITE_SECRET_KEY = 'SA!nUNPZ5o!OSV&B'

View File

@@ -5,10 +5,10 @@ VITE_ENV = 'development'
VITE_HOST = 'https://test.gitcode.net'
# 接口地址
VITE_API_HOST = '/gateway/official'
VITE_API_HOST = 'http://222.20.126.217:8100/'
# devPress接口地址
VITE_DEV_API_HOST = '/gateway/official'
VITE_DEV_API_HOST = 'http://222.20.126.217:8100/'
# devPress地址
VITE_DEV_HOST = 'https://test.gitcode.net'

View File

@@ -39,7 +39,7 @@ VITE_USER_CENTER_HOST = 'https://local.gitcode.net:8002'
# ai-copilot 子应用
VITE_COPILOT_HOST = 'https://local.gitcode.net:8003/child/aichat-app'
VITE_BASE_URL=/openRepoPortal/
VITE_BASE_URL=/SAP/
# AI大模型接口地址
VITE_AI_API_HOST = 'http://222.20.126.208:8089'

View File

@@ -37,7 +37,7 @@ VITE_USER_CENTER_HOST = 'https://pre-usercenter-app.gitcode.com'
# ai-copilot 子应用
VITE_COPILOT_HOST = 'https://pre-copilot-app.gitcode.com'
VITE_BASE_URL=/openRepoPortal/
VITE_BASE_URL=/SAP/
# AI大模型接口地址
VITE_AI_API_HOST = 'http://222.20.126.208:8089'

View File

@@ -2,13 +2,13 @@
VITE_ENV = 'production'
# 主域名
VITE_HOST = 'http://222.20.126.217:10010/openRepoPortal'
VITE_HOST = 'http://222.20.126.217:10010/SAP'
# 接口地址
VITE_API_HOST = '/gateway/official'
VITE_API_HOST = 'http://222.20.126.217:8100/'
# devPress接口地址
VITE_DEV_API_HOST = '/gateway/official'
VITE_DEV_API_HOST = 'http://222.20.126.217:8100/'
# devPress地址
VITE_DEV_HOST = 'https://gitcode.com'
@@ -26,7 +26,7 @@ VITE_CSDN_URL = 'https://web-api.gitcode.com/oauth/login/csdn'
VITE_ORG_COMMUNITY = 'https://gitcode.com'
# 静态资源域名
VITE_STATIC_HOST = 'http://222.20.126.217:10010/openRepoPortal'
VITE_STATIC_HOST = 'http://222.20.126.217:10010/tcode'
# 主页搜索子应用
VITE_CHILD_HOMEWEB_HOST = 'https://homepage-app.gitcode.com'
@@ -37,7 +37,7 @@ VITE_USER_CENTER_HOST = 'https://usercenter-app.gitcode.com'
# ai-copilot 子应用
VITE_COPILOT_HOST = 'https://copilot-app.gitcode.com'
VITE_BASE_URL=/openRepoPortal/
VITE_BASE_URL=/SAP/
# AI大模型接口地址
VITE_AI_API_HOST = 'http://222.20.126.208:8087'

4
.gitignore vendored
View File

@@ -12,7 +12,7 @@ lerna-debug.log*
node_modules
.DS_Store
dist
openRepoPortal
SAP
nginx
dist-ssr
coverage
@@ -39,3 +39,5 @@ components.d.ts
# 组件汇总文件,仅限开发本地浏览
component.md
./deploy.js

View File

@@ -1,4 +1,4 @@
# 可信开源代码库
# Tcode
架构:基于[create-vue](https://github.com/vuejs/create-vue)构建vite+vue3+pinia+scss
组件库:[Vue DevUI](https://vue-devui.github.io/)

46
china.js Normal file

File diff suppressed because one or more lines are too long

75
deploy.js Normal file
View File

@@ -0,0 +1,75 @@
import { Client } from 'ssh2';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const serverConfig = {
host: '222.20.126.217',
port: 22,
username: 'admini',
password: 'zlzxb@1888',
remotePath: '/home/admini/ossrpp/base/nginx/dist/SAP/'
};
async function uploadDirectory(sftp, localDir, remoteDir) {
const items = fs.readdirSync(localDir);
// 确保远程目录存在
try {
await new Promise((res) => sftp.mkdir(remoteDir, { mode: '0755' }, () => res()));
} catch (e) {}
for (const item of items) {
const localPath = path.join(localDir, item);
const remotePath = path.join(remoteDir, item).replace(/\\/g, '/'); // 转换为Linux路径
if (fs.statSync(localPath).isDirectory()) {
await uploadDirectory(sftp, localPath, remotePath);
} else {
await new Promise((resolve, reject) => {
console.log(`🚀 上传中: ${item}`);
sftp.fastPut(localPath, remotePath, (err) => {
if (err) reject(err);
else resolve();
});
});
}
}
}
async function startDeploy() {
const conn = new Client();
console.log('✨ 开始部署流程...');
conn.on('ready', () => {
console.log('✅ SSH 连接成功!');
conn.sftp(async (err, sftp) => {
if (err) throw err;
try {
const localDist = path.join(__dirname, 'SAP');
await uploadDirectory(sftp, localDist, serverConfig.remotePath);
console.log('🎉 所有文件上传成功!');
conn.end();
} catch (uploadErr) {
console.error('❌ 上传失败:', uploadErr);
conn.end();
}
});
}).on('error', (err) => {
console.error('❌ 连接出错 (检查IP或密码):', err.message);
}).on('close', () => {
console.log('🔌 连接已关闭');
process.exit(0);
}).connect({
host: serverConfig.host,
port: serverConfig.port,
username: serverConfig.username,
password: serverConfig.password,
readyTimeout: 10000 // 10秒超时
});
}
startDeploy();

View File

@@ -9,7 +9,7 @@
<link rel="dns-prefetch" href="//usercenter-app.gitcode.com">
<link rel="dns-prefetch" href="//res.hc-cdn.com">
<!-- <meta name="referrer" content="no-referrer"> -->
<title>可信开源代码库</title>
<title>Tcode</title>
<style type="text/css">
.icon {
width: 1em; height: 1em;
@@ -23,19 +23,4 @@
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
<!-- <script type="text/javascript" src="https://cdn-static.gitcode.com/js/gitcode-icon.js"></script>-->
<!-- <script type="text/javascript" src="https://cdn-static.gitcode.com/js/event-msg.min.0.0.2.js"></script>-->
<!-- <script type="text/javascript" src="https://cdn-static.gitcode.com/js/lottie.min.js" defer></script>-->
<!-- <script type="text/javascript" src="https://cdn-static.gitcode.com/js/furion.js" defer></script>-->
<!-- baidu统计 -->
<script>
var _hmt = _hmt || [];
_hmt.push(['_setAutoPageview', false]);
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?62047c952451105d57bab2c4af9ce85b";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</html>

733
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "gitcode-v2",
"name": "SAP",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gitcode-v2",
"name": "SAP",
"version": "0.0.0",
"dependencies": {
"@devui-design/icons": "^1.4.0",
@@ -14,15 +14,18 @@
"@sentry/vue": "^7.101.1",
"@types/crypto-js": "^4.1.1",
"@vueuse/core": "^10.2.1",
"ant-design-vue": "4.x",
"axios": "^1.6.8",
"canvas-confetti": "^1.9.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"devui-theme": "^0.0.7",
"dompurify": "^3.0.5",
"echarts": "^6.0.0",
"highlight.js": "^11.11.1",
"highlightjs-line-numbers.js": "^2.9.0",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.5.4",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lottie-web": "^5.12.2",
@@ -37,6 +40,7 @@
"swiper": "^10.3.1",
"vue": "^3.3.4",
"vue-devui": "^1.6.31",
"vue-fast-marquee": "^1.0.6",
"vue-router": "^4.2.2",
"xlsx": "^0.18.5",
"yaml": "^2.4.1"
@@ -61,6 +65,7 @@
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"ssh2": "^1.17.0",
"tailwindcss": "^3.3.3",
"terser": "^5.39.0",
"typescript": "~5.0.4",
@@ -103,6 +108,34 @@
"node": ">=6.0.0"
}
},
"node_modules/@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz",
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.0"
}
},
"node_modules/@ant-design/icons-svg": {
"version": "4.4.2",
"resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
"license": "MIT"
},
"node_modules/@ant-design/icons-vue": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz",
"integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.2.1"
},
"peerDependencies": {
"vue": ">=3.0.3"
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@@ -1699,7 +1732,6 @@
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
"integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -1710,8 +1742,7 @@
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/@babel/template": {
"version": "7.24.0",
@@ -1765,11 +1796,389 @@
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
"integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@devui-design/icons": {
"version": "1.4.0",
"resolved": "https://mirrors.cloud.tencent.com/npm/@devui-design/icons/-/icons-1.4.0.tgz",
"integrity": "sha512-taAX1RNW0QHUqQTRPqLTYTB2PZIqUplhWeF4hcmWkSTjpWlDNI40DssG/WRb3sISkfBk/4BMUxxC5XeTL3jo7A=="
},
"node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT"
},
"node_modules/@emotion/unitless": {
"version": "0.8.1",
"resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
"license": "MIT"
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
@@ -2322,6 +2731,16 @@
"vue": "2.x || 3.x"
}
},
"node_modules/@simonwep/pickr": {
"version": "1.8.2",
"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
"integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==",
"license": "MIT",
"dependencies": {
"core-js": "^3.15.1",
"nanopop": "^2.1.0"
}
},
"node_modules/@tsconfig/node18": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-2.0.1.tgz",
@@ -3196,6 +3615,46 @@
"node": ">=4"
}
},
"node_modules/ant-design-vue": {
"version": "4.2.6",
"resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz",
"integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^7.0.0",
"@babel/runtime": "^7.10.5",
"@ctrl/tinycolor": "^3.5.0",
"@emotion/hash": "^0.9.0",
"@emotion/unitless": "^0.8.0",
"@simonwep/pickr": "~1.8.0",
"array-tree-filter": "^2.1.0",
"async-validator": "^4.0.0",
"csstype": "^3.1.1",
"dayjs": "^1.10.5",
"dom-align": "^1.12.1",
"dom-scroll-into-view": "^2.0.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.15",
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.25",
"shallow-equal": "^1.0.0",
"stylis": "^4.1.3",
"throttle-debounce": "^5.0.0",
"vue-types": "^3.0.0",
"warning": "^4.0.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ant-design-vue"
},
"peerDependencies": {
"vue": ">=3.2.0"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -3242,6 +3701,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/array-tree-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==",
"license": "MIT"
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -3273,6 +3738,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
@@ -3390,6 +3865,16 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3468,6 +3953,16 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/buildcheck": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz",
"integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==",
"dev": true,
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -3686,6 +4181,12 @@
"node": ">=18"
}
},
"node_modules/compute-scroll-into-view": {
"version": "1.0.20",
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
"license": "MIT"
},
"node_modules/computeds": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz",
@@ -3708,7 +4209,6 @@
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
"integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
"dev": true,
"hasInstallScript": true,
"funding": {
"type": "opencollective",
@@ -3728,6 +4228,21 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "~0.0.6",
"nan": "^2.19.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://mirrors.cloud.tencent.com/npm/crc-32/-/crc-32-1.2.2.tgz",
@@ -4693,6 +5208,18 @@
"node": ">=6.0.0"
}
},
"node_modules/dom-align": {
"version": "1.12.4",
"resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz",
"integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==",
"license": "MIT"
},
"node_modules/dom-scroll-into-view": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==",
"license": "MIT"
},
"node_modules/dompurify": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz",
@@ -4705,12 +5232,13 @@
"dev": true
},
"node_modules/echarts": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.3.tgz",
"integrity": "sha512-BRw2serInRwO5SIwRviZ6Xgm5Lb7irgz+sLiFMmy/HOaf4SQ+7oYqxKzRHAKp4xHQ05AuHw1xvoQWJjDQq/FGw==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.3.2"
"zrender": "6.0.0"
}
},
"node_modules/echarts/node_modules/tslib": {
@@ -5517,6 +6045,21 @@
"dev": true,
"peer": true
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -6144,6 +6687,15 @@
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz",
"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -6294,8 +6846,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -6310,6 +6861,12 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsencrypt": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.5.4.tgz",
"integrity": "sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==",
"license": "MIT"
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -6516,6 +7073,18 @@
"dev": true,
"peer": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lottie-web": {
"version": "5.12.2",
"resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz",
@@ -6725,6 +7294,14 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/nan": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz",
"integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://mirrors.cloud.tencent.com/npm/nanoid/-/nanoid-3.3.11.tgz",
@@ -6742,6 +7319,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/nanopop": {
"version": "2.4.2",
"resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz",
"integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==",
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -7667,6 +8250,12 @@
"node": ">=0.10.0"
}
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
"license": "MIT"
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -7866,6 +8455,15 @@
"node": ">=14.0.0"
}
},
"node_modules/scroll-into-view-if-needed": {
"version": "2.2.31",
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
"license": "MIT",
"dependencies": {
"compute-scroll-into-view": "^1.0.20"
}
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
@@ -7911,6 +8509,12 @@
"node": ">= 0.4"
}
},
"node_modules/shallow-equal": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -8063,6 +8667,24 @@
"node": ">=0.8"
}
},
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.23.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -8314,7 +8936,7 @@
},
"node_modules/swiper": {
"version": "10.3.1",
"resolved": "https://mirrors.cloud.tencent.com/npm/swiper/-/swiper-10.3.1.tgz",
"resolved": "https://registry.npmmirror.com/swiper/-/swiper-10.3.1.tgz",
"integrity": "sha512-24Wk3YUdZHxjc9faID97GTu6xnLNia+adMt6qMTZG/HgdSUt4fS0REsGUXJOgpTED0Amh/j+gRGQxsLayJUlBQ==",
"funding": [
{
@@ -8326,6 +8948,7 @@
"url": "http://opencollective.com/swiper"
}
],
"license": "MIT",
"engines": {
"node": ">= 4.7.0"
}
@@ -8425,6 +9048,15 @@
"node": ">=0.8"
}
},
"node_modules/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
"license": "MIT",
"engines": {
"node": ">=12.22"
}
},
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
@@ -8469,6 +9101,13 @@
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"dev": true,
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -8930,6 +9569,16 @@
"resolved": "https://registry.npmjs.org/devui-theme/-/devui-theme-0.0.1.tgz",
"integrity": "sha512-5nF6fChlsXKeAtvkaAF4bZ0NMiEAbzwqQ9XZQiNuM0RRFz5lW29nnbhfDCNPkmnw5ZyCVyXqYwVRBIZrwZHXrA=="
},
"node_modules/vue-devui/node_modules/echarts": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.3.3.tgz",
"integrity": "sha512-BRw2serInRwO5SIwRviZ6Xgm5Lb7irgz+sLiFMmy/HOaf4SQ+7oYqxKzRHAKp4xHQ05AuHw1xvoQWJjDQq/FGw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.3.2"
}
},
"node_modules/vue-devui/node_modules/entities": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
@@ -8966,11 +9615,26 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
},
"node_modules/vue-devui/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/vue-devui/node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"node_modules/vue-devui/node_modules/zrender": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.2.tgz",
"integrity": "sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/vue-eslint-parser": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
@@ -9028,6 +9692,15 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/vue-fast-marquee": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vue-fast-marquee/-/vue-fast-marquee-1.0.6.tgz",
"integrity": "sha512-VMqaeuBSS3L55Q84EuLJPHZ4Zv4UVBDXmh+2hb1IBTLKt/FmurJnWVXv8BNnbQSupA4kHbPlWDLwp+kvEMOF7A==",
"license": "MIT",
"peerDependencies": {
"vue": ">=3.2.0"
}
},
"node_modules/vue-router": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
@@ -9102,6 +9775,30 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/vue-types": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz",
"integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
"license": "MIT",
"dependencies": {
"is-plain-object": "3.0.1"
},
"engines": {
"node": ">=10.15.0"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9392,9 +10089,10 @@
}
},
"node_modules/zrender": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.2.tgz",
"integrity": "sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
@@ -9402,7 +10100,8 @@
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
}
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "gitcode-v2",
"name": "SAP",
"version": "0.0.0",
"private": true,
"scripts": {
@@ -16,7 +16,8 @@
"preview": "vite preview",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
"format": "prettier --write src/",
"deploy": "node deploy.js"
},
"dependencies": {
"@devui-design/icons": "^1.4.0",
@@ -25,15 +26,18 @@
"@sentry/vue": "^7.101.1",
"@types/crypto-js": "^4.1.1",
"@vueuse/core": "^10.2.1",
"ant-design-vue": "4.x",
"axios": "^1.6.8",
"canvas-confetti": "^1.9.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"devui-theme": "^0.0.7",
"dompurify": "^3.0.5",
"echarts": "^6.0.0",
"highlight.js": "^11.11.1",
"highlightjs-line-numbers.js": "^2.9.0",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.5.4",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lottie-web": "^5.12.2",
@@ -48,6 +52,7 @@
"swiper": "^10.3.1",
"vue": "^3.3.4",
"vue-devui": "^1.6.31",
"vue-fast-marquee": "^1.0.6",
"vue-router": "^4.2.2",
"xlsx": "^0.18.5",
"yaml": "^2.4.1"
@@ -72,6 +77,7 @@
"prettier": "^2.8.8",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"ssh2": "^1.17.0",
"tailwindcss": "^3.3.3",
"terser": "^5.39.0",
"typescript": "~5.0.4",

453
src/api/jyh/situation.ts Normal file
View File

@@ -0,0 +1,453 @@
import { reqCatchV2, type catchRt } from '@/utils/catch';
import request from '@/utils/request';
import axios from 'axios';
// 详情-获取版本信息
export function releaseInfo(params: { softwareId: string }): Promise<any> {
const { softwareId } = params;
return request({
url: `/api/v1/repo/release/info?softwareId=${softwareId}`,
method: 'get'
});
}
// 详情-搜索结果列表
export function releasePage(data): Promise<any> {
return request({
url: `/api/v1/repo/release/page`,
method: 'post',
data
});
}
// 全球开源风险态势感知监控中心-统计概览
export function overviewDataPage1(data): Promise<any> {
return request({
url: `/api/v1/page1/overviewData`,
method: 'get',
data
});
}
// 全球开源风险态势感知监控中心-地图-漏洞类型分布
export function mapVulnStatsPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/map/vulnStats`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-地图-威胁情报地域排行
export function mapRegionStatsPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/map/regionStats`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-地图-实时情报检测
export function mapVulnTypeStatsPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/map/alerts`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-地图-地图上的数据
export function mapDataPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/map/mapData`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-行业分布饼图
export function industryDistributionPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/industryDistribution`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-高危组件 Top 10 柱状图
export function highRiskComponentPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/highRiskComponent`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-情报流
export function feedListPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/feedList`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-CVE 月度趋势-全球数据
export function cveDataGlobalPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/cveData/global`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-CVE 月度趋势-区域数据
export function cveDataRegionalPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/cveData/regional`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-漏洞语言分布堆叠图
export function languageTrendPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/languageTrend`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-漏洞等级分布
export function vulnerabilitySeverityPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/vulnerabilitySeverity`,
method: 'get',
params
});
}
// 全球开源风险态势感知监控中心-编程语言安全态势气泡图
export function scatterDataPage1(params?: any): Promise<any> {
return request({
url: `/api/v1/page1/scatterData`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-统计概览
export function overviewDataPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/overviewData`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-全球开源企业贡献榜 Top 30 (全部 & 中国 & 美国)
export function rawEnterpriseDataPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/rawEnterpriseData`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-基础设施覆盖率
export function infrastructurePage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/infrastructure`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-高校开源社团/俱乐部数量柱状图
export function universityClubsPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/universityClubs`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-社区治理健康度对比柱状图
export function communityHealthPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/communityHealth`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-语言与技术竞争 (主流语言与新兴语言)
export function languageTechPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/languageTech`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-明星开发者聚焦
export function excellentDeveloperPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/excellentDeveloper`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-开发者活力波形图
export function vitalityPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/vitalityData`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-开源项目热力图
export function projectHeatMapPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/projectHeatmap`,
method: 'get',
params
});
}
// 开源生态与贡献价值全景-中国开源人才密度地图
export function talentMapDataPage2(params?: any): Promise<any> {
return request({
url: `/api/v1/page2/talentMapData`,
method: 'get',
params
});
}
// 武汉市开源生态建设-统计概览1
export function hmStatsBarPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmStatsBar`,
method: 'get',
params
});
}
// 武汉市开源生态建设-统计概览2
export function statsBarPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/statsBar`,
method: 'get',
params
});
}
// 武汉市开源生态建设-鸿蒙项目城市分布
export function hmCityDataPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmCityData`,
method: 'get',
params
});
}
// 武汉市开源生态建设-鸿蒙开源项目
export function hmProjectsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmProjects`,
method: 'get',
params
});
}
// 武汉市开源生态建设-武汉鸿蒙项目
export function whProjectsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/whProjects`,
method: 'get',
params
});
}
// 武汉市开源生态建设-开发者贡献榜
export function hmDevelopersPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmDevelopers`,
method: 'get',
params
});
}
// 武汉市开源生态建设-开发者分布
export function hmDevCityDataPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmDevCityData`,
method: 'get',
params
});
}
// 武汉市开源生态建设-武汉开发者贡献榜
export function whHmDevelopersPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/whHmDevelopers`,
method: 'get',
params
});
}
// 武汉市开源生态建设-开源企业分布
export function hmEnterpriseCityDataPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmEnterpriseCityData`,
method: 'get',
params
});
}
// 武汉市开源生态建设-开源企业贡献榜
export function hmEnterprisesPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/hmEnterprises`,
method: 'get',
params
});
}
// 武汉市开源生态建设-武汉企业贡献榜
export function whEnterprisesPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/whEnterprises`,
method: 'get',
params
});
}
// 武汉市开源生态建设-全国高校分布
export function uniCityDataPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/uniCityData`,
method: 'get',
params
});
}
// 武汉市开源生态建设-高校贡献者榜
export function uniContributorsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/uniContributors`,
method: 'get',
params
});
}
// 武汉市开源生态建设-武汉优质开源项目榜
export function wuhanGithubProjectsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/wuhanGithubProjects`,
method: 'get',
params
});
}
// 武汉市开源生态建设-2024武汉优秀开源项目
export function chinaExcellentProjectsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/chinaExcellentProjects`,
method: 'get',
params
});
}
// 武汉市开源生态建设-2025武汉优秀开源项目
export function excellentProjectsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/excellentProjects`,
method: 'get',
params
});
}
// 武汉市开源生态建设-城市社区数量排行
export function communityRankChartPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/communityRankChart`,
method: 'get',
params
});
}
// 武汉市开源生态建设-武汉知名开源社区
export function wuhanCommunitiesPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/wuhanCommunities`,
method: 'get',
params
});
}
// 武汉市开源生态建设-明星开发者名单
export function developerNamesPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/developerNames`,
method: 'get',
params
});
}
// 武汉市开源生态建设-城市项目分布
export function cityProjectPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/cityProject`,
method: 'get',
params
});
}
// 武汉市开源生态建设-城市开源人才数量对比
export function talentPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/talent`,
method: 'get',
params
});
}
// 武汉市开源生态建设-重点城市高校开源开源社区数量
export function ClubPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/club`,
method: 'get',
params
});
}
// 武汉市开源生态建设-各省相关政策数量
export function policyPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/policy`,
method: 'get',
params
});
}
// 武汉市开源生态建设-校园行城市活动场次
export function campusPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/campus`,
method: 'get',
params
});
}
// 武汉市开源生态建设-城市镜像站/节点数量对比
export function mirrorsPage3(params?: any): Promise<any> {
return request({
url: `/api/v1/page3/mirrors`,
method: 'get',
params
});
}

View File

@@ -52,7 +52,7 @@ export const injectRouter = (router) => {
// 判断命名空间类型
const globalStore = useGlobalInfoStore();
globalStore.setNamespaceType(-1); // 重置namespaceType防止非组织路由请求组织信息
globalStore.setLanguageList();
// globalStore.setLanguageList();
if (to.meta.pageType === 'userOrOrg') {
const { namespace } = to.params;
const params = {

View File

@@ -0,0 +1,137 @@
<template>
<div class="stats-glass-bar">
<div class="stat-node">
<span class="stat-value">150+</span>
<span class="stat-desc">开源企业</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">12</span>
<span class="stat-desc">武汉开源社区数量</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">70W+</span>
<span class="stat-desc">武汉开源开发者数量</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">500+</span>
<span class="stat-desc">武汉开源项目数量</span>
</div>
</div>
</template>
<script>
export default {
name: "StatsBar"
}
</script>
<style lang="scss" scoped>
.stats-glass-bar {
display: flex;
align-items: center;
justify-content: space-between;
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
//margin: 2rem 0;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
/* 极细微的边框勾勒 */
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
/* 外阴影与内发光,增加悬浮感 */
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3),
inset 0 0 20px rgba(255, 255, 255, 0.02);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(56, 189, 248, 0.4);
background: rgba(255, 255, 255, 0.04);
transform: translateY(-2px);
}
}
.stat-node {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
.stat-value {
text-shadow: 0 0 25px rgba(56, 189, 248, 0.8);
}
}
}
.stat-value {
/* 科技感亮蓝渐变 */
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 2.25rem;
font-weight: 800;
background: linear-gradient(180deg, #fff 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* 基础发光效果 */
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3));
letter-spacing: -1px;
}
.stat-desc {
/* 在黑底上使用半透明白色,更有高级感 */
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.1rem;
text-transform: uppercase;
}
.stat-separator {
/* 渐隐分割线 */
width: 1px;
height: 3rem;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0)
);
margin: 0 1rem;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.stats-glass-bar {
padding: 2rem;
}
.stat-value {
font-size: 1.75rem;
}
}
@media (max-width: 768px) {
.stats-glass-bar {
flex-direction: column;
gap: 2rem;
}
.stat-separator {
width: 50%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255,255,255,0.2), transparent);
}
}
</style>

View File

@@ -0,0 +1,210 @@
<template>
<div class="stats-glass-bar">
<div class="stat-node">
<span class="stat-value">{{ hmDevelopers }}</span>
<span class="stat-desc">鸿蒙开发者</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ hmCertificationDevelopers }}</span>
<span class="stat-desc">鸿蒙认证开发者</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ whHmCertificationDevelopers }}</span>
<span class="stat-desc">武汉鸿蒙认证开发者</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ hmProjects }}</span>
<span class="stat-desc">鸿蒙开源项目</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ hmCompanies }}</span>
<span class="stat-desc">鸿蒙开源企业</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { hmStatsBarPage3 } from '@/api/jyh/situation';
// 定义响应式数据
const hmDevelopers = ref<string>('加载中...');
const hmCertificationDevelopers = ref<string>('加载中...');
const whHmCertificationDevelopers = ref<string>('加载中...');
const hmProjects = ref<string>('加载中...');
const hmCompanies = ref<string>('加载中...');
// 加载状态
const loading = ref(true);
// 格式化数字的工具函数
const formatNumber = (num: number, unit: string = '', forceUnit?: string): string => {
if (forceUnit === 'wan') {
// 强制以"万"为单位显示
return Math.floor(num) + '万+';
}
if (num >= 100000000) {
return Math.floor(num / 100000000) + '亿+';
} else if (num >= 10000) {
return Math.floor(num / 10000) + '万+';
} else if (num >= 1000) {
return Math.floor(num / 1000) + '千+';
}
return num.toString() + '+';
};
// 获取统计数据
const fetchStats = async () => {
try {
loading.value = true;
const response = await hmStatsBarPage3();
const businessData = response.data;
if (businessData.code === 200 && businessData) {
const { developer, certificationDeveloper, wuhanCertificationDeveloper, project, company } = businessData.data;
// 格式化数据显示
hmDevelopers.value = `${formatNumber(developer, '', 'wan')}`;
hmCertificationDevelopers.value = `${formatNumber(certificationDeveloper, '', 'wan')}`;
whHmCertificationDevelopers.value = `${formatNumber(wuhanCertificationDeveloper, '', 'wan')}`;
hmProjects.value = `${project}`; // 项目数不需要特殊格式化
hmCompanies.value = `${company}+`; // 公司数直接加+号
} else {
console.error('获取鸿蒙统计数据失败:', businessData.msg || '未知错误');
// 设置默认值
hmDevelopers.value = '1000万+';
hmCertificationDevelopers.value = '60万+';
whHmCertificationDevelopers.value = '1万+';
hmProjects.value = '728';
hmCompanies.value = '600+';
}
} catch (error) {
console.error('获取鸿蒙统计数据异常:', error);
// 设置默认值
hmDevelopers.value = '1000万+';
hmCertificationDevelopers.value = '60万+';
whHmCertificationDevelopers.value = '1万+';
hmProjects.value = '728';
hmCompanies.value = '600+';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchStats();
});
</script>
<style lang="scss" scoped>
.stats-glass-bar {
display: flex;
align-items: center;
justify-content: space-between;
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
margin: 0 0 1rem;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
/* 极细微的边框勾勒 */
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
/* 外阴影与内发光,增加悬浮感 */
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3),
inset 0 0 20px rgba(255, 255, 255, 0.02);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(56, 189, 248, 0.4);
background: rgba(255, 255, 255, 0.04);
transform: translateY(-2px);
}
}
.stat-node {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
.stat-value {
text-shadow: 0 0 25px rgba(56, 189, 248, 0.8);
}
}
}
.stat-value {
/* 科技感亮蓝渐变 */
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 2.25rem;
font-weight: 800;
background: linear-gradient(180deg, #fff 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* 基础发光效果 */
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3));
letter-spacing: -1px;
}
.stat-desc {
/* 在黑底上使用半透明白色,更有高级感 */
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.1rem;
text-transform: uppercase;
}
.stat-separator {
/* 渐隐分割线 */
width: 1px;
height: 3rem;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0)
);
margin: 0 1rem;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.stats-glass-bar {
padding: 2rem;
}
.stat-value {
font-size: 1.75rem;
}
}
@media (max-width: 768px) {
.stats-glass-bar {
flex-direction: column;
gap: 2rem;
}
.stat-separator {
width: 50%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255,255,255,0.2), transparent);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,90 @@
<template>
<div class="stats-glass-bar">
<div class="stat-node">
<span class="stat-value">1000W+</span>
<span class="stat-desc">鸿蒙开发者</span>
<span class="stat-value">{{ whCompany }}</span>
<span class="stat-desc">武汉开源企业</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">150+</span>
<span class="stat-desc">开源企业</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">12</span>
<span class="stat-value">{{ whCommunity }}</span>
<span class="stat-desc">武汉开源社区数量</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">70W+</span>
<span class="stat-value">{{ whDeveloper }}</span>
<span class="stat-desc">武汉开源开发者数量</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">500+</span>
<span class="stat-value">{{ whProject }}</span>
<span class="stat-desc">武汉开源项目数量</span>
</div>
</div>
</template>
<script>
export default {
name: "StatsBar"
}
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { statsBarPage3 } from '@/api/jyh/situation';
// 定义响应式数据
const whCompany = ref<string>('加载中...');
const whCommunity = ref<string>('加载中...');
const whDeveloper = ref<string>('加载中...');
const whProject = ref<string>('加载中...');
// 加载状态
const loading = ref(true);
// 格式化数字的工具函数
const formatNumber = (num: number, unit: string = ''): string => {
if (num >= 100000000) {
return (num / 100000000).toFixed(1) + '亿';
} else if (num >= 10000) {
return (num / 10000).toFixed(1) + '万';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + '千';
}
return num.toString();
};
// 获取统计数据
const fetchStats = async() => {
try {
loading.value = true;
const response = await statsBarPage3();
const businessData = response.data;
if (businessData.code === 200 && businessData) {
const { developer, community, company, project } = businessData.data;
// 格式化数据显示
whDeveloper.value = `${formatNumber(developer)}+`;
whCommunity.value = formatNumber(community);
whCompany.value = `${formatNumber(company)}+`;
whProject.value = `${formatNumber(project)}+`;
} else {
console.error('获取统计数据失败:', businessData.msg || '未知错误');
// 设置默认值
whDeveloper.value = '70万+';
whCommunity.value = '12';
whCompany.value = '150+';
whProject.value = '500+';
}
} catch (error) {
console.error('获取统计数据异常:', error);
// 设置默认值
whDeveloper.value = '70万+';
whCommunity.value = '12';
whCompany.value = '150+';
whProject.value = '500+';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchStats();
});
</script>
<style lang="scss" scoped>
@@ -42,7 +96,7 @@ name: "StatsBar"
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
//margin: 2rem 0;
margin: 0 0 1rem;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);

View File

@@ -14,7 +14,6 @@
汇聚武汉开源力量共建城市开源生态
</p>
</div>
<StatsBar />
</div>
</div>
@@ -30,9 +29,12 @@
class="lucide lucide-activity" aria-hidden="true">
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
<span>武汉开源生态数据可视化</span>
<span>武汉开源生态建设总览</span>
</h2>
</div>
<div class="index-module__NV_5cW__sectionHeader">
<StatsBar />
</div>
<div class="index-module__NV_5cW__chartsGrid">
@@ -309,129 +311,255 @@ import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import StatsBar from './components/StatsBar.vue';
import HarmonyEcoCharts from './components/HarmonyEcoCharts.vue';
import { wuhanGithubProjectsPage3, cityProjectPage3, chinaExcellentProjectsPage3, excellentProjectsPage3, talentPage3 } from '@/api/jyh/situation';
const { widthType } = usePageResize();
const githubProjects = ref([
{
name: 'bootstrap',
desc: 'The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.',
stars: '174000'
},
{
name: 'JavaGuide',
desc: 'Java 学习&面试指南Go、Python 后端面试通用,计算机基础面试总结)。准备后端技术面试,首选 JavaGuide',
stars: '153000'
},
{
name: 'cpython',
desc: 'The Python programming language',
stars: '71000'
},
{
name: 'core',
desc: '🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.',
stars: '52700'
},
{
name: 'bevy',
desc: 'A refreshingly simple data-driven game engine built in Rust',
stars: '43900'
},
{
name: 'linux-insides',
desc: 'A book-in-progress about the Linux kernel and its insides.',
stars: '31700'
},
{
name: 'podman',
desc: 'Podman: A tool for managing OCI containers and pods.',
stars: '30300'
},
{
name: 'fastai',
desc: 'The fastai deep learning library',
stars: '27800'
},
{
name: 'OI-wiki',
desc: '🌟 Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)',
stars: '25200'
},
{
name: 'bilibili-API-collect',
desc: '哔哩哔哩-API收集整理【不断更新中....】',
stars: '20100'
const githubProjects = ref([]);
// 武汉优质开源项目备用数据
const setgithubProjectsdata = () => {
githubProjects.value = [
{
name: 'bootstrap',
desc: 'The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.',
stars: '174000'
},
{
name: 'JavaGuide',
desc: 'Java 学习&面试指南Go、Python 后端面试通用,计算机基础面试总结)。准备后端技术面试,首选 JavaGuide',
stars: '153000'
},
{
name: 'cpython',
desc: 'The Python programming language',
stars: '71000'
},
{
name: 'core',
desc: '🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.',
stars: '52700'
},
{
name: 'bevy',
desc: 'A refreshingly simple data-driven game engine built in Rust',
stars: '43900'
},
{
name: 'linux-insides',
desc: 'A book-in-progress about the Linux kernel and its insides.',
stars: '31700'
},
{
name: 'podman',
desc: 'Podman: A tool for managing OCI containers and pods.',
stars: '30300'
},
{
name: 'fastai',
desc: 'The fastai deep learning library',
stars: '27800'
},
{
name: 'OI-wiki',
desc: '🌟 Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)',
stars: '25200'
},
{
name: 'bilibili-API-collect',
desc: '哔哩哔哩-API收集整理【不断更新中....】',
stars: '20100'
}
];
};
// 获取武汉优质开源项目数据
const fetchGithubProjects = async() => {
try {
const response = await wuhanGithubProjectsPage3();
const projectdata = response.data;
if (projectdata?.code === 200 && projectdata?.data) {
// 将星星数转换为字符串格式以匹配模板显示
const formattedData = projectdata.data.map(item => ({
...item,
stars: String(item.stars)
}));
githubProjects.value = formattedData;
} else {
console.error('获取武汉优质开源项目数据失败:', projectdata?.msg || '未知错误');
setgithubProjectsdata();
}
} catch (error) {
console.error('请求武汉优质开源项目数据时发生错误:', error);
setgithubProjectsdata();
}
]);
};
// 中国城市开源项目数量备用数据
const setCityProjectDefaultData = () => {
chartData.value.cityProject.xAxis = ['北京', '上海', '深圳', '杭州', '广州', '成都', '武汉', '南京', '西安', '重庆'];
chartData.value.cityProject.data = [757259, 678614, 343417, 315406, 177003, 168300, 120827, 111593, 58678, 56564];
isEmpty.value.cityProject = false;
};
// 获取中国城市开源项目数量数据
const fetchCityProjectData = async() => {
try {
const response = await cityProjectPage3();
const projectdata = response.data;
if (projectdata?.code === 200 && projectdata?.data) {
// 更新图表数据
chartData.value.cityProject.xAxis = projectdata.data.map(item => item.name);
chartData.value.cityProject.data = projectdata.data.map(item => item.data);
// 设置非空状态
isEmpty.value.cityProject = false;
} else {
console.error('获取中国城市开源项目数量数据失败:', projectdata?.msg || '未知错误');
setCityProjectDefaultData();
}
} catch (error) {
console.error('请求中国城市开源项目数量数据时发生错误:', error);
setCityProjectDefaultData();
}
};
// 城市开源人才数量备用数据
const setTalentDefaultData = () => {
chartData.value.talent.xAxis = ['北京', '上海', '广州', '深圳', '成都', '杭州', '武汉', '南京', '郑州', '长沙'];
chartData.value.talent.data = [2115924, 1591988, 1555240, 1163580, 894108, 830306, 791335, 733581, 553397, 487971];
isEmpty.value.talent = false;
};
// 获取城市开源人才数量数据
const fetchTalentData = async() => {
try {
const response = await talentPage3();
const talentdata = response.data;
if (talentdata?.code === 200 && talentdata?.data) {
// 更新图表数据 - 后端返回字段为 name 和 number
chartData.value.talent.xAxis = talentdata.data.map(item => item.name);
chartData.value.talent.data = talentdata.data.map(item => item.number);
// 设置非空状态
isEmpty.value.talent = false;
} else {
console.error('获取城市开源人才数量数据失败:', talentdata?.msg || '未知错误');
setTalentDefaultData();
}
} catch (error) {
console.error('请求城市开源人才数量数据时发生错误:', error);
setTalentDefaultData();
}
};
const activeTab = ref(0);
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': '仓颉语言版 Spring 框架替代', 'org': '江汉大学' },
{ 'name': 'deepin', 'desc': '深度桌面操作系统', 'org': '深之度' },
{ 'name': 'RustSBI', 'desc': 'RISC-V 安全引导程序', 'org': '华中科技大学' },
{ 'name': 'NextDenovo', 'desc': '长读长序列组装工具', 'org': '武汉希望组' }
]);
const excellentProjects = ref([]);
const chinaExcellentProjects = ref([
{
'name': 'Deepin 深度操作系统',
'desc': '国内领先的国产 Linux 发行版,旨在为全球用户提供美观易用、安全可靠的操作系统体验,是国产软件生态的核心。',
'org': '武汉深之度'
},
{
'name': 'Jupiter 微服务治理框架',
'desc': '由斗鱼研发并开源的面向治理的微服务框架,支持多数据源、多注册中心,适用于高并发场景。',
'org': '武汉斗鱼'
},
{
'name': 'RustSBI 安全引导程序',
'desc': '基于 Rust 语言开发的 RISC-V 指令集体系结构安全引导程序,提供更高内存安全性。',
'org': '华中科技大学'
},
{
'name': '芯片开源 EDA 研发',
'desc': '致力于国产电子设计自动化工具开源化,解决芯片设计环节中的关键技术难题。',
'org': '华中科技大学'
},
{
'name': 'LuoJiaSET 框架与 LuoJiaSET 样本库',
'desc': '专注于智能遥感影像解译,提供海量遥感样本数据集和深度学习框架。',
'org': '武汉大学'
},
{
'name': '鲲鹏安全库 (Kunpengsecl)',
'desc': '基于鲲鹏处理器的机密计算和受信任执行环境安全开发库,支撑云计算与隐私计算。',
'org': '武汉大学'
},
{
'name': '柚坛工具箱 (Uotan Toolbox)',
'desc': '针对 Android 发烧友开发的图形化综合工具箱,集成刷机、系统优化等多种功能。',
'org': '武昌理工学院'
},
{
'name': '凹语言 (Wa Language)',
'desc': '国内首个针对 WebAssembly 设计的通用编程语言,兼具 Web 灵活性与原生运行速度。',
'org': '武汉凹语言'
},
{
'name': 'OpenHIS 开源医疗系统',
'desc': '开源的医院信息系统,旨在通过标准化、模块化方式赋能基层医疗机构数字化升级。',
'org': '武汉新致新源'
},
{
'name': 'NextDenovo',
'desc': '高效且精确的长读长序列纠错和组装工具,主要用于基因组测序领域。',
'org': '武汉希望组生物科技有限公司'
const chinaExcellentProjects = ref([]);
// 2024武汉优秀开源项目备用数据
const chinaExcellentProjectsdata = () => {
chinaExcellentProjects.value = [
{
'name': 'Deepin 深度操作系统',
'desc': '国内领先的国产 Linux 发行版,旨在为全球用户提供美观易用、安全可靠的操作系统体验,是国产软件生态的核心。',
'org': '武汉深之度'
},
{
'name': 'Jupiter 微服务治理框架',
'desc': '由斗鱼研发并开源的面向治理的微服务框架,支持多数据源、多注册中心,适用于高并发场景。',
'org': '武汉斗鱼'
},
{
'name': 'RustSBI 安全引导程序',
'desc': '基于 Rust 语言开发的 RISC-V 指令集体系结构安全引导程序,提供更高内存安全性。',
'org': '华中科技大学'
},
{
'name': '芯片开源 EDA 研发',
'desc': '致力于国产电子设计自动化工具开源化,解决芯片设计环节中的关键技术难题。',
'org': '华中科技大学'
},
{
'name': 'LuoJiaSET 框架与 LuoJiaSET 样本库',
'desc': '专注于智能遥感影像解译,提供海量遥感样本数据集和深度学习框架。',
'org': '武汉大学'
},
{
'name': '鲲鹏安全库 (Kunpengsecl)',
'desc': '基于鲲鹏处理器的机密计算和受信任执行环境安全开发库,支撑云计算与隐私计算。',
'org': '武汉大学'
},
{
'name': '柚坛工具箱 (Uotan Toolbox)',
'desc': '针对 Android 发烧友开发的图形化综合工具箱,集成刷机、系统优化等多种功能。',
'org': '武昌理工学院'
},
{
'name': '凹语言 (Wa Language)',
'desc': '国内首个针对 WebAssembly 设计的通用编程语言,兼具 Web 灵活性与原生运行速度。',
'org': '武汉凹语言'
},
{
'name': 'OpenHIS 开源医疗系统',
'desc': '开源的医院信息系统,旨在通过标准化、模块化方式赋能基层医疗机构数字化升级。',
'org': '武汉新致新源'
},
{
'name': 'NextDenovo',
'desc': '高效且精确的长读长序列纠错和组装工具,主要用于基因组测序领域。',
'org': '武汉希望组生物科技有限公司'
}
];
};
// 2025武汉优秀开源项目备用数据
const excellentProjectsdata = () => {
excellentProjects.value = [
{ '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': '仓颉语言版 Spring 框架替代', 'org': '江汉大学' },
{ 'name': 'deepin', 'desc': '深度桌面操作系统', 'org': '深之度' },
{ 'name': 'RustSBI', 'desc': 'RISC-V 安全引导程序', 'org': '华中科技大学' },
{ 'name': 'NextDenovo', 'desc': '长读长序列组装工具', 'org': '武汉希望组' }
];
};
const fetchchinaExcellentProjects = async() => {
try {
const response = await chinaExcellentProjectsPage3();
const projectdata = response.data;
if (projectdata?.code === 200 && projectdata?.data) {
chinaExcellentProjects.value = projectdata.data;
} else {
console.error('获取2024武汉优秀开源项目数据失败:', projectdata?.msg || '未知错误');
chinaExcellentProjectsdata();
}
} catch (error) {
console.error('请求2024武汉优秀开源项目数据时发生错误:', error);
chinaExcellentProjectsdata();
}
]);
};
const fetchexcellentProjects = async() => {
try {
const response = await excellentProjectsPage3();
const projectdata = response.data;
if (projectdata?.code === 200 && projectdata?.data) {
excellentProjects.value = projectdata.data;
} else {
console.error('获取2025武汉优秀开源项目数据失败:', projectdata?.msg || '未知错误');
excellentProjectsdata();
}
} catch (error) {
console.error('请求2025武汉优秀开源项目数据时发生错误:', error);
excellentProjectsdata();
}
};
const wuhanCommunities = ref([
{ name: '黄鹤开源社区', type: '核心生态', slogan: '国家级、城市级开源社区平台,武汉开源生态的核心枢纽' },
@@ -458,12 +586,12 @@ const developerNames = ['白纯骢', '陆红旭', '慕冬亮', '张继德', '蒋
// 图表数据
const chartData = ref({
cityProject: {
xAxis: ['北京', '上海', '深圳', '杭州', '广州', '成都', '武汉', '南京', '西安', '重庆'],
data: [757259, 678614, 343417, 315406, 177003, 168300, 120827, 111593, 58678, 56564]
xAxis: [],
data: []
},
talent: {
xAxis: ['北京', '上海', '广州', '深圳', '成都', '杭州', '武汉', '南京', '郑州', '长沙'],
data: [2115924, 1591988, 1555240, 1163580, 894108, 830306, 791335, 733581, 553397, 487971]
xAxis: [],
data: []
},
communityRank: {
xAxis: ['Deepin', 'OpenHarmony', 'MindSpore', 'IoTDB', 'GoFrame'],
@@ -642,6 +770,7 @@ const initCommunityRankChart = () => {
const el = document.getElementById('communityRankChart');
if (!el) return;
const myChart = echarts.init(el);
chartInstances.value.communityRank = myChart;
myChart.setOption({
tooltip: { trigger: 'axis', valueFormatter: (val: any) => val + ' 个' },
grid: { left: '3%', right: '4%', bottom: '15px', top: '22%', containLabel: true },
@@ -767,8 +896,13 @@ watch(widthType, () => {
init();
});
onMounted(() => {
init();
onMounted(async() => {
await fetchGithubProjects();
await fetchCityProjectData(); // 获取城市项目数据
await fetchTalentData(); // 获取城市人才数据
await fetchchinaExcellentProjects(); // 获取2024中国优秀开源项目数据
await fetchexcellentProjects(); // 获取2025武汉优秀开源项目数据
init(); // 再初始化图表
});
onUnmounted(() => {
@@ -1363,4 +1497,8 @@ onUnmounted(() => {
grid-template-columns: 1fr;
}
}
.index-module__NV_5cW__chartsSection {
margin-top: 2rem;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,382 @@
<template>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">社区治理健康度对比</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty || loading">加载中...</div>
<div v-else id="ossCompassChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import { communityHealthPage2 } from '@/api/jyh/situation';
// 定义数据结构
interface CommunityHealthItem {
indicators: string;
github: number;
gitcode: number;
}
// 响应式数据存储
const communityHealthData = ref<CommunityHealthItem[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await communityHealthPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
communityHealthData.value = businessData.data;
} else {
console.error('获取社区治理健康度数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
communityHealthData.value = [
{ indicators: 'PR 合并率', github: 82, gitcode: 68 },
{ indicators: 'Issue 响应时间', github: 88, gitcode: 75 },
{ indicators: 'CI/CD 使用率', github: 78, gitcode: 55 },
{ indicators: '平均代码质量得分', github: 85, gitcode: 72 },
{ indicators: '安全审计频率', github: 75, gitcode: 60 },
{ indicators: '贡献者多样性', github: 80, gitcode: 65 }
];
}
} catch (error) {
console.error('获取社区治理健康度数据异常:', error);
// 设置默认数据
communityHealthData.value = [
{ indicators: 'PR 合并率', github: 82, gitcode: 68 },
{ indicators: 'Issue 响应时间', github: 88, gitcode: 75 },
{ indicators: 'CI/CD 使用率', github: 78, gitcode: 55 },
{ indicators: '平均代码质量得分', github: 85, gitcode: 72 },
{ indicators: '安全审计频率', github: 75, gitcode: 60 },
{ indicators: '贡献者多样性', github: 80, gitcode: 65 }
];
} finally {
loading.value = false;
}
};
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
});
// 页面尺寸响应式
const { widthType } = usePageResize();
// 空数据标识
const isEmpty = computed(() => {
return communityHealthData.value.length === 0;
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
communityHealthChart: null
});
// 初始化社区治理健康度对比(纵向柱状图)
const initCommunityHealthChart = () => {
const el = document.getElementById('ossCompassChart');
if (!el) return;
if (chartInstances.value.communityHealthChart) {
chartInstances.value.communityHealthChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.communityHealthChart = myChart;
// 提取数据以适应图表需求
const indicators = communityHealthData.value.map(item => item.indicators);
const githubValues = communityHealthData.value.map(item => item.github);
const gitcodeValues = communityHealthData.value.map(item => item.gitcode);
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgba(59, 130, 246, 0.1)'
}
},
backgroundColor: 'rgba(255, 255, 255, 0.98)',
borderColor: '#3b82f6',
borderWidth: 2,
borderRadius: 8,
padding: 16,
textStyle: { color: '#333', fontSize: 13 },
extraCssText: 'box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);',
formatter: (params: any) => {
const indicator = params[0].name;
const githubValue = params[0].value;
const gitcodeValue = params[1].value;
const diff = githubValue - gitcodeValue;
const diffPercent = ((diff / githubValue) * 100).toFixed(1);
return `
<div style="line-height: 2.2;">
<div style="font-weight: bold; color: #3b82f6; font-size: 15px; margin-bottom: 8px; border-bottom: 2px solid #3b82f6; padding-bottom: 4px;">${indicator}</div>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 6px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #BF232A, #FF4900); border-radius: 50%; box-shadow: 0 2px 4px rgba(191, 35, 42, 0.3);"></span>
<span style="color: #666; min-width: 100px;">GitHub 社区</span>
<span style="color: #BF232A; font-weight: 700; font-size: 15px;">${githubValue}</span>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="display: inline-block; width: 10px; height: 10px; background: linear-gradient(135deg, #FF4900, #FFAB00); border-radius: 50%; box-shadow: 0 2px 4px rgba(255, 73, 0, 0.3);"></span>
<span style="color: #666; min-width: 100px;">GitCode 社区</span>
<span style="color: #FF4900; font-weight: 700; font-size: 15px;">${gitcodeValue}</span>
</div>
${diff > 0 ? `<div style="margin-top: 8px; padding: 4px 8px; background: rgba(59, 130, 246, 0.1); border-radius: 4px; color: #3b82f6; font-size: 11px;">📊 GitHub 领先 ${diffPercent}%</div>` : ''}
</div>
`;
}
},
legend: {
data: ['GitHub 社区', 'GitCode 社区'],
top: '2%',
right: '5%',
itemWidth: 14,
itemHeight: 14,
itemGap: 15,
textStyle: {
color: '#333',
fontSize: 12,
fontWeight: '600'
},
icon: 'roundRect'
},
grid: {
left: '5%',
right: '5%',
bottom: '10%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: indicators,
axisLabel: {
fontSize: 11,
color: '#666',
fontWeight: '500',
interval: 0,
rotate: 20,
margin: 12
},
axisTick: {
alignWithLabel: true,
length: 5
},
axisLine: {
lineStyle: { color: '#e5e7eb', width: 2 }
}
},
yAxis: {
type: 'value',
name: '健康度得分',
nameTextStyle: {
fontSize: 13,
color: '#666',
fontWeight: '600',
padding: [0, 0, 0, 10]
},
max: 100,
axisLabel: {
fontSize: 12,
color: '#666',
formatter: '{value}'
},
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.06)',
type: 'dashed'
}
}
},
series: [
{
name: 'GitHub 社区',
type: 'bar',
data: githubValues,
barWidth: '35%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 0, color: '#FF4900' },
{ offset: 0.5, color: '#BF232A' },
{ offset: 1, color: '#BF232A' }
]),
borderRadius: [6, 6, 0, 0],
shadowBlur: 10,
shadowColor: 'rgba(191, 35, 42, 0.3)'
},
label: {
show: true,
position: 'top',
formatter: '{c}',
fontSize: 11,
color: '#BF232A',
fontWeight: 'bold'
},
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(191, 35, 42, 0.6)'
}
}
},
{
name: 'GitCode 社区',
type: 'bar',
data: gitcodeValues,
barWidth: '35%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 0, color: '#FFAB00' },
{ offset: 0.5, color: '#FF4900' },
{ offset: 1, color: '#FF4900' }
]),
borderRadius: [6, 6, 0, 0],
shadowBlur: 10,
shadowColor: 'rgba(255, 73, 0, 0.3)'
},
label: {
show: true,
position: 'top',
formatter: '{c}',
fontSize: 11,
color: '#FF4900',
fontWeight: 'bold'
},
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(255, 73, 0, 0.6)'
}
}
}
],
animationDuration: 1000,
animationEasing: 'cubicOut',
animationDelay: (idx: number) => idx * 80
});
};
// 监听数据变化,重新渲染图表
watch(communityHealthData, () => {
if (!isEmpty.value) {
nextTick(() => {
initCommunityHealthChart();
});
}
}, { deep: true });
// 页面resize时重绘图表
watch(widthType, () => {
nextTick(() => {
if (!isEmpty.value) {
initCommunityHealthChart();
}
});
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
if (!isEmpty.value) {
initCommunityHealthChart();
}
}, 100);
});
// 组件卸载时销毁图表实例
onUnmounted(() => {
if (chartInstances.value.communityHealthChart) {
chartInstances.value.communityHealthChart.dispose();
chartInstances.value.communityHealthChart = null;
}
});
</script>
<style scoped>
/* 社区治理健康度对比图表卡片样式 - 从主页面复制 */
.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.25rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
height: 500px;
}
.index-module__NV_5cW__chartCard::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
transition: left 0.6s;
}
.index-module__NV_5cW__chartCard:hover::before {
left: 100%;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.index-module__NV_5cW__chartCard:hover .chartCard__title {
color: #3b82f6;
transform: scale(1.02);
transition: all 0.3s;
}
/* 标题样式 - 从主页面复制 */
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0;
text-align: center;
}
/* 容器样式 - 从主页面复制 */
.chartCard__container {
width: 100%;
height: 450px;
position: relative;
overflow: hidden;
}
/* 空数据显示样式 - 从主页面复制 */
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
/* 图表容器样式 */
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,379 @@
<template>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">基础设施覆盖率</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty"></div>
<div v-else id="infrastructureChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import { infrastructurePage2 } from '@/api/jyh/situation';
// 基础颜色配置
const baseColors = ['#00F0FF', '#FFAB00', '#B20003'];
// 定义数据结构
interface InfrastructureItem {
name: string;
value: number;
}
// 响应式数据存储
const infrastructureData = ref<InfrastructureItem[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async() => {
try {
loading.value = true;
const response = await infrastructurePage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData.code === 200 && businessData.data) {
// 为每个数据项添加颜色和半径配置
infrastructureData.value = businessData.data.map((item: InfrastructureItem, index: number) => ({
name: item.name,
value: item.value,
color: baseColors[index % baseColors.length],
radius: [`${70 - index * 20}%`, `${80 - index * 20}%`] // 根据索引计算半径移除calc函数以避免潜在问题
}));
} else {
console.error('获取基础设施覆盖率数据失败:', businessData.msg || '未知错误');
// 设置默认数据
infrastructureData.value = [
{
name: '镜像站规模',
value: 85,
color: '#00F0FF',
radius: ['70%', '80%']
},
{
name: '代码托管平台规模',
value: 90,
color: '#FFAB00',
radius: ['50%', '60%']
},
{
name: '构建平台活跃度',
value: 40,
color: '#B20003',
radius: ['30%', '40%']
}
];
}
} catch (error) {
console.error('获取基础设施覆盖率数据异常:', error);
// 设置默认数据
infrastructureData.value = [
{
name: '镜像站规模',
value: 85,
color: '#00F0FF',
radius: ['70%', '80%']
},
{
name: '代码托管平台规模',
value: 90,
color: '#FFAB00',
radius: ['50%', '60%']
},
{
name: '构建平台活跃度',
value: 40,
color: '#B20003',
radius: ['30%', '40%']
}
];
} finally {
loading.value = false;
}
};
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
});
// 页面尺寸响应式
const { widthType } = usePageResize();
// 空数据标识
const isEmpty = computed(() => {
return infrastructureData.value.length === 0;
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
infrastructureChart: null
});
// 初始化基础设施覆盖率图表(多层环形图)
const initInfrastructureChart = () => {
const el = document.getElementById('infrastructureChart');
if (!el) return;
if (chartInstances.value.infrastructureChart) {
chartInstances.value.infrastructureChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.infrastructureChart = myChart;
const seriesData: any[] = [];
const center = ['50%', '55%'];
infrastructureData.value.forEach(item => {
const completeValue = item.value;
const remainingValue = 100 - item.value;
seriesData.push({
name: item.name,
type: 'pie',
radius: item.radius,
center: center,
avoidLabelOverlap: false,
startAngle: 90,
hoverAnimation: true,
label: {
show: false
},
labelLine: {
show: false
},
emphasis: {
scale: true,
scaleSize: 5
},
data: [
{
value: completeValue,
name: item.name,
itemStyle: {
color: new echarts.graphic.LinearGradient(
0, 0, 0, 1,
[
{ offset: 0, color: item.color },
{ offset: 1, color: item.color + '80' }
]
),
shadowBlur: 10,
shadowColor: item.color + '40'
}
},
{
value: remainingValue,
name: '未覆盖',
itemStyle: {
color: 'rgba(255, 255, 255, 0.08)'
},
tooltip: {
show: false
},
emphasis: {
disabled: true
}
}
]
});
});
// 计算平均覆盖率
const avgCoverage = Math.round(
infrastructureData.value.reduce((sum, d) => sum + d.value, 0) /
infrastructureData.value.length
);
myChart.setOption({
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#3b82f6',
borderWidth: 1,
borderRadius: 6,
padding: 12,
textStyle: { color: '#333', fontSize: 13 },
formatter: (params: any) => {
if (params.name === '未覆盖') return '';
const remaining = 100 - params.value;
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #3b82f6; margin-bottom: 6px; border-bottom: 2px solid #3b82f6; padding-bottom: 4px;">${params.name}</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #10b981; border-radius: 50%;"></span>
已覆盖:<span style="color: #10b981; font-weight: 700; font-size: 15px;">${params.value}%</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #ef4444; border-radius: 50%;"></span>
未覆盖:<span style="color: #ef4444; font-weight: 700; font-size: 15px;">${remaining}%</span>
</div>
</div>
`;
}
},
legend: {
data: infrastructureData.value.map(d => ({
name: d.name,
itemStyle: {
color: d.color
}
})),
orient: 'horizontal',
top: '3%',
left: 'center',
itemWidth: 12, // 图例方块宽度
itemHeight: 12, // 图例方块高度
itemGap: 20, // 图例项之间的间距
icon: 'circle', // 图例形状:'circle'圆形, 'rect'方形, 'roundRect'圆角方形
textStyle: {
color: '#333',
fontSize: 12,
padding: [0, 0, 0, 8] // 文字与图标的间距
}
},
graphic: [
{
type: 'text',
left: 'center',
top: '50%',
style: {
text: '总覆盖度',
fill: '#666',
fontSize: 13,
fontWeight: '500'
}
},
{
type: 'text',
left: 'center',
top: '57%',
style: {
text: `${avgCoverage}%`,
fill: '#3b82f6',
fontSize: 28,
fontWeight: 'bold'
}
}
],
series: seriesData,
animationDuration: 1000,
animationEasing: 'cubicOut',
animationDelay: (idx: number) => idx * 100
});
};
// 监听数据变化,重新渲染图表
watch(infrastructureData, () => {
if (!isEmpty.value) {
nextTick(() => {
initInfrastructureChart();
});
}
}, { deep: true });
// 页面resize时重绘图表
watch(widthType, () => {
nextTick(() => {
if (!isEmpty.value) {
initInfrastructureChart();
}
});
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
if (!isEmpty.value) {
initInfrastructureChart();
}
}, 100);
});
// 组件卸载时销毁图表实例
onUnmounted(() => {
if (chartInstances.value.infrastructureChart) {
chartInstances.value.infrastructureChart.dispose();
chartInstances.value.infrastructureChart = null;
}
});
</script>
<style scoped>
/* 基础设施覆盖率图表卡片样式 - 从主页面复制 */
.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.25rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.index-module__NV_5cW__chartCard::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
transition: left 0.6s;
}
.index-module__NV_5cW__chartCard:hover::before {
left: 100%;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.index-module__NV_5cW__chartCard:hover .chartCard__title {
color: #3b82f6;
transform: scale(1.02);
transition: all 0.3s;
}
/* 标题样式 - 从主页面复制 */
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0;
text-align: center;
}
/* 容器样式 - 从主页面复制 */
.chartCard__container {
width: 100%;
height: 390px;
position: relative;
overflow: hidden;
}
/* 空数据显示样式 - 从主页面复制 */
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
/* 图表容器样式 */
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,436 @@
<template>
<div class="index-module__NV_5cW__chartCard list-card">
<h3 class="chartCard__title">明星开发者聚焦</h3>
<!-- 新增ref + 鼠标事件 -->
<div class="chartCard__container list-container developer-list-container" ref="scrollContainer2"
@mouseenter="pauseScroll(2)" @mouseleave="resumeScroll(2)">
<div class="num-empty" v-if="loading">加载中...</div>
<div class="project-list developer-list" v-else>
<div class="project-item developer-item" v-for="(item, index) in excellentProjects2025" :key="index">
<div class="dev-avatar">
<img :src="item.avatar" :alt="item.name" class="avatar-img" @error="handleImageError" />
</div>
<span class="project-rank dev-rank">{{ item.desc }}</span>
<div class="project-info dev-info">
<span class="project-name dev-name">{{ item.name }}</span>
<span class="project-desc dev-location">📍 {{ item.org }}</span>
</div>
<span class="project-org dev-followers">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<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>
</svg>
{{ formatFollowers(item.followers) }}
</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { excellentDeveloperPage2 } from '@/api/jyh/situation';
// 定义数据结构
interface DeveloperItem {
name: string;
description: string;
followers: number;
avatar: string;
org: string;
}
// 明星开发者数据
const excellentProjects2025 = ref<DeveloperItem[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await excellentDeveloperPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
// 适配后端数据格式将description映射为desc
excellentProjects2025.value = businessData.data.map((item: any) => ({
...item,
desc: item.description
}));
} else {
console.error('获取明星开发者数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
excellentProjects2025.value = [
{ name: '稚晖', desc: 'TOP 1', org: 'Shanghai', followers: 82738, avatar: 'https://github.com/peng-zhihui.png' },
{ name: 'Crypto Michael', desc: 'TOP 2', org: 'Beijing, China', followers: 37622, avatar: 'https://github.com/michaelliao.png' },
{ name: '代码家', desc: 'TOP 3', org: 'Beijing, China', followers: 24743, avatar: 'https://github.com/daimajia.png' },
{ name: 'Jackson Tian', desc: 'TOP 4', org: 'Hangzhou, China', followers: 21267, avatar: 'https://github.com/JacksonTian.png' },
{ name: '云风', desc: 'TOP 5', org: 'China', followers: 21192, avatar: 'https://github.com/cloudwu.png' },
{ name: 'Fengda Huang', desc: 'TOP 6', org: 'Shanghai / Hangzhou', followers: 20229, avatar: 'https://github.com/phodal.png' },
{ name: '程序员鱼皮', desc: 'TOP 7', org: 'Shanghai', followers: 18461, avatar: 'https://github.com/liyupi.png' },
{ name: 'Trinea', desc: 'TOP 8', org: 'HangZhou', followers: 17019, avatar: 'https://github.com/Trinea.png' },
{ name: 'lifesinger', desc: 'TOP 9', org: 'Hangzhou, China', followers: 15894, avatar: 'https://github.com/lifesinger.png' },
{ name: 'stormzhang', desc: 'TOP 10', org: 'Shanghai, China', followers: 15889, avatar: 'https://github.com/stormzhang.png' },
{ name: 'Wenli Zhang', desc: 'TOP 11', org: 'Shanghai, China', followers: 15680, avatar: 'https://github.com/Ovilia.png' },
{ name: 'Wei Wang', desc: 'TOP 12', org: 'Yokohama / China', followers: 15532, avatar: 'https://github.com/onevcat.png' },
{ name: 'astaxie', desc: 'TOP 13', org: 'Shanghai, China', followers: 15022, avatar: 'https://github.com/astaxie.png' },
{ name: '司徒正美', desc: 'TOP 14', org: 'China', followers: 14742, avatar: 'https://github.com/RubyLouvre.png' },
{ name: '云谦', desc: 'TOP 15', org: 'HangZhou, China', followers: 14466, avatar: 'https://github.com/sorrycc.png' },
{ name: 'cangdu', desc: 'TOP 16', org: 'Shanghai, China', followers: 13728, avatar: 'https://github.com/bailicangdu.png' },
{ name: 'Huang Haiguang', desc: 'TOP 17', org: 'Qingdao, China', followers: 13422, avatar: 'https://github.com/fengdu78.png' },
{ name: '花裤衩', desc: 'TOP 18', org: 'Shanghai, China', followers: 13236, avatar: 'https://github.com/PanJiaChen.png' },
];
}
} catch (error) {
console.error('获取明星开发者数据异常:', error);
// 设置默认数据
excellentProjects2025.value = [
{ name: '稚晖', desc: 'TOP 1', org: 'Shanghai', followers: 82738, avatar: 'https://github.com/peng-zhihui.png' },
{ name: 'Crypto Michael', desc: 'TOP 2', org: 'Beijing, China', followers: 37622, avatar: 'https://github.com/michaelliao.png' },
{ name: '代码家', desc: 'TOP 3', org: 'Beijing, China', followers: 24743, avatar: 'https://github.com/daimajia.png' },
{ name: 'Jackson Tian', desc: 'TOP 4', org: 'Hangzhou, China', followers: 21267, avatar: 'https://github.com/JacksonTian.png' },
{ name: '云风', desc: 'TOP 5', org: 'China', followers: 21192, avatar: 'https://github.com/cloudwu.png' },
{ name: 'Fengda Huang', desc: 'TOP 6', org: 'Shanghai / Hangzhou', followers: 20229, avatar: 'https://github.com/phodal.png' },
{ name: '程序员鱼皮', desc: 'TOP 7', org: 'Shanghai', followers: 18461, avatar: 'https://github.com/liyupi.png' },
{ name: 'Trinea', desc: 'TOP 8', org: 'HangZhou', followers: 17019, avatar: 'https://github.com/Trinea.png' },
{ name: 'lifesinger', desc: 'TOP 9', org: 'Hangzhou, China', followers: 15894, avatar: 'https://github.com/lifesinger.png' },
{ name: 'stormzhang', desc: 'TOP 10', org: 'Shanghai, China', followers: 15889, avatar: 'https://github.com/stormzhang.png' },
{ name: 'Wenli Zhang', desc: 'TOP 11', org: 'Shanghai, China', followers: 15680, avatar: 'https://github.com/Ovilia.png' },
{ name: 'Wei Wang', desc: 'TOP 12', org: 'Yokohama / China', followers: 15532, avatar: 'https://github.com/onevcat.png' },
{ name: 'astaxie', desc: 'TOP 13', org: 'Shanghai, China', followers: 15022, avatar: 'https://github.com/astaxie.png' },
{ name: '司徒正美', desc: 'TOP 14', org: 'China', followers: 14742, avatar: 'https://github.com/RubyLouvre.png' },
{ name: '云谦', desc: 'TOP 15', org: 'HangZhou, China', followers: 14466, avatar: 'https://github.com/sorrycc.png' },
{ name: 'cangdu', desc: 'TOP 16', org: 'Shanghai, China', followers: 13728, avatar: 'https://github.com/bailicangdu.png' },
{ name: 'Huang Haiguang', desc: 'TOP 17', org: 'Qingdao, China', followers: 13422, avatar: 'https://github.com/fengdu78.png' },
{ name: '花裤衩', desc: 'TOP 18', org: 'Shanghai, China', followers: 13236, avatar: 'https://github.com/PanJiaChen.png' },
];
} finally {
loading.value = false;
}
};
// 滚动相关变量
const scrollContainer2 = ref<HTMLDivElement | null>(null);
const scrollTimers: { [key: number]: NodeJS.Timeout | null } = { 2: null };
// 3. 滚动配置
const scrollStep = 1; // 每次滚动的步长(像素,越小越流畅)
const scrollInterval = 20; // 滚动间隔(毫秒,越小越流畅)
// 4. 通用自动滚动函数
const autoScroll = (containerKey: number) => {
// 获取对应容器
const container = containerKey === 2 ? scrollContainer2.value : null;
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(() => {
if (scrollTimers[2]) {
clearInterval(scrollTimers[2]!);
scrollTimers[2] = null;
}
});
// 8. 格式化粉丝数82738 → 82.7K
const formatFollowers = (num: number) => {
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
};
// 9. 图片加载失败处理
const handleImageError = (e: Event) => {
const target = e.target as HTMLImageElement;
const name = target.alt;
target.style.display = 'none';
if (target.parentElement) {
target.parentElement.innerHTML = `<span class="avatar-text">${name.slice(0, 1)}</span>`;
}
};
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
// 数据加载完成后启动自动滚动
setTimeout(() => {
autoScroll(2);
}, 100);
});
</script>
<style scoped>
.index-module__NV_5cW__chartCard {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6; /* rgba(255, 255, 255, 0.9) */
border: 1px solid #ffffff4d; /* rgba(255, 255, 255, 0.3) */
border-radius: 1rem; /* 16px */
padding: 1.5rem; /* 24px */
box-shadow: 0 4px 16px #00000014; /* subtle shadow */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
/* 悬停效果开始 */
.index-module__NV_5cW__chartCard::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
transition: left 0.6s;
}
.index-module__NV_5cW__chartCard:hover::before {
left: 100%;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.index-module__NV_5cW__chartCard:hover .chartCard__title {
color: #3b82f6;
transform: scale(1.02);
transition: all 0.3s;
}
.index-module__NV_5cW__chartCard.list-card {
padding: 1.5rem; /* 24px */
}
.chartCard__title {
color: #333;
font-size: 1.1rem; /* 17.6px */
font-weight: 600;
margin: 0;
text-align: center;
width: 100%;
}
.chartCard__container.list-container {
width: 100%;
height: 380px;
position: relative;
overflow: hidden; /* 外层容器隐藏溢出 */
}
.list-container {
height: auto;
max-height: 300px; /* 限制列表高度,超出滚动 */
overflow-y: auto; /* 允许垂直滚动 */
padding: 0 0.5rem; /* 8px */
/* 隐藏滚动条 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
}
.list-container::-webkit-scrollbar {
display: none; /* Chrome/Safari/Opera */
}
/* 隐藏webkit内核浏览器的滚动条 */
.chartCard__container.list-container::-webkit-scrollbar {
display: none;
}
/* 空数据显示样式 */
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.developer-list-container {
max-height: 450px;
margin-top: 20px;
}
.project-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.project-item.developer-item {
display: flex;
align-items: center;
gap: 0.75rem; /* 12px */
padding: 0.75rem 1rem; /* 12px 16px */
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-left: 3px solid #3b82f6;
border-radius: 0.5rem; /* 8px */
transition: all 0.2s;
cursor: pointer;
}
.project-item.developer-item:hover {
background: linear-gradient(135deg, #eff6ff 0%, #f0f4ff 100%);
border-left-color: #8b5cf6;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.15);
transform: translateX(2px);
}
.dev-avatar {
width: 42px;
height: 42px;
border-radius: 50%;
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
overflow: hidden;
flex-shrink: 0;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-text {
font-size: 16px;
font-weight: bold;
color: #fff;
}
.project-rank.dev-rank {
width: auto;
height: auto;
border-radius: 12px;
background: linear-gradient(135deg, #ec4899 0%, #f59e0b 100%);
padding: 3px 10px;
font-size: 11px;
font-weight: 700;
box-shadow: 0 2px 6px rgba(236, 72, 153, 0.3);
flex-shrink: 0;
min-width: auto;
}
.project-info.dev-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.3rem; /* 5px */
}
.project-name.dev-name {
font-size: 15px; /* 15px */
font-weight: 700; /* 更粗的字体 */
color: #1e293b; /* 深灰色 */
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.project-desc.dev-location {
font-size: 11px; /* 11px */
color: #64748b; /* 灰蓝色 */
font-style: normal; /* 取消斜体 */
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.project-org.dev-followers {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.2);
color: #3b82f6;
font-size: 12px;
font-weight: 600;
white-space: nowrap;
max-width: none;
flex-shrink: 0;
}
.project-org.dev-followers svg {
flex-shrink: 0;
}
.scroll-indicator {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
gap: 0.25rem; /* 4px */
padding: 0.25rem 0.5rem; /* 4px 8px */
background: rgba(59, 130, 246, 0.1);
border-radius: 0 0 0 0.5rem; /* 与原文件一致 */
font-size: 0.7rem; /* 与原文件一致 */
color: #3b82f6;
z-index: 10;
animation: bounce 2s ease-in-out infinite; /* 添加动画 */
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
</style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="stats-glass-bar">
<div class="stat-node">
<span class="stat-value">{{ activeDevelopers }}</span>
<span class="stat-desc">活跃开发者</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ openSourceProjects }}</span>
<span class="stat-desc">开源项目</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ openSourceCompanies }}</span>
<span class="stat-desc">开源企业</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ industryValuation }}</span>
<span class="stat-desc">产业估值</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { overviewDataPage2 } from '@/api/jyh/situation';
// 定义响应式数据
const activeDevelopers = ref<string>('加载中...');
const openSourceProjects = ref<string>('加载中...');
const openSourceCompanies = ref<string>('加载中...');
const industryValuation = ref<string>('加载中...');
// 加载状态
const loading = ref(true);
// 格式化数字的工具函数
const formatNumber = (num: number, unit: string = ''): string => {
if (num >= 100000000) {
return Math.floor(num / 100000000) + '亿';
} else if (num >= 10000) {
return Math.floor(num / 10000) + '万';
} else if (num >= 1000) {
return Math.floor(num / 1000) + '千';
}
return num.toString();
};
// 格式化估值的工具函数
const formatValuation = (num: number): string => {
if (num >= 100000000) {
return Math.floor(num / 100000000) + '亿';
} else if (num >= 10000) {
return Math.floor(num / 10000) + '万';
}
return num.toString() + '亿';
};
// 获取统计数据
const fetchStats = async () => {
try {
loading.value = true;
const response = await overviewDataPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
const { developer, project, company, valuation } = businessData.data;
// 格式化数据显示
activeDevelopers.value = `${formatNumber(developer)}+`;
openSourceProjects.value = `${formatNumber(project)}+`;
openSourceCompanies.value = `${formatNumber(company)}+`;
industryValuation.value = `${formatValuation(valuation)}`;
} else {
console.error('获取统计数据失败:', businessData?.msg || '未知错误');
// 设置默认值
activeDevelopers.value = '200万+';
openSourceProjects.value = '300万+';
openSourceCompanies.value = '500+';
industryValuation.value = '2800亿';
}
} catch (error) {
console.error('获取统计数据异常:', error);
// 设置默认值
activeDevelopers.value = '200万+';
openSourceProjects.value = '300万+';
openSourceCompanies.value = '500+';
industryValuation.value = '2800亿';
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchStats();
});
</script>
<style lang="scss" scoped>
.stats-glass-bar {
display: flex;
align-items: center;
justify-content: space-between;
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
//margin: 2rem 0;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
/* 极细微的边框勾勒 */
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
/* 外阴影与内发光,增加悬浮感 */
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3),
inset 0 0 20px rgba(255, 255, 255, 0.02);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(56, 189, 248, 0.4);
background: rgba(255, 255, 255, 0.04);
transform: translateY(-2px);
}
}
.stat-node {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
.stat-value {
text-shadow: 0 0 25px rgba(56, 189, 248, 0.8);
}
}
}
.stat-value {
/* 科技感亮蓝渐变 */
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 2.25rem;
font-weight: 800;
background: linear-gradient(180deg, #fff 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* 基础发光效果 */
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3));
letter-spacing: -1px;
}
.stat-desc {
/* 在黑底上使用半透明白色,更有高级感 */
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.1rem;
text-transform: uppercase;
}
.stat-separator {
/* 渐隐分割线 */
width: 1px;
height: 3rem;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0)
);
margin: 0 1rem;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.stats-glass-bar {
padding: 2rem;
}
.stat-value {
font-size: 1.75rem;
}
}
@media (max-width: 768px) {
.stats-glass-bar {
flex-direction: column;
gap: 2rem;
}
.stat-separator {
width: 50%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255,255,255,0.2), transparent);
}
}
</style>

View File

@@ -0,0 +1,425 @@
<template>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">高校开源社团/俱乐部数量</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty || loading">加载中...</div>
<div v-else id="communityChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch, onUnmounted, computed } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import { universityClubsPage2 } from '@/api/jyh/situation';
// 响应式数据存储
const universityClubData = ref<{ university: string; club: string }[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await universityClubsPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
universityClubData.value = businessData.data;
} else {
console.error('获取高校开源社团数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
universityClubData.value = [
// 华东地区
{ university: '上海交通大学', club: 'SJTU-LUG (Linux User Group)' },
{ university: '上海交通大学', club: '开放原子开源俱乐部' },
{ university: '复旦大学', club: 'Fudan LUG' },
{ university: '复旦大学', club: '开放原子开源俱乐部' },
{ university: '浙江大学', club: 'ZJU-LUG' },
{ university: '浙江大学', club: 'AAA (Azure Availability Association)' },
{ university: '浙江大学', club: '开放原子开源俱乐部' },
{ university: '南京大学', club: 'NJU-LUG' },
{ university: '南京大学', club: 'eScience 协会' },
{ university: '南京大学', club: '开放原子开源俱乐部' },
{ university: '中国科学技术大学', club: 'USTC-LUG' },
{ university: '中国科学技术大学', club: 'VLAB' },
{ university: '同济大学', club: 'Tongji LUG' },
{ university: '同济大学', club: '开放原子开源俱乐部' },
{ university: '华东师范大学', club: 'ECNU LUG' },
// 华北地区
{ university: '清华大学', club: 'TUNA (清华大学学生网络与开源软件协会)' },
{ university: '清华大学', club: '开放原子开源俱乐部' },
{ university: '北京大学', club: 'PKU-LUG' },
{ university: '北京大学', club: '开源软件协会' },
{ university: '中国人民大学', club: 'RUC 开源社' },
{ university: '北京航空航天大学', club: 'BUAA-LUG' },
{ university: '北京理工大学', club: 'BIT-LUG' },
{ university: '南开大学', club: 'NKU-LUG' },
{ university: '天津大学', club: 'TJU-LUG' },
// 华南地区
{ university: '中山大学', club: 'SYSU-LUG' },
{ university: '华南理工大学', club: 'SCUT-LUG' },
{ university: '暨南大学', club: 'JNU-LUG' },
{ university: '深圳大学', club: 'SZU-LUG' },
{ university: '华南师范大学', club: 'SCNU-LUG' },
// 华中地区
{ university: '华中科技大学', club: 'HUST-LUG' },
{ university: '华中科技大学', club: '开放原子开源俱乐部' },
{ university: '武汉大学', club: 'WHU-LUG' },
{ university: '武汉大学', club: '珞珈码农社' },
{ university: '中南大学', club: 'CSU-LUG' },
{ university: '山东大学', club: 'SDU-LUG' },
{ university: '厦门大学', club: 'XMU-LUG' }
];
}
} catch (error) {
console.error('获取高校开源社团数据异常:', error);
// 设置默认数据
universityClubData.value = [
// 华东地区
{ university: '上海交通大学', club: 'SJTU-LUG (Linux User Group)' },
{ university: '上海交通大学', club: '开放原子开源俱乐部' },
{ university: '复旦大学', club: 'Fudan LUG' },
{ university: '复旦大学', club: '开放原子开源俱乐部' },
{ university: '浙江大学', club: 'ZJU-LUG' },
{ university: '浙江大学', club: 'AAA (Azure Availability Association)' },
{ university: '浙江大学', club: '开放原子开源俱乐部' },
{ university: '南京大学', club: 'NJU-LUG' },
{ university: '南京大学', club: 'eScience 协会' },
{ university: '南京大学', club: '开放原子开源俱乐部' },
{ university: '中国科学技术大学', club: 'USTC-LUG' },
{ university: '中国科学技术大学', club: 'VLAB' },
{ university: '同济大学', club: 'Tongji LUG' },
{ university: '同济大学', club: '开放原子开源俱乐部' },
{ university: '华东师范大学', club: 'ECNU LUG' },
// 华北地区
{ university: '清华大学', club: 'TUNA (清华大学学生网络与开源软件协会)' },
{ university: '清华大学', club: '开放原子开源俱乐部' },
{ university: '北京大学', club: 'PKU-LUG' },
{ university: '北京大学', club: '开源软件协会' },
{ university: '中国人民大学', club: 'RUC 开源社' },
{ university: '北京航空航天大学', club: 'BUAA-LUG' },
{ university: '北京理工大学', club: 'BIT-LUG' },
{ university: '南开大学', club: 'NKU-LUG' },
{ university: '天津大学', club: 'TJU-LUG' },
// 华南地区
{ university: '中山大学', club: 'SYSU-LUG' },
{ university: '华南理工大学', club: 'SCUT-LUG' },
{ university: '暨南大学', club: 'JNU-LUG' },
{ university: '深圳大学', club: 'SZU-LUG' },
{ university: '华南师范大学', club: 'SCNU-LUG' },
// 华中地区
{ university: '华中科技大学', club: 'HUST-LUG' },
{ university: '华中科技大学', club: '开放原子开源俱乐部' },
{ university: '武汉大学', club: 'WHU-LUG' },
{ university: '武汉大学', club: '珞珈码农社' },
{ university: '中南大学', club: 'CSU-LUG' },
{ university: '山东大学', club: 'SDU-LUG' },
{ university: '厦门大学', club: 'XMU-LUG' }
];
} finally {
loading.value = false;
}
};
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
});
// 页面尺寸响应式
const { widthType } = usePageResize();
// 处理高校社团数据
const processUniversityClubsData = () => {
const counts: { [key: string]: { count: number; clubs: string[] } } = {};
universityClubData.value.forEach(item => {
if (!counts[item.university]) {
counts[item.university] = { count: 0, clubs: [] };
}
counts[item.university].count++;
counts[item.university].clubs.push(item.club);
});
// 转换为数组并排序
const sorted = Object.entries(counts)
.map(([name, data]) => ({
name,
value: data.count,
clubs: data.clubs
}))
.sort((a, b) => b.value - a.value);
return {
universities: sorted.slice(0, 10).map(item => item.name), // 取前10名避免X轴过挤
values: sorted.slice(0, 10).map(item => item.value),
details: sorted.slice(0, 10).reduce((acc, item) => {
acc[item.name] = item.clubs;
return acc;
}, {} as { [key: string]: string[] })
};
};
const communityData = computed(() => processUniversityClubsData());
// 空数据标识
const isEmpty = computed(() => {
return communityData.value.universities.length === 0;
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
communityChart: null
});
// 初始化高校开源社团/俱乐部数量图表(纵向柱状图)
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;
const data = communityData.value;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#3b82f6',
borderWidth: 1,
borderRadius: 6,
padding: 12,
textStyle: { color: '#333', fontSize: 13 },
formatter: (params: any) => {
const name = params[0].name;
const value = params[0].value;
const clubs = data.details[name] || [];
const clubsHtml = clubs.map(c => `${c}`).join('<br/>');
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #3b82f6; margin-bottom: 6px; border-bottom: 2px solid #3b82f6; padding-bottom: 4px;">${name}</div>
<div>社团数量: <span style="color: #10b981; font-weight: 700; font-size: 16px;">${value}</span> 个</div>
<div style="margin-top: 8px; border-top: 1px solid rgba(0,0,0,0.1); padding-top: 6px; font-size: 11px; color: #666; max-height: 150px; overflow-y: auto;">
${clubsHtml}
</div>
</div>
`;
}
},
grid: {
left: '10%',
right: '10%',
bottom: '5%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.universities,
axisLabel: {
fontSize: 10,
rotate: 45,
interval: 0,
color: '#666',
margin: 15
},
axisTick: {
alignWithLabel: true,
length: 5
},
axisLine: {
lineStyle: { color: '#e5e7eb', width: 2 }
}
},
yAxis: {
type: 'value',
name: '社团数量',
nameTextStyle: { fontSize: 12, color: '#666' },
axisLabel: { fontSize: 12, color: '#666' },
minInterval: 1,
splitLine: {
lineStyle: { color: 'rgba(0, 0, 0, 0.06)', type: 'dashed' }
}
},
series: [
{
name: '社团数量',
type: 'bar',
data: data.values,
barWidth: '60%',
barCategoryGap: '30%',
itemStyle: {
color: (params: any) => {
// 为每个柱状图提供唯一颜色
const colors = [
['#3b82f6', '#60a5fa'], // 蓝色
['#8b5cf6', '#a78bfa'], // 紫色
['#ec4899', '#f472b6'], // 粉色
['#10b981', '#34d399'], // 绿色
['#f59e0b', '#fbbf24'], // 橙色
['#ef4444', '#fca5a5'], // 红色
['#06b6d4', '#7dd3fc'], // 青色
['#84cc16', '#a3e635'], // 黄绿色
['#f97316', '#fb923c'], // 橙红色
['#6366f1', '#818cf8'], // 靛蓝色
['#14b8a6', '#2dd4bf'], // 翠绿色
['#f43f5e', '#f9a8d4'], // 玫红
['#a855f7', '#c084fc'], // 紫罗兰
['#d946ef', '#e879f9'], // 梅花色
['#0ea5e9', '#38bdf8'] // 天蓝色
];
const colorPair = colors[params.dataIndex % colors.length];
return new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 0, color: colorPair[1] },
{ offset: 1, color: colorPair[0] }
]);
},
borderRadius: [4, 4, 0, 0],
shadowBlur: 8,
shadowColor: 'rgba(59, 130, 246, 0.3)'
},
label: {
show: true,
position: 'top',
formatter: '{c}',
fontSize: 11,
color: '#3b82f6',
fontWeight: 'bold'
},
emphasis: {
itemStyle: {
shadowBlur: 15,
shadowColor: 'rgba(59, 130, 246, 0.6)'
}
}
}
],
animationDuration: 1000,
animationEasing: 'cubicOut',
animationDelay: (idx: number) => idx * 50
});
};
// 监听数据变化,重新渲染图表
watch(universityClubData, () => {
if (!isEmpty.value) {
nextTick(() => {
initCommunityChart();
});
}
}, { deep: true });
// 页面resize时重绘图表
watch(widthType, () => {
nextTick(() => {
if (!isEmpty.value) {
initCommunityChart();
}
});
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
if (!isEmpty.value) {
initCommunityChart();
}
}, 100);
});
// 组件卸载时销毁图表实例
onUnmounted(() => {
if (chartInstances.value.communityChart) {
chartInstances.value.communityChart.dispose();
chartInstances.value.communityChart = null;
}
});
</script>
<style scoped>
/* 高校开源社团/俱乐部数量图表卡片样式 - 从主页面复制 */
.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.25rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.index-module__NV_5cW__chartCard::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
transition: left 0.6s;
}
.index-module__NV_5cW__chartCard:hover::before {
left: 100%;
}
.index-module__NV_5cW__chartCard:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.index-module__NV_5cW__chartCard:hover .chartCard__title {
color: #3b82f6;
transform: scale(1.02);
transition: all 0.3s;
}
/* 标题样式 - 从主页面复制 */
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0;
text-align: center;
}
/* 容器样式 - 从主页面复制 */
.chartCard__container {
width: 100%;
height: 390px;
position: relative;
overflow: hidden;
}
/* 空数据显示样式 - 从主页面复制 */
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
/* 图表容器样式 */
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,446 @@
<template>
<div class="index-module__NV_5cW__vitalitySection">
<h3 class="vitality__title">
<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">
<path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
</svg>
<span>开发者活力波形图</span>
</h3>
<div class="vitality__container">
<div class="num-empty" v-if="loading">加载中...</div>
<div id="vitalityChart" class="chart" v-else></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, watch } from 'vue';
import * as echarts from 'echarts';
import { usePageResize } from '@/utils/hooks/usePageResize';
import { vitalityPage2 } from '@/api/jyh/situation';
// 页面尺寸响应式
const { widthType } = usePageResize();
// 定义数据结构
interface VitalityItem {
timeData: string;
total: number;
}
// 开发者活力数据
const vitalityData = ref({
timeData: [] as string[],
total: [] as number[],
new: [] as number[] // 默认数据因为API只返回timeData和total
});
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await vitalityPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
// 提取API返回的数据
const apiTimeData: string[] = [];
const apiTotalData: number[] = [];
businessData.data.forEach((item: VitalityItem) => {
apiTimeData.push(item.timeData);
apiTotalData.push(item.total);
});
// 计算年度新增数据(相邻年份的差值)
const apiNewData: number[] = [];
for (let i = 0; i < apiTotalData.length; i++) {
if (i === 0) {
apiNewData.push(apiTotalData[i]); // 第一年的新增就是当年总数
} else {
apiNewData.push(apiTotalData[i] - apiTotalData[i - 1]); // 后续年份为差值
}
}
vitalityData.value = {
timeData: apiTimeData,
total: apiTotalData,
new: apiNewData
};
} else {
console.error('获取开发者活力数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
vitalityData.value = {
timeData: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"],
total: [20, 50, 100, 200, 300, 400, 600, 800, 1000, 1200, 1350],
new: [20, 30, 50, 100, 100, 100, 200, 200, 200, 200, 150]
};
}
} catch (error) {
console.error('获取开发者活力数据异常:', error);
// 设置默认数据
vitalityData.value = {
timeData: ["2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"],
total: [20, 50, 100, 200, 300, 400, 600, 800, 1000, 1200, 1350],
new: [20, 30, 50, 100, 100, 100, 200, 200, 200, 200, 150]
};
} finally {
loading.value = false;
}
};
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
vitalityChart: null
});
// 初始化开发者活力波形图
const initVitalityChart = () => {
const el = document.getElementById('vitalityChart');
if (!el) return;
if (chartInstances.value.vitalityChart) {
chartInstances.value.vitalityChart.dispose();
}
const myChart = echarts.init(el);
chartInstances.value.vitalityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999',
width: 1,
type: 'dashed'
},
lineStyle: {
color: '#3b82f6',
width: 1,
type: 'dashed'
}
},
backgroundColor: 'rgba(255, 255, 255, 0.98)',
borderColor: '#10b981',
borderWidth: 2,
borderRadius: 8,
padding: 16,
textStyle: { color: '#333', fontSize: 14 },
extraCssText: 'box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);',
formatter: (params: any) => {
if (!Array.isArray(params) || params.length === 0) {
return '<div>暂无数据</div>';
}
const totalItem = params.find((item: any) => item.seriesName === '累计开发者');
const newItem = params.find((item: any) => item.seriesName === '年度新增');
const totalValue = totalItem?.value || 0;
const newValue = newItem?.value || 0;
const time = totalItem?.axisValue || newItem?.axisValue || '未知时间';
// 计算增长率
const prevIndex = vitalityData.value.timeData.indexOf(time) - 1;
let growthRate = 0;
if (prevIndex >= 0) {
const prevTotal = vitalityData.value.total[prevIndex];
growthRate = ((totalValue - prevTotal) / prevTotal * 100).toFixed(1);
}
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #10b981; font-size: 16px; margin-bottom: 8px; border-bottom: 2px solid #10b981; padding-bottom: 4px;">📅 ${time}年</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #10b981; border-radius: 50%;"></span>
累计开发者:<span style="color: #10b981; font-weight: 700; font-size: 15px;">${totalValue}</span> 万人
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="display: inline-block; width: 8px; height: 8px; background: #f59e0b; border-radius: 50%;"></span>
年度新增:<span style="color: #f59e0b; font-weight: 700; font-size: 15px;">${newValue}</span> 万人
</div>
${growthRate > 0 ? `<div style="margin-top: 6px; padding: 4px 8px; background: rgba(16, 185, 129, 0.1); border-radius: 4px; color: #10b981; font-size: 12px;">📈 同比增长 ${growthRate}%</div>` : ''}
</div>
`;
}
},
legend: {
left: 'center',
top: '0%',
itemWidth: 16,
itemHeight: 16,
itemGap: 20,
textStyle: { color: '#333', fontSize: 14, fontWeight: '600' },
data: ['累计开发者', '年度新增'],
icon: 'roundRect',
borderRadius: 4
},
grid: {
top: '18%',
left: '3%',
right: '4%',
bottom: '8%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
axisTick: { show: false },
axisLine: {
lineStyle: { color: '#e5e7eb', width: 2 },
onZero: false
},
axisLabel: {
color: '#666',
fontSize: 13,
fontWeight: '500',
interval: 0,
margin: 12
},
data: vitalityData.value.timeData
},
yAxis: {
type: 'value',
name: '开发者数量(万人)',
nameTextStyle: { color: '#666', fontSize: 12 },
axisLabel: { color: '#666', fontSize: 12 },
splitLine: {
show: true,
lineStyle: { color: 'rgba(0, 0, 0, 0.06)', type: 'dashed' }
}
},
series: [
{
name: '累计开发者',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 8,
lineStyle: { color: '#10b981', width: 3 },
itemStyle: {
color: '#10b981',
borderWidth: 2,
borderColor: '#fff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(16, 185, 129, 0.4)' },
{ offset: 1, color: 'rgba(16, 185, 129, 0.05)' }
]),
shadowBlur: 10,
shadowColor: 'rgba(16, 185, 129, 0.2)'
},
data: vitalityData.value.total,
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(16, 185, 129, 0.6)',
scale: 1.2
},
label: {
show: true,
formatter: '{c} 万',
position: 'top',
color: '#10b981',
fontSize: 13,
fontWeight: 'bold'
}
},
markPoint: {
data: [
{
type: 'max', name: '最大值',
itemStyle: { color: '#10b981' },
label: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
}
}
],
symbolSize: 60
},
markLine: {
silent: true,
lineStyle: {
color: '#10b981',
type: 'dashed',
width: 1
},
label: {
color: '#10b981',
fontSize: 11
},
data: [{ type: 'average', name: '平均值' }]
}
},
{
name: '年度新增',
type: 'bar',
barWidth: '40%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#f59e0b' },
{ offset: 1, color: '#fbbf24' }
]),
borderRadius: [4, 4, 0, 0],
shadowBlur: 8,
shadowColor: 'rgba(245, 158, 11, 0.3)'
},
data: vitalityData.value.new,
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowColor: 'rgba(245, 158, 11, 0.6)'
},
label: {
show: true,
formatter: '{c} 万',
position: 'top',
color: '#f59e0b',
fontSize: 12,
fontWeight: 'bold'
}
},
markPoint: {
data: [
{
type: 'max', name: '峰值',
itemStyle: { color: '#f59e0b' },
label: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
}
}
],
symbolSize: 60
},
animationDelay: (idx: number) => idx * 50
}
]
});
};
// 页面resize时重绘图表
watch(widthType, () => {
nextTick(() => initVitalityChart());
});
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
// 数据加载完成后初始化图表
setTimeout(() => {
initVitalityChart();
}, 100);
});
</script>
<style lang="scss" scoped>
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.index-module__NV_5cW__vitalitySection {
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
background: #ffffffe6;
border: 1px solid #ffffff4d;
border-radius: 1.5rem;
margin-bottom: 2rem;
padding: 2rem;
box-shadow: 0 8px 24px #00000014;
transition: all 0.3s;
}
.index-module__NV_5cW__vitalitySection:hover {
border-color: #3b82f64d;
transform: translateY(-2px);
box-shadow: 0 12px 32px #0000001f;
}
.vitality__title {
color: #333;
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 1.5rem 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
position: relative;
}
.vitality__title::before {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: linear-gradient(90deg, #10b981, #f59e0b);
border-radius: 2px;
}
.vitality__title svg {
color: #10b981;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.vitality__container {
width: 100%;
height: 400px;
position: relative;
background: linear-gradient(135deg,
rgba(16, 185, 129, 0.02) 0%,
rgba(245, 158, 11, 0.02) 100%);
border-radius: 0.75rem;
padding: 1rem;
}
.chart {
width: 100%;
height: 100%;
}
/* 响应式适配 */
@media (max-width: 768px) {
.vitality__container {
height: 300px;
}
.vitality__title {
font-size: 1.25rem;
}
}
@media (max-width: 480px) {
.vitality__container {
height: 250px;
}
.vitality__title {
font-size: 1.2rem;
}
}
</style>

View File

@@ -0,0 +1,476 @@
<template>
<div class="map-container">
<div id="talentMapChart" class="chart" v-if="!loading"></div>
<div class="num-empty" v-else>加载中...</div>
<!-- 右侧排行榜 -->
<div class="map-rank-panel">
<div class="panel-title">🏆 开源人才数量排行 TOP 5</div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in topRegions" :key="index">
<span :class="['rank-num', 'rank-' + (index + 1)]">{{ index + 1 }}</span>
<span class="rank-name">{{ item.name }}</span>
<span class="rank-score">{{ item.score }} 万人</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, onBeforeUnmount, computed} from 'vue';
// 初始化中国开源人才密度地图
import * as echarts from "echarts";
import geoJson from '../json/china.json';
import { talentMapDataPage2 } from '@/api/jyh/situation';
const talentMapChart = ref()
// 定义数据结构
interface TalentMapItem {
name: string;
density: number;
total: number;
}
// 中国开源人才密度地图数据
const talentMapData = ref<any[]>([]);
const loading = ref(true);
// 从API获取数据
const fetchDataFromApi = async () => {
try {
loading.value = true;
const response = await talentMapDataPage2();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200 && businessData.data) {
// 转换API返回的数据格式为图表所需格式
talentMapData.value = businessData.data.map((item: TalentMapItem) => {
// 查找省份坐标,如果找不到则使用默认坐标
let coordinates = [104.5, 37.5]; // 默认为中国中心坐标
// 匹配省份坐标
const provinceCoords: Record<string, [number, number]> = {
'北京市': [116.4074, 39.9042],
'上海市': [121.4737, 31.2304],
'浙江省': [120.1551, 30.2794],
'广东省': [113.2644, 23.1291],
'台湾省': [121.5090, 25.0443],
'四川省': [104.0657, 30.6595],
'江苏省': [118.7780, 32.0472],
'湖北省': [114.3053, 30.5928],
'福建省': [119.3062, 26.0747],
'陕西省': [108.9480, 34.2632],
'重庆市': [106.5049, 29.5332],
'山东省': [120.3331, 36.0670],
'湖南省': [112.9822, 28.1944],
'安徽省': [117.2830, 31.8611],
'辽宁省': [123.4321, 41.8007],
'云南省': [102.7123, 25.0406],
'天津市': [117.1901, 39.1258],
'河南省': [113.6654, 34.7579],
'吉林省': [125.3245, 43.8171],
'江西省': [115.8581, 28.6820],
'广西壮族自治区': [108.3200, 22.8241],
'黑龙江省': [126.5349, 45.8038],
'澳门特别行政区': [113.5490, 22.1987],
'河北省': [114.5024, 38.0454],
'山西省': [112.5489, 37.8570],
'贵州省': [106.7135, 26.5783],
'内蒙古自治区': [111.6708, 40.8183],
'香港特别行政区': [114.1095, 22.3964],
'新疆维吾尔自治区': [87.6168, 43.7928],
'甘肃省': [103.8235, 36.0580],
'海南省': [110.3312, 20.0319],
'宁夏回族自治区': [106.2782, 38.4664],
'西藏自治区': [91.1175, 29.6469],
'青海省': [101.7782, 36.6172]
};
const coord = provinceCoords[item.name] || coordinates;
// 返回格式:[经度, 纬度, 密度, 总量]
return {
name: item.name,
value: [coord[0], coord[1], item.density, item.total]
};
});
} else {
console.error('获取人才地图数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
talentMapData.value = [
{ name: '北京市', value: [116.4074, 39.9042, 141.15, 231.47] },
{ name: '上海市', value: [121.4737, 31.2304, 269.83, 169.99] },
{ name: '浙江省', value: [120.1551, 30.2794, 8.94, 90.98] },
{ name: '广东省', value: [113.2644, 23.1291, 7.5, 149.76] },
{ name: '台湾省', value: [121.5090, 25.0443, 32.75, 117.88] },
{ name: '四川省', value: [104.0657, 30.6595, 1.01, 48.7] },
{ name: '江苏省', value: [118.7780, 32.0472, 5.25, 56.28] },
{ name: '湖北省', value: [114.3053, 30.5928, 1.84, 34.09] },
{ name: '福建省', value: [119.3062, 26.0747, 1.39, 16.79] },
{ name: '陕西省', value: [108.9480, 34.2632, 1.04, 21.38] },
{ name: '重庆市', value: [106.5049, 29.5332, 1.61, 13.24] },
{ name: '山东省', value: [120.3331, 36.0670, 1.02, 16.02] },
{ name: '湖南省', value: [112.9822, 28.1944, 0.68, 14.25] },
{ name: '安徽省', value: [117.2830, 31.8611, 0.93, 12.92] },
{ name: '辽宁省', value: [123.4321, 41.8007, 0.77, 11.37] },
{ name: '云南省', value: [102.7123, 25.0406, 0.1, 3.68] },
{ name: '天津市', value: [117.1901, 39.1258, 7.79, 9.34] },
{ name: '河南省', value: [113.6654, 34.7579, 0.55, 9.07] },
{ name: '吉林省', value: [125.3245, 43.8171, 0.15, 2.7] },
{ name: '江西省', value: [115.8581, 28.6820, 0.25, 4.17] },
{ name: '广西壮族自治区', value: [108.3200, 22.8241, 0.14, 3.15] },
{ name: '黑龙江省', value: [126.5349, 45.8038, 0.11, 5.13] },
{ name: '澳门特别行政区', value: [113.5490, 22.1987, 737.5, 2.36] },
{ name: '河北省', value: [114.5024, 38.0454, 0.26, 4.74] },
{ name: '山西省', value: [112.5489, 37.8570, 0.25, 3.78] },
{ name: '贵州省', value: [106.7135, 26.5783, 0.11, 1.93] },
{ name: '内蒙古自治区', value: [111.6708, 40.8183, 0.01, 0.96] },
{ name: '香港特别行政区', value: [114.1095, 22.3964, 1797.7, 197.75] },
{ name: '新疆维吾尔自治区', value: [87.6168, 43.7928, 0.01, 0.77] },
{ name: '甘肃省', value: [103.8235, 36.0580, 0.04, 1.46] },
{ name: '海南省', value: [110.3312, 20.0319, 0.36, 1.25] },
{ name: '宁夏回族自治区', value: [106.2782, 38.4664, 0.05, 0.27] },
{ name: '西藏自治区', value: [91.1175, 29.6469, 0.01, 0.14] },
{ name: '青海省', value: [101.7782, 36.6172, 0.01, 0.18] }
];
}
} catch (error) {
console.error('获取人才地图数据异常:', error);
// 设置默认数据
talentMapData.value = [
{ name: '北京市', value: [116.4074, 39.9042, 141.15, 231.47] },
{ name: '上海市', value: [121.4737, 31.2304, 269.83, 169.99] },
{ name: '浙江省', value: [120.1551, 30.2794, 8.94, 90.98] },
{ name: '广东省', value: [113.2644, 23.1291, 7.5, 149.76] },
{ name: '台湾省', value: [121.5090, 25.0443, 32.75, 117.88] },
{ name: '四川省', value: [104.0657, 30.6595, 1.01, 48.7] },
{ name: '江苏省', value: [118.7780, 32.0472, 5.25, 56.28] },
{ name: '湖北省', value: [114.3053, 30.5928, 1.84, 34.09] },
{ name: '福建省', value: [119.3062, 26.0747, 1.39, 16.79] },
{ name: '陕西省', value: [108.9480, 34.2632, 1.04, 21.38] },
{ name: '重庆市', value: [106.5049, 29.5332, 1.61, 13.24] },
{ name: '山东省', value: [120.3331, 36.0670, 1.02, 16.02] },
{ name: '湖南省', value: [112.9822, 28.1944, 0.68, 14.25] },
{ name: '安徽省', value: [117.2830, 31.8611, 0.93, 12.92] },
{ name: '辽宁省', value: [123.4321, 41.8007, 0.77, 11.37] },
{ name: '云南省', value: [102.7123, 25.0406, 0.1, 3.68] },
{ name: '天津市', value: [117.1901, 39.1258, 7.79, 9.34] },
{ name: '河南省', value: [113.6654, 34.7579, 0.55, 9.07] },
{ name: '吉林省', value: [125.3245, 43.8171, 0.15, 2.7] },
{ name: '江西省', value: [115.8581, 28.6820, 0.25, 4.17] },
{ name: '广西壮族自治区', value: [108.3200, 22.8241, 0.14, 3.15] },
{ name: '黑龙江省', value: [126.5349, 45.8038, 0.11, 5.13] },
{ name: '澳门特别行政区', value: [113.5490, 22.1987, 737.5, 2.36] },
{ name: '河北省', value: [114.5024, 38.0454, 0.26, 4.74] },
{ name: '山西省', value: [112.5489, 37.8570, 0.25, 3.78] },
{ name: '贵州省', value: [106.7135, 26.5783, 0.11, 1.93] },
{ name: '内蒙古自治区', value: [111.6708, 40.8183, 0.01, 0.96] },
{ name: '香港特别行政区', value: [114.1095, 22.3964, 1797.7, 197.75] },
{ name: '新疆维吾尔自治区', value: [87.6168, 43.7928, 0.01, 0.77] },
{ name: '甘肃省', value: [103.8235, 36.0580, 0.04, 1.46] },
{ name: '海南省', value: [110.3312, 20.0319, 0.36, 1.25] },
{ name: '宁夏回族自治区', value: [106.2782, 38.4664, 0.05, 0.27] },
{ name: '西藏自治区', value: [91.1175, 29.6469, 0.01, 0.14] },
{ name: '青海省', value: [101.7782, 36.6172, 0.01, 0.18] }
];
} finally {
loading.value = false;
}
};
// 计算 TOP 5 地区
const topRegions = computed(() => {
const sorted = [...talentMapData.value]
.map(item => ({
name: item.name,
score: item.value[3],
density: item.value[2]
}))
.sort((a, b) => b.score - a.score);
return sorted.slice(0, 5);
});
// 组件挂载时获取数据
onMounted(() => {
fetchDataFromApi();
// 数据加载完成后初始化图表
setTimeout(() => {
initTalentMapChart()
}, 100);
});
const initTalentMapChart = () => {
const el = document.getElementById('talentMapChart');
if (!el) return;
if (talentMapChart.value) {
talentMapChart.value.dispose();
}
const myChart = echarts.init(el);
talentMapChart.value = myChart;
// --- 修改后的加载逻辑:直接获取 JSON ---
// 这里可以换成你本地项目的路径,例如:'/maps/china.json'
const MAP_JSON_URL = 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json';
// 1. 注册地图数据
echarts.registerMap('china', geoJson);
// 2. 调用渲染函数
renderMap();
// 渲染地图的函数 (配置保持原有的科技感风格)
function renderMap() {
myChart.setOption({
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(17, 54, 122, 0.95)',
borderColor: '#00A0FF',
borderWidth: 1,
borderRadius: 8,
padding: 16,
textStyle: { color: '#6dd0e3', fontSize: 14 },
formatter: (params: any) => {
if (params.seriesType === 'scatter') {
const data = params.data.value;
const density = data[2];
const total = data[3];
// 内部辅助函数获取等级
const getLevel = (d: number) => {
if(d > 500) return '人才极高密度区';
if(d > 100) return '人才高密度区';
return '人才稳步增长区';
};
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #00F2FF; font-size: 16px; margin-bottom: 10px;">${params.name}</div>
<div><span style="color: #6dd0e3;">人才密度:</span><span style="color: #00C0FF; font-weight: 700;">${density}</span> 人/km²</div>
<div><span style="color: #6dd0e3;">人才总量:</span><span style="color: #00C0FF; font-weight: 700;">${total}</span> 万人</div>
<div style="margin-top: 8px; padding: 4px 10px; background: rgba(0, 242, 255, 0.2); border-radius: 6px; color: #00F2FF; font-size: 12px;">${getLevel(density)}</div>
</div>
`;
}
return '';
}
},
visualMap: {
show: false,
min: 0,
max: 500,
inRange: {
color: ['#00A0FF', '#0063D0', '#004190'],
symbolSize: [10, 60]
}
},
geo: {
map: 'china',
roam: true,
scaleLimit: { min: 0.8, max: 4 },
zoom: 2.6, // 调整为 standard zoom
center: [104.5, 37.5],
label: {
show: true,
fontSize: 10,
color: '#CEF4FD'
},
itemStyle: {
areaColor: 'rgba(0, 30, 80, 0.6)',
borderColor: 'rgba(0, 160, 255, 0.5)',
borderWidth: 1.5,
shadowBlur: 10,
shadowColor: 'rgba(0, 160, 255, 0.3)'
},
emphasis: {
itemStyle: {
areaColor: 'rgba(0, 50, 120, 0.8)',
borderColor: '#00C0FF'
}
}
},
series: [
{
name: '开源人才密度',
type: 'scatter',
coordinateSystem: 'geo',
data: talentMapData.value,
symbolSize: (val: any) => Math.sqrt(val[2]) * 2 + 8,
itemStyle: {
color: (params: any) => {
const density = params.value[2];
if (density >= 500) return '#FF4900';
if (density >= 100) return '#FFAB00';
return '#00C0FF';
},
shadowBlur: 20,
shadowColor: 'rgba(0, 242, 255, 0.6)'
}
}
]
});
}
};
</script>
<style lang="scss" scoped>
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #999;
}
.map-container {
display: flex;
gap: 2rem;
align-items: stretch;
position: relative;
min-height: 600px;
}
.map-container .chart {
flex: 1;
min-height: 600px;
background: linear-gradient(135deg,
rgba(0, 20, 60, 0.4) 0%,
rgba(0, 40, 100, 0.3) 100%);
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
box-shadow: 0 8px 24px rgba(0, 192, 255, 0.2);
padding: 1rem;
}
.map-rank-panel {
width: 300px;
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 192, 255, 0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
gap: 1rem;
}
.panel-title {
font-size: 1.2rem;
font-weight: 700;
color: black;
text-align: center;
padding-bottom: 1rem;
margin-bottom: 0.5rem;
border-bottom: 2px solid rgba(0, 242, 255, 0.3);
}
.rank-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-height: 400px;
overflow-y: auto;
padding-right: 5px;
}
.rank-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.85rem 1rem;
background: rgba(0, 192, 255, 0.08);
border-radius: 0.75rem;
border: 1px solid rgba(0, 192, 255, 0.2);
border-left: 3px solid #00A0FF;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
margin-bottom: 0.75rem;
}
.rank-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 242, 255, 0.15), transparent);
transition: left 0.5s;
}
.rank-item:hover::before {
left: 100%;
}
.rank-item:hover {
background: rgba(0, 192, 255, 0.2);
border-color: #00F2FF;
border-left-color: #00F2FF;
box-shadow: 0 6px 16px rgba(0, 242, 255, 0.3);
transform: translateX(5px) scale(1.02);
}
.rank-num {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
font-family: 'DIN Alternate', 'Arial', sans-serif;
}
.rank-num.rank-1 {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(255, 215, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-2 {
background: linear-gradient(135deg, #E8E8E8, #C0C0C0);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(192, 192, 192, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-3 {
background: linear-gradient(135deg, #CD7F32, #B8733C);
color: white;
box-shadow: 0 4px 12px rgba(205, 127, 50, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-4,
.rank-num.rank-5 {
background: rgba(0, 192, 255, 0.2);
color: #00C0FF;
font-weight: bold;
border: 2px solid rgba(0, 192, 255, 0.4);
}
.rank-name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #000000;
}
.rank-score {
font-size: 14px;
font-weight: 700;
color: #00F2FF;
font-family: 'DIN Alternate', 'Arial', sans-serif;
text-shadow: 0 0 8px rgba(0, 242, 255, 0.5);
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,347 +0,0 @@
<template>
<div class="map-container">
<div id="talentMapChart" class="chart"></div>
<!-- 右侧排行榜 -->
<div class="map-rank-panel">
<div class="panel-title">🏆 开源人才数量排行 TOP 5</div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in topRegions" :key="index">
<span :class="['rank-num', 'rank-' + (index + 1)]">{{ index + 1 }}</span>
<span class="rank-name">{{ item.name }}</span>
<span class="rank-score">{{ item.score }} 万人</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {onMounted, ref, onBeforeUnmount, computed} from 'vue';
// 初始化中国开源人才密度地图
import * as echarts from "echarts";
import geoJson from './json/china.json';
const talentMapChart = ref()
// 中国开源人才密度地图数据
const talentMapData = ref([
{ name: '北京市', value: [116.4074, 39.9042, 141.15, 231.47] },
{ name: '上海市', value: [121.4737, 31.2304, 269.83, 169.99] },
{ name: '浙江省', value: [120.1551, 30.2794, 8.94, 90.98] },
{ name: '广东省', value: [113.2644, 23.1291, 7.5, 149.76] },
{ name: '台湾省', value: [121.5090, 25.0443, 32.75, 117.88] },
{ name: '四川省', value: [104.0657, 30.6595, 1.01, 48.7] },
{ name: '江苏省', value: [118.7780, 32.0472, 5.25, 56.28] },
{ name: '湖北省', value: [114.3053, 30.5928, 1.84, 34.09] },
{ name: '福建省', value: [119.3062, 26.0747, 1.39, 16.79] },
{ name: '陕西省', value: [108.9480, 34.2632, 1.04, 21.38] },
{ name: '重庆市', value: [106.5049, 29.5332, 1.61, 13.24] },
{ name: '山东省', value: [120.3331, 36.0670, 1.02, 16.02] },
{ name: '湖南省', value: [112.9822, 28.1944, 0.68, 14.25] },
{ name: '安徽省', value: [117.2830, 31.8611, 0.93, 12.92] },
{ name: '辽宁省', value: [123.4321, 41.8007, 0.77, 11.37] },
{ name: '云南省', value: [102.7123, 25.0406, 0.1, 3.68] },
{ name: '天津市', value: [117.1901, 39.1258, 7.79, 9.34] },
{ name: '河南省', value: [113.6654, 34.7579, 0.55, 9.07] },
{ name: '吉林省', value: [125.3245, 43.8171, 0.15, 2.7] },
{ name: '江西省', value: [115.8581, 28.6820, 0.25, 4.17] },
{ name: '广西壮族自治区', value: [108.3200, 22.8241, 0.14, 3.15] },
{ name: '黑龙江省', value: [126.5349, 45.8038, 0.11, 5.13] },
{ name: '澳门特别行政区', value: [113.5490, 22.1987, 737.5, 2.36] },
{ name: '河北省', value: [114.5024, 38.0454, 0.26, 4.74] },
{ name: '山西省', value: [112.5489, 37.8570, 0.25, 3.78] },
{ name: '贵州省', value: [106.7135, 26.5783, 0.11, 1.93] },
{ name: '内蒙古自治区', value: [111.6708, 40.8183, 0.01, 0.96] },
{ name: '香港特别行政区', value: [114.1095, 22.3964, 1797.7, 197.75] },
{ name: '新疆维吾尔自治区', value: [87.6168, 43.7928, 0.01, 0.77] },
{ name: '甘肃省', value: [103.8235, 36.0580, 0.04, 1.46] },
{ name: '海南省', value: [110.3312, 20.0319, 0.36, 1.25] },
{ name: '宁夏回族自治区', value: [106.2782, 38.4664, 0.05, 0.27] },
{ name: '西藏自治区', value: [91.1175, 29.6469, 0.01, 0.14] },
{ name: '青海省', value: [101.7782, 36.6172, 0.01, 0.18] }
]);
// 计算 TOP 5 地区
const topRegions = computed(() => {
const sorted = [...talentMapData.value]
.map(item => ({
name: item.name,
score: item.value[3],
density: item.value[2]
}))
.sort((a, b) => b.score - a.score);
return sorted.slice(0, 5);
});
// 挂载时初始化
onMounted(() => {
setTimeout(() => {
initTalentMapChart()
}, 100);
});
const initTalentMapChart = () => {
const el = document.getElementById('talentMapChart');
if (!el) return;
if (talentMapChart.value) {
talentMapChart.value.dispose();
}
const myChart = echarts.init(el);
talentMapChart.value = myChart;
// --- 修改后的加载逻辑:直接获取 JSON ---
// 这里可以换成你本地项目的路径,例如:'/maps/china.json'
const MAP_JSON_URL = 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json';
// 1. 注册地图数据
echarts.registerMap('china', geoJson);
// 2. 调用渲染函数
renderMap();
// 渲染地图的函数 (配置保持原有的科技感风格)
function renderMap() {
myChart.setOption({
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(17, 54, 122, 0.95)',
borderColor: '#00A0FF',
borderWidth: 1,
borderRadius: 8,
padding: 16,
textStyle: { color: '#6dd0e3', fontSize: 14 },
formatter: (params: any) => {
if (params.seriesType === 'scatter') {
const data = params.data.value;
const density = data[2];
const total = data[3];
// 内部辅助函数获取等级
const getLevel = (d: number) => {
if(d > 500) return '人才极高密度区';
if(d > 100) return '人才高密度区';
return '人才稳步增长区';
};
return `
<div style="line-height: 2;">
<div style="font-weight: bold; color: #00F2FF; font-size: 16px; margin-bottom: 10px;">${params.name}</div>
<div><span style="color: #6dd0e3;">人才密度:</span><span style="color: #00C0FF; font-weight: 700;">${density}</span> 人/km²</div>
<div><span style="color: #6dd0e3;">人才总量:</span><span style="color: #00C0FF; font-weight: 700;">${total}</span> 万人</div>
<div style="margin-top: 8px; padding: 4px 10px; background: rgba(0, 242, 255, 0.2); border-radius: 6px; color: #00F2FF; font-size: 12px;">${getLevel(density)}</div>
</div>
`;
}
return '';
}
},
visualMap: {
show: false,
min: 0,
max: 500,
inRange: {
color: ['#00A0FF', '#0063D0', '#004190'],
symbolSize: [10, 60]
}
},
geo: {
map: 'china',
roam: true,
scaleLimit: { min: 0.8, max: 4 },
zoom: 2.6, // 调整为 standard zoom
center: [104.5, 37.5],
label: {
show: true,
fontSize: 10,
color: '#CEF4FD'
},
itemStyle: {
areaColor: 'rgba(0, 30, 80, 0.6)',
borderColor: 'rgba(0, 160, 255, 0.5)',
borderWidth: 1.5,
shadowBlur: 10,
shadowColor: 'rgba(0, 160, 255, 0.3)'
},
emphasis: {
itemStyle: {
areaColor: 'rgba(0, 50, 120, 0.8)',
borderColor: '#00C0FF'
}
}
},
series: [
{
name: '开源人才密度',
type: 'scatter',
coordinateSystem: 'geo',
data: talentMapData.value,
symbolSize: (val: any) => Math.sqrt(val[2]) * 2 + 8,
itemStyle: {
color: (params: any) => {
const density = params.value[2];
if (density >= 500) return '#FF4900';
if (density >= 100) return '#FFAB00';
return '#00C0FF';
},
shadowBlur: 20,
shadowColor: 'rgba(0, 242, 255, 0.6)'
}
}
]
});
}
};
</script>
<style lang="scss" scoped>
.map-container {
display: flex;
gap: 2rem;
align-items: stretch;
position: relative;
min-height: 600px;
}
.map-container .chart {
flex: 1;
min-height: 600px;
background: linear-gradient(135deg,
rgba(0, 20, 60, 0.4) 0%,
rgba(0, 40, 100, 0.3) 100%);
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
box-shadow: 0 8px 24px rgba(0, 192, 255, 0.2);
padding: 1rem;
}
.map-rank-panel {
width: 300px;
border-radius: 1rem;
border: 2px solid rgba(0, 192, 255, 0.3);
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 192, 255, 0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
gap: 1rem;
}
.panel-title {
font-size: 1.2rem;
font-weight: 700;
color: black;
text-align: center;
padding-bottom: 1rem;
margin-bottom: 0.5rem;
border-bottom: 2px solid rgba(0, 242, 255, 0.3);
}
.rank-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
max-height: 400px;
overflow-y: auto;
padding-right: 5px;
}
.rank-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.85rem 1rem;
background: rgba(0, 192, 255, 0.08);
border-radius: 0.75rem;
border: 1px solid rgba(0, 192, 255, 0.2);
border-left: 3px solid #00A0FF;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
margin-bottom: 0.75rem;
}
.rank-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 242, 255, 0.15), transparent);
transition: left 0.5s;
}
.rank-item:hover::before {
left: 100%;
}
.rank-item:hover {
background: rgba(0, 192, 255, 0.2);
border-color: #00F2FF;
border-left-color: #00F2FF;
box-shadow: 0 6px 16px rgba(0, 242, 255, 0.3);
transform: translateX(5px) scale(1.02);
}
.rank-num {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 700;
flex-shrink: 0;
font-family: 'DIN Alternate', 'Arial', sans-serif;
}
.rank-num.rank-1 {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(255, 215, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-2 {
background: linear-gradient(135deg, #E8E8E8, #C0C0C0);
color: #1a1a1a;
box-shadow: 0 4px 12px rgba(192, 192, 192, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-3 {
background: linear-gradient(135deg, #CD7F32, #B8733C);
color: white;
box-shadow: 0 4px 12px rgba(205, 127, 50, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
}
.rank-num.rank-4,
.rank-num.rank-5 {
background: rgba(0, 192, 255, 0.2);
color: #00C0FF;
font-weight: bold;
border: 2px solid rgba(0, 192, 255, 0.4);
}
.rank-name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #000000;
}
.rank-score {
font-size: 14px;
font-weight: 700;
color: #00F2FF;
font-family: 'DIN Alternate', 'Arial', sans-serif;
text-shadow: 0 0 8px rgba(0, 242, 255, 0.5);
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<div class="index-module__NV_5cW__titleSection">
<div class="stats-glass-bar">
<div class="stat-node">
<span class="stat-value">{{ stats.enterprise.toLocaleString() }}+</span>
<span class="stat-desc">全球开源企业</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ stats.university.toLocaleString() }}+</span>
<span class="stat-desc">全球高校参与</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ stats.activity.toLocaleString() }}+</span>
<span class="stat-desc">全球社区活动</span>
</div>
<div class="stat-separator"></div>
<div class="stat-node">
<span class="stat-value">{{ stats.project.toLocaleString() }}亿+</span>
<span class="stat-desc">全球开源项目</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { overviewDataPage1 } from '@/api/jyh/situation';
// 定义响应式数据对象,初始值为 0 或 null
const stats = reactive({
enterprise: 0,
university: 0,
activity: 0,
project: 0
});
// 加载状态(可选,用于显示 skeleton 或 loading
const loading = ref(true);
const formatProjectValue = (val: number) => {
if (val >= 100000000) {
return (val / 100000000).toFixed(1) + '亿+';
} else if (val >= 10000) {
return (val / 10000).toFixed(0) + '万+';
}
return val + '+';
};
const fetchStats = async () => {
try {
loading.value = true;
const { data:res } = await overviewDataPage1({});
if (res.code === 200) {
// 将接口数据赋值给响应式对象
Object.assign(stats, res.data);
}
} catch (error) {
console.error('获取统计数据失败:', error);
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchStats();
});
</script>
<style scoped lang="scss">
.index-module__NV_5cW__titleSection {
color: #333;
z-index: 2;
flex-direction: column;
align-items: center;
gap: 1rem;
display: flex;
position: relative;
}
.stats-glass-bar {
display: flex;
align-items: center;
justify-content: space-between;
/* 布局控制:撑满容器并增加内边距 */
width: 100%;
padding: 2rem 4rem;
//margin: 2rem 0;
/* 深色玻璃拟态背景 */
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
/* 极细微的边框勾勒 */
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
/* 外阴影与内发光,增加悬浮感 */
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3),
inset 0 0 20px rgba(255, 255, 255, 0.02);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(56, 189, 248, 0.4);
background: rgba(255, 255, 255, 0.04);
transform: translateY(-2px);
}
}
.stat-node {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
.stat-value {
text-shadow: 0 0 25px rgba(56, 189, 248, 0.8);
}
}
}
.stat-value {
/* 科技感亮蓝渐变 */
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 2.25rem;
font-weight: 800;
background: linear-gradient(180deg, #fff 0%, #38bdf8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
/* 基础发光效果 */
filter: drop-shadow(0 0 10px rgba(56, 189, 248, 0.3));
letter-spacing: -1px;
}
.stat-desc {
/* 在黑底上使用半透明白色,更有高级感 */
color: rgba(255, 255, 255, 0.6);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.1rem;
text-transform: uppercase;
}
.stat-separator {
/* 渐隐分割线 */
width: 1px;
height: 3rem;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 0.2),
rgba(255, 255, 255, 0)
);
margin: 0 1rem;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.stats-glass-bar {
padding: 2rem;
}
.stat-value {
font-size: 1.75rem;
}
}
@media (max-width: 768px) {
.stats-glass-bar {
flex-direction: column;
gap: 2rem;
}
.stat-separator {
width: 50%;
height: 1px;
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.2), transparent);
}
}
</style>

View File

@@ -0,0 +1,401 @@
<template>
<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>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import { cveDataGlobalPage1, cveDataRegionalPage1 } from '@/api/jyh/situation';
// 视图切换状态变量global: 全球, regional: 区域)
const cveView = ref('global');
// 空数据标识
const isEmpty = ref({
cveData: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
CVEChart: null
});
// CVE图表数据 - 响应式数据
const cveData = ref({
global: {
cveCount: [] as number[],
acceleration: [] as number[]
},
regional: {
cveCount: [] as number[],
acceleration: [] as number[]
}
});
// 获取后端数据
const fetchCveData = async () => {
try {
console.log('开始请求全球CVE数据...');
// 请求全球数据
const globalResponse = await cveDataGlobalPage1();
const globalBusinessData = globalResponse.data; // Axios响应格式业务数据在response.data中
console.log('全球CVE数据响应:', globalResponse);
if (globalBusinessData?.code === 200) {
console.log('全球CVE数据:', globalBusinessData.data);
cveData.value.global = {
cveCount: globalBusinessData.data.cveCount,
acceleration: globalBusinessData.data.acceleration
};
} else {
console.error('获取全球CVE数据失败:', globalBusinessData?.msg || '未知错误');
// 设置默认数据
setDefaultGlobalData();
}
console.log('开始请求区域CVE数据...');
// 请求区域数据
const regionalResponse = await cveDataRegionalPage1();
const regionalBusinessData = regionalResponse.data; // Axios响应格式业务数据在response.data中
console.log('区域CVE数据响应:', regionalResponse);
if (regionalBusinessData?.code === 200) {
console.log('区域CVE数据:', regionalBusinessData.data);
cveData.value.regional = {
cveCount: regionalBusinessData.data.cveCount,
acceleration: regionalBusinessData.data.acceleration
};
} else {
console.error('获取区域CVE数据失败:', regionalBusinessData?.msg || '未知错误');
// 设置默认数据
setDefaultRegionalData();
}
// 检查是否有有效数据
const hasGlobalData = cveData.value.global.cveCount.length > 0;
const hasRegionalData = cveData.value.regional.cveCount.length > 0;
isEmpty.value.cveData = !hasGlobalData && !hasRegionalData;
} catch (error) {
console.error('获取CVE数据异常:', error);
// 发生异常时设置默认数据
setDefaultGlobalData();
setDefaultRegionalData();
isEmpty.value.cveData = false; // 有默认数据,不算空
}
};
// 设置默认全球数据
const setDefaultGlobalData = () => {
cveData.value.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]
};
};
// 设置默认区域数据
const setDefaultRegionalData = () => {
cveData.value.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]
};
};
// CVE视图切换方法
const switchCveView = (view: 'global' | 'regional') => {
cveView.value = view;
nextTick(() => {
initCVEChart();
});
};
// 初始化CVE图表柱状图+折线图)
const initCVEChart = () => {
const el = document.getElementById('CVEChart');
if (!el) return;
// 延迟执行以确保DOM已渲染
setTimeout(() => {
const chartEl = document.getElementById('CVEChart');
if (!chartEl) return;
if (chartInstances.value.CVEChart) {
chartInstances.value.CVEChart.dispose();
}
const myChart = echarts.init(chartEl);
chartInstances.value.CVEChart = myChart;
// 获取当前视图的数据
const currentData = cveData.value[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'
}
}
]
});
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', () => {
if (chartInstances.value.CVEChart) {
chartInstances.value.CVEChart.resize();
}
});
}, 0);
};
// 在组件挂载后获取数据并初始化图表
onMounted(async () => {
await fetchCveData();
nextTick(() => {
initCVEChart();
});
});
// 监听数据变化,重新初始化图表
watch(cveData, () => {
if (!isEmpty.value.cveData) {
nextTick(() => {
initCVEChart();
});
}
}, { deep: true });
// 在组件卸载前清理资源
onUnmounted(() => {
if (chartInstances.value.CVEChart) {
chartInstances.value.CVEChart.dispose();
}
});
defineExpose({
initCVEChart,
switchCveView,
cveView,
cveData
});
</script>
<style scoped>
.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: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: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
/* 图表卡片头部样式 */
.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: #c1c7cd;
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;
}
/* 与示例一致的空数据样式 */
.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%;
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<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"></div>
<div v-else ref="chartRef" class="chart"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, nextTick, watch, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { highRiskComponentPage1 } from '@/api/jyh/situation';
// 定义组件数据结构
interface ComponentData {
name: string;
cvss: number;
vulnCount: number;
usage: string;
desc: string;
}
// 定义属性,允许从父组件传入数据
const props = defineProps<{
data?: Array<ComponentData>;
loading?: boolean;
}>();
// 定义 emits 事件
const emit = defineEmits<{
(e: 'chart-ready', chart: echarts.ECharts): void;
}>();
// 空数据标识
const isEmpty = ref(false);
// 图表DOM引用
const chartRef = ref<HTMLDivElement | null>(null);
// 图表实例存储
let chartInstance: echarts.ECharts | null = null;
// 组件数据
const componentData = ref<ComponentData[]>([]);
// 获取高危组件数据
const fetchComponentData = async () => {
try {
const response = await highRiskComponentPage1();
if (response.data.code === 200) {
componentData.value = response.data.data;
} else {
console.error('获取高危组件数据失败:', response.data.msg || '未知错误');
// 如果接口调用失败,使用默认数据
componentData.value = [
{ 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和原型污染漏洞' }
];
}
} catch (error) {
console.error('请求高危组件数据时发生错误:', error);
// 发生异常时使用默认数据
componentData.value = [
{ 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和原型污染漏洞' }
];
}
};
// 初始化图表
const initChart = async () => {
if (!chartRef.value) return;
// 销毁之前的实例
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
// 如果有传入的数据,则优先使用传入的数据,否则使用获取到的数据
const chartData = props.data || componentData.value;
// 检查是否有有效数据
isEmpty.value = !chartData || chartData.length === 0;
if (isEmpty.value) {
return;
}
// 准备图表数据
const componentNames = chartData.map(item => item.name);
const cvssScores = chartData.map(item => item.cvss);
const vulnCounts = chartData.map(item => item.vulnCount);
const usages = chartData.map(item => item.usage);
const descs = chartData.map(item => item.desc);
// 配置图表选项,与原页面保持一致
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: (params: any) => {
const data = chartData[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: componentNames,
axisLabel: { fontSize: 12 },
inverse: true // 反转Y轴让最高的值在顶部
},
series: [
{
name: 'CVSS评分',
type: 'bar',
data: cvssScores,
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
}
}
]
};
chartInstance.setOption(option);
// 触发图表准备就绪事件
emit('chart-ready', chartInstance);
};
// 组件挂载时初始化图表
onMounted(async () => {
await fetchComponentData(); // 获取高危组件数据
await nextTick();
initChart();
});
// 监听数据变化,重新渲染图表
watch(() => props.data, () => {
initChart();
}, { deep: true });
// 监听组件内部数据变化,重新渲染图表
watch(componentData, () => {
initChart();
}, { deep: true });
// 组件卸载时清理图表实例
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
// 页面大小变化时调整图表大小
const resizeHandler = () => {
if (chartInstance) {
chartInstance.resize();
}
};
// 监听窗口大小变化
window.addEventListener('resize', resizeHandler);
// 在组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler);
});
</script>
<style scoped>
.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:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
.chart {
width: 100%;
height: 100%;
}
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<div class="index-module__NV_5cW__chartCard">
<h3 class="chartCard__title">行业分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="computedIsEmpty"></div>
<div v-else class="industry-distribution-container">
<div class="chart-container" ref="chartDiv"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue';
import * as echarts from 'echarts';
import { industryDistributionPage1 } from '@/api/jyh/situation';
// 定义数据类型
interface IndustryData {
value: number;
name: string;
}
// 定义配色方案
const industryColorPalette = [
'#00f0ff', '#00b03e', '#ffeb00', '#ffab00',
'#ff5100', '#c0232a', '#a262f2', '#379bff'
];
// 内部数据
const internalData = ref<IndustryData[]>([]);
// 响应式数据
const chartDiv = ref<HTMLDivElement | null>(null);
let myChart: echarts.ECharts | null = null;
// 计算 isEmpty 状态
const computedIsEmpty = computed(() => {
return internalData.value.length === 0;
});
// 初始化图表
const initChart = () => {
if (!chartDiv.value || computedIsEmpty.value) return;
// 销毁已存在的图表实例
if (myChart) {
myChart.dispose();
}
myChart = echarts.init(chartDiv.value);
// 构建图例数据,对于较长的名称才在图例中显示数值
const legendData = internalData.value.map(item => ({
name: item.name,
icon: 'circle'
}));
const option: echarts.EChartsOption = {
tooltip: {
trigger: 'item',
formatter: (params: any) => `${params.name}: ${params.value} (${params.percent}%)`
},
legend: {
bottom: '0',
textStyle: { fontSize: 10 },
itemWidth: 10,
itemHeight: 10,
orient: 'horizontal',
padding: [5, 0, 0, 0],
data: legendData
},
series: [
{
name: '行业分布',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '45%'],
data: internalData.value,
label: {
show: true,
formatter: '{b}\n{d}%',
fontSize: 10,
rich: {
b: {
fontSize: 10,
lineHeight: 12
},
d: {
fontSize: 10,
lineHeight: 12
}
}
},
itemStyle: {
color: (params: any) => {
return industryColorPalette[params.dataIndex % industryColorPalette.length];
}
}
}
],
grid: { left: 0, bottom: 0, top: 0, right: 0 }
};
myChart.setOption(option);
};
// 响应式处理
const handleResize = () => {
if (myChart) {
myChart.resize();
}
};
// 获取行业分布数据
const fetchIndustryData = async () => {
try {
const response = await industryDistributionPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData.code === 200 && businessData.data) {
internalData.value = businessData.data;
} else {
console.error('获取行业分布数据失败:', businessData.msg || '未知错误');
internalData.value = [
{ 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: '移动开发' }
];
}
} catch (error) {
console.error('请求行业分布数据时发生错误:', error);
internalData.value = [
{ 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: '移动开发' }
];
}
};
// 生命周期钩子
onMounted(async () => {
await fetchIndustryData(); // 获取行业分布数据
await nextTick();
initChart();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
if (myChart) {
myChart.dispose();
}
});
</script>
<style lang="scss" scoped>
.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: left;
}
.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;
}
.num-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.industry-distribution-container {
width: 100%;
height: 100%;
position: relative;
}
.chart-container {
width: 100%;
height: 100%;
min-height: 300px;
}
/* 响应式样式 */
@media (max-width: 1024px) {
.chartCard__container {
height: 250px; /* 移动端图表高度调整 */
}
}
@media (max-width: 480px) {
.chartCard__container {
height: 200px; /* 小屏移动端图表高度调整 */
}
}
</style>

View File

@@ -0,0 +1,327 @@
<template>
<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>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
import { feedListPage1 } from '@/api/jyh/situation';
//定义组件数据结构
interface FeedData {
type: string;
content: string;
}
// 接收外部数据作为属性
const props = defineProps<{
initialData?: Array<FeedData>;
loading?: boolean;
}>();
//组件数据
const feedList = ref<FeedData[]>([]);
//获取情报流数据
const fetchFeedData = async() => {
try {
console.log('开始请求情报流数据...');
const response = await feedListPage1();
if (response.data?.code === 200) {
feedList.value = response.data.data || [];
} else {
console.error('获取情报流数据失败:', response?.data?.msg || '未知错误');
// 如果接口调用失败,使用默认数据
feedList.value = [
{
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 个伪造开源组件,包含后门程序'
}
];
}
} catch (error) {
console.error('获取情报流数据异常:', error);
// 发生异常时使用默认数据
feedList.value = [
{
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 个伪造开源组件,包含后门程序'
}
];
}
};
// 如果提供了初始数据,则使用提供的数据
watch(() => props.initialData, (newData) => {
if (newData && newData.length > 0) {
feedList.value = newData;
}
}, { immediate: true });
// 1. 定义滚动容器的ref
const scrollContainer1 = 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 : null;
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. 组件挂载时启动滚动
onMounted(async() => {
await fetchFeedData(); // 获取情报流数据
nextTick(() => {
autoScroll(1);
});
});
// 8. 组件卸载时清除定时器
onUnmounted(() => {
Object.keys(scrollTimers).forEach(key => {
const k = Number(key);
if (scrollTimers[k]) {
clearInterval(scrollTimers[k]!);
scrollTimers[k] = null;
}
});
});
</script>
<style scoped>
/* 使用主页面相同的CSS类定义以确保样式一致性 */
.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;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: left;
}
.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-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.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.投毒 {
background: #ffa500;
}
.project-rank.CNVD {
background: #ffff00;
color: #000;
}
.project-rank.GitHub {
background: #0000ff;
}
.project-rank.修复 {
background: #008000;
}
.project-rank.供应链 {
background: #800080;
} */
.project-name {
font-size: 14px;
font-weight: 600;
color: #333;
line-height: 1.4;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<div class="index-module__NV_5cW__chartCard mb-[1.5rem]">
<h3 class="chartCard__title">编程语言安全态势</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.scatterData"></div>
<div v-else id="scatterChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import { scatterDataPage1 } from '@/api/jyh/situation';
// 空数据标识
const isEmpty = ref({
scatterData: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
scatterChart: null
});
// 气泡图数据 - 响应式数据
const scatterData = ref({
scatterData: [] as any[],
midX: 50,
midY: 50,
quadrantColors: {
Q1: '#BF232A', // [高密度, 低活跃度] - 极高风险
Q2: '#FF4900', // [高密度, 高活跃度] - 高风险,需密切关注
Q3: '#00B43E', // [低密度, 低活跃度] - 低风险,但维护成本高
Q4: '#0063D0' // [低密度, 高活跃度] - 健康生态
}
});
// 获取后端数据
const fetchScatterData = async () => {
try {
console.log('开始请求编程语言安全态势数据...');
const response = await scatterDataPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
console.log('API响应:', response);
if (businessData?.code === 200) {
console.log('API返回的数据:', businessData.data);
// 处理后端返回的数据格式
const processedData = processBackendScatterData(businessData.data);
scatterData.value.scatterData = processedData;
// 更新空数据标识
isEmpty.value.scatterData = !processedData || processedData.length === 0;
} else {
console.error('获取编程语言安全态势数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
setDefaultScatterData();
}
} catch (error) {
console.error('获取编程语言安全态势数据异常:', error);
// 发生异常时设置默认数据
setDefaultScatterData();
}
};
// 处理后端返回的数据格式
const processBackendScatterData = (backendData: any[]) => {
// 后端返回的数据已经是正确的格式,直接转换即可
return backendData.map(item => [
item.x, // 代码活跃度(X)
item.y, // 漏洞密度(Y)
item.size, // 漏洞总数(Size)
item.name // 语言名称(Name)
]);
};
// 设置默认数据
const setDefaultScatterData = () => {
scatterData.value.scatterData = [
[95, 75, 450, 'JavaScript'], // 最高活跃度9,345,046个项目
[90, 70, 380, 'Python'], // 高活跃度9,261,587个项目
[85, 65, 320, 'TypeScript'], // 高增长78.1%增长率
[75, 80, 420, 'Java'], // 传统强势3,520,215个项目
[45, 60, 280, 'C++'], // 中等活跃1,701,552个项目
[40, 55, 250, 'C#'], // 中等活跃1,478,463个项目
[35, 45, 180, 'PHP'], // 传统Web语言
[25, 25, 80, 'Go'], // 新兴语言,低漏洞密度
[15, 20, 45, 'Rust'], // 安全性较好的新语言
[60, 50, 200, 'Shell'], // 脚本语言
];
isEmpty.value.scatterData = false;
};
// 初始化气泡图
const initScatterChart = () => {
const el = document.getElementById('scatterChart');
if (!el) return;
// 延迟执行以确保DOM已渲染
setTimeout(() => {
const chartEl = document.getElementById('scatterChart');
if (!chartEl) return;
if (chartInstances.value.scatterChart) {
chartInstances.value.scatterChart.dispose();
}
const myChart = echarts.init(chartEl);
chartInstances.value.scatterChart = myChart;
// 数据处理
const data = scatterData.value.scatterData.map(item => ({
value: [item[0], item[1], item[2]],
name: item[3]
}));
// 计算四象限颜色
const getColor = (x: number, y: number) => {
if (x < scatterData.value.midX && y >= scatterData.value.midY) return scatterData.value.quadrantColors.Q1; // 高密度, 低活跃度 - 极高风险
if (x >= scatterData.value.midX && y >= scatterData.value.midY) return scatterData.value.quadrantColors.Q2; // 高密度, 高活跃度 - 高风险
if (x < scatterData.value.midX && y < scatterData.value.midY) return scatterData.value.quadrantColors.Q3; // 低密度, 低活跃度 - 低风险
return scatterData.value.quadrantColors.Q4; // 低密度, 高活跃度 - 健康生态
};
myChart.setOption({
tooltip: {
trigger: 'item',
formatter: (params: any) => {
return `
<div>${params.data.name}</div>
<div>代码活跃度: ${params.data.value[0]}</div>
<div>漏洞密度: ${params.data.value[1]}</div>
<div>漏洞总数: ${params.data.value[2]}</div>
`;
}
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: {
type: 'value',
name: '代码活跃度',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
splitLine: { show: true }
},
yAxis: {
type: 'value',
name: '漏洞密度',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 },
splitLine: { show: true }
},
series: [{
type: 'scatter',
data: data,
symbolSize: (val: any) => val[2] / 2, // 根据漏洞总数调整气泡大小
itemStyle: {
color: (params: any) => getColor(params.value[0], params.value[1])
},
label: {
show: true,
formatter: (params: any) => params.data.name,
position: 'inside',
fontSize: 10,
color: '#fff'
}
}]
});
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', () => {
if (chartInstances.value.scatterChart) {
chartInstances.value.scatterChart.resize();
}
});
}, 0);
};
// 在组件挂载后获取数据并初始化图表
onMounted(async () => {
await fetchScatterData();
nextTick(() => {
initScatterChart();
});
});
// 监听数据变化,重新初始化图表
watch(scatterData, () => {
if (!isEmpty.value.scatterData) {
nextTick(() => {
initScatterChart();
});
}
}, { deep: true });
// 在组件卸载前清理资源
onUnmounted(() => {
if (chartInstances.value.scatterChart) {
chartInstances.value.scatterChart.dispose();
}
});
defineExpose({
initScatterChart
});
</script>
<style scoped>
.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:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
.mb-\[1\.5rem\] {
margin-bottom: 1.5rem;
}
/* 与示例一致的空数据样式 */
.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%;
}
</style>

View File

@@ -60,19 +60,14 @@ Vue 3 适配版代码
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import * as echarts from 'echarts';
import worldJson from './world.json';
import { mapVulnStatsPage1, mapRegionStatsPage1, mapDataPage1, mapVulnTypeStatsPage1 } from '@/api/jyh/situation';
// 响应式数据
const chartDiv = ref(null);
let myChart = null;
let timer = null;
const vulnStats = ref([
{ name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' },
{ name: 'SQL 注入', value: 11.9, color: '#f97316' },
{ name: '内存缓冲区溢出', value: 10.9, color: '#eab308' },
{ name: '输入验证不当', value: 9.3, color: '#3b82f6' },
{ name: '信息泄露', value: 8.0, color: '#10b981' },
]);
const vulnStats = ref([]);
const regionStats = ref([
{ name: 'United States', score: 435202 },
@@ -82,11 +77,8 @@ const regionStats = ref([
{ name: 'Germany', score: 126397 },
]);
const alerts = [
{ title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" },
{ title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" },
{ title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" },
];
// 实时情报数据状态
const alerts = ref([]);
const currentAlert = ref(null);
let alertIndex = 0;
@@ -100,35 +92,118 @@ const geoCoordMap = {
'Moscow': [37.6173, 55.7558],
};
// 获取实时情报数据
const fetchAlertsData = async () => {
try {
const response = await mapVulnTypeStatsPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200) {
alerts.value = businessData.data || [];
if (alerts.value.length > 0) {
currentAlert.value = alerts.value[0];
}
} else {
console.error('获取实时情报数据失败:', businessData?.msg || '未知错误');
// 如果接口调用失败,使用默认数据
alerts.value = [
{ title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" },
{ title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" },
{ title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" },
];
if (alerts.value.length > 0) {
currentAlert.value = alerts.value[0];
}
}
} catch (error) {
console.error('请求实时情报数据时发生错误:', error);
// 发生异常时使用默认数据
alerts.value = [
{ title: "高危漏洞通告", desc: "Apache Log4j2 远程代码执行漏洞复现" },
{ title: "僵尸网络活动", desc: "Mirai 变种正在扫描 IoT 设备端口 23" },
{ title: "APT 组织活动", desc: "OceanLotus 组织针对金融行业的钓鱼攻击" },
];
if (alerts.value.length > 0) {
currentAlert.value = alerts.value[0];
}
}
};
// 逻辑方法
const startAlertRotation = () => {
currentAlert.value = alerts[0];
if (alerts.value.length > 0) {
currentAlert.value = alerts.value[0];
}
timer = setInterval(() => {
alertIndex = (alertIndex + 1) % alerts.length;
currentAlert.value = null;
setTimeout(() => {
currentAlert.value = alerts[alertIndex];
}, 500);
if (alerts.value.length > 0) {
alertIndex = (alertIndex + 1) % alerts.value.length;
currentAlert.value = null;
setTimeout(() => {
currentAlert.value = alerts.value[alertIndex];
}, 500);
}
}, 3500);
};
const setChart = () => {
// 地图数据状态
const mapData = ref([]);
const scatterData = ref([]);
// 获取地图数据
const fetchMapData = async () => {
try {
const response = await mapDataPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200) {
mapData.value = businessData.data;
} else {
console.error('获取地图数据失败:', businessData?.msg || '未知错误');
// 如果接口调用失败,使用默认数据
mapData.value = [
{ name: 'China', value: 100 },
{ name: 'United States', value: 80 },
{ name: 'Russia', value: 60 },
{ name: 'Brazil', value: 40 },
{ name: 'Canada', value: 30 },
{ name: 'Australia', value: 20 },
{ name: 'India', value: 50 },
{ name: 'Germany', value: 45 },
{ name: 'United Kingdom', value: 40 },
{ name: 'France', value: 35 },
];
}
} catch (error) {
console.error('请求地图数据时发生错误:', error);
// 发生异常时使用默认数据
mapData.value = [
{ name: 'China', value: 100 },
{ name: 'United States', value: 80 },
{ name: 'Russia', value: 60 },
{ name: 'Brazil', value: 40 },
{ name: 'Canada', value: 30 },
{ name: 'Australia', value: 20 },
{ name: 'India', value: 50 },
{ name: 'Germany', value: 45 },
{ name: 'United Kingdom', value: 40 },
{ name: 'France', value: 35 },
];
}
};
const setChart = async () => {
if (!chartDiv.value) return;
echarts.registerMap('world', worldJson);
const mapData = [
{ name: 'China', value: 100 },
{ name: 'United States', value: 80 },
{ name: 'Russia', value: 60 },
{ name: 'Brazil', value: 40 },
{ name: 'Canada', value: 30 },
{ name: 'Australia', value: 20 },
{ name: 'India', value: 50 },
{ name: 'Germany', value: 45 },
{ name: 'United Kingdom', value: 40 },
{ name: 'France', value: 35 },
];
const scatterData = [
// 初始化散点数据
const geoCoordMap = {
'Beijing': [116.407395, 39.904211],
'Shanghai': [121.473701, 31.230416],
'Guangzhou': [113.264385, 23.129112],
'Chengdu': [104.066541, 30.572269],
'Washington': [-77.036871, 38.907192],
'Moscow': [37.6173, 55.7558],
};
const scatterDataLocal = [
{ name: '北京中心', value: [...geoCoordMap['Beijing'], 100] },
{ name: '上海节点', value: [...geoCoordMap['Shanghai'], 80] },
{ name: '广州节点', value: [...geoCoordMap['Guangzhou'], 70] },
@@ -186,13 +261,13 @@ const setChart = () => {
name: 'Threat Distribution',
type: 'map',
geoIndex: 0,
data: mapData
data: mapData.value
},
{
name: 'Key Nodes',
type: 'effectScatter',
coordinateSystem: 'geo',
data: scatterData,
data: scatterDataLocal,
symbolSize: (val) => val[2] / 5,
showEffectOn: 'render',
rippleEffect: {
@@ -222,10 +297,78 @@ const setChart = () => {
myChart.setOption(option);
};
// 获取漏洞类型统计数据
const fetchVulnStats = async () => {
try {
const response = await mapVulnStatsPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200) {
// 为每个数据项添加颜色
const colors = ['#ef4444', '#f97316', '#eab308', '#3b82f6', '#10b981', '#8b5cf6', '#ec4899', '#06b6d4'];
vulnStats.value = businessData.data.map((item, index) => ({
...item,
color: colors[index % colors.length]
}));
} else {
console.error('获取漏洞类型统计数据失败:', businessData?.msg || '未知错误');
// 如果接口调用失败,使用默认数据
vulnStats.value = [
{ name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' },
{ name: 'SQL 注入', value: 11.9, color: '#f97316' },
{ name: '内存缓冲区溢出', value: 10.9, color: '#eab308' },
{ name: '输入验证不当', value: 9.3, color: '#3b82f6' },
{ name: '信息泄露', value: 8.0, color: '#10b981' },
];
}
} catch (error) {
console.error('请求漏洞类型统计数据时发生错误:', error);
// 发生异常时使用默认数据
vulnStats.value = [
{ name: 'XSS 跨站脚本', value: 32.1, color: '#ef4444' },
{ name: 'SQL 注入', value: 11.9, color: '#f97316' },
{ name: '内存缓冲区溢出', value: 10.9, color: '#eab308' },
{ name: '输入验证不当', value: 9.3, color: '#3b82f6' },
{ name: '信息泄露', value: 8.0, color: '#10b981' },
];
}
};
// 获取威胁情报地域排行数据
const fetchRegionStats = async () => {
try {
const response = await mapRegionStatsPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
if (businessData?.code === 200) {
regionStats.value = businessData.data;
} else {
console.error('获取威胁情报地域排行数据失败:', businessData?.msg || '未知错误');
// 如果接口调用失败,使用默认数据
regionStats.value = [
{ name: 'United States', score: 435202 },
{ name: 'India', score: 252054 },
{ name: 'China (中国)', score: 184085 },
{ name: 'Brazil', score: 174811 },
{ name: 'Germany', score: 126397 },
];
}
} catch (error) {
console.error('请求威胁情报地域排行数据时发生错误:', error);
// 发生异常时使用默认数据
regionStats.value = [
{ name: 'United States', score: 435202 },
{ name: 'India', score: 252054 },
{ name: 'China (中国)', score: 184085 },
{ name: 'Brazil', score: 174811 },
{ name: 'Germany', score: 126397 },
];
}
};
// 生命周期钩子
onMounted(async () => {
await nextTick(); // 确保 DOM 已渲染
setChart();
await Promise.all([fetchVulnStats(), fetchRegionStats(), fetchMapData(), fetchAlertsData()]); // 同时获取所有统计数据
await setChart(); // 等待图表设置完成
startAlertRotation();
window.addEventListener('resize', handleResize);
});

View File

@@ -0,0 +1,381 @@
<template>
<div class="index-module__NV_5cW__chartCard chartCard__wide mb-[1.5rem]">
<h3 class="chartCard__title">漏洞语言分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.languageTrend"></div>
<div v-else id="languageTrendChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import { languageTrendPage1 } from '@/api/jyh/situation';
// 空数据标识
const isEmpty = ref({
languageTrend: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
languageTrendChart: null
});
// 漏洞语言分布数据 - 响应式数据
const languageTrendData = ref({
years: [] as string[],
series: [] as any[]
});
// 获取后端数据
const fetchLanguageTrendData = async () => {
try {
console.log('开始请求漏洞语言分布数据...');
const response = await languageTrendPage1();
const businessData = response.data; // Axios响应格式业务数据在response.data中
console.log('API响应:', response);
if (businessData?.code === 200) {
console.log('API返回的数据:', businessData.data);
// 处理后端返回的数据格式
const processedData = processBackendData(businessData.data);
languageTrendData.value = processedData;
// 更新空数据标识
isEmpty.value.languageTrend = !processedData.years || processedData.years.length === 0;
} else {
console.error('获取漏洞语言分布数据失败:', businessData?.msg || '未知错误');
// 设置默认数据
setDefaultData();
}
} catch (error) {
console.error('获取漏洞语言分布数据异常:', error);
// 发生异常时设置默认数据
setDefaultData();
}
};
// 处理后端返回的数据格式
const processBackendData = (backendData: any[]) => {
// 按语言分组
const groupedData: { [key: string]: { year: string, value: number }[] } = {};
backendData.forEach(item => {
if (!groupedData[item.name]) {
groupedData[item.name] = [];
}
groupedData[item.name].push({
year: item.year,
value: item.data
});
});
// 提取所有年份并排序
const allYearsSet = new Set<string>();
Object.values(groupedData).forEach(langData => {
langData.forEach(d => allYearsSet.add(d.year));
});
const years = Array.from(allYearsSet).sort();
// 生成系列数据
const series = Object.entries(groupedData).map(([name, data]) => {
// 根据年份顺序填充数据缺失的设为0
const values = years.map(year => {
const found = data.find(d => d.year === year);
return found ? found.value : 0;
});
// 这里可以使用预定义的颜色方案,或者动态生成
const predefinedColors: { [key: string]: string } = {
'JavaScript': '#FAC858',
'Python': '#5470C6',
'HTML/CSS': '#91CC75',
'SQL': '#9254DE',
'Java': '#EE6666',
'TypeScript': '#73C0DE',
'Shell': '#52C41A',
'C++': '#FF6E00',
'C#': '#722ED1',
'Go': '#00DDFF',
'PHP': '#F5222D',
'Rust': '#FC8452'
};
const color = predefinedColors[name] || getRandomColor();
return {
name: name,
type: 'established', // 可以根据实际情况调整
color: color,
data: values
};
});
return {
years: years,
series: series
};
};
// 设置默认数据
const setDefaultData = () => {
languageTrendData.value = {
years: ['2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'],
series: [
{
name: 'JavaScript',
type: 'established',
color: '#FAC858',
data: [65, 64, 69, 70, 69, 65, 61, 61]
},
{
name: 'Python',
type: 'established',
color: '#5470C6',
data: [32, 41, 49, 55, 52, 53, 54, 60]
},
{
name: 'HTML/CSS',
type: 'established',
color: '#91CC75',
data: [60, 55, 61, 61, 60, 54, 52, 52]
},
{
name: 'SQL',
type: 'established',
color: '#9254DE',
data: [42, 47, 56, 56, 54, 49, 52, 50]
},
{
name: 'Java',
type: 'established',
color: '#EE6666',
data: [47, 51, 50, 54, 49, 48, 49, 49]
},
{
name: 'TypeScript',
type: 'emerging',
color: '#73C0DE',
data: [12, 17, 25, 28, 29, 34, 34, 50]
},
{
name: 'Shell',
type: 'established',
color: '#52C41A',
data: [29, 29, 40, 39, 37, 34, 34, 41]
},
{
name: 'C++',
type: 'established',
color: '#FF6E00',
data: [17, 18, 20, 27, 23, 25, 25, 21]
},
{
name: 'C#',
type: 'established',
color: '#722ED1',
data: [20, 22, 24, 22, 21, 23, 21, 19]
},
{
name: 'Go',
type: 'emerging',
color: '#00DDFF',
data: [8, 12, 18, 19, 17, 19, 17, 14]
},
{
name: 'PHP',
type: 'declining',
color: '#F5222D',
data: [30, 26, 29, 27, 32, 20, 18, 15]
},
{
name: 'Rust',
type: 'emerging',
color: '#FC8452',
data: [2, 2, 5, 7, 6, 9, 10, 10]
}
]
};
isEmpty.value.languageTrend = false;
};
// 生成随机颜色的辅助函数
const getRandomColor = () => {
const colors = ['#FAC858', '#5470C6', '#91CC75', '#9254DE', '#EE6666', '#73C0DE', '#52C41A', '#FF6E00', '#722ED1', '#00DDFF', '#F5222D', '#FC8452'];
return colors[Math.floor(Math.random() * colors.length)];
};
// 初始化漏洞语言分布图表(堆叠面积图)
const initLanguageTrendChart = () => {
const el = document.getElementById('languageTrendChart');
if (!el) return;
// 延迟执行以确保DOM已渲染
setTimeout(() => {
const chartEl = document.getElementById('languageTrendChart');
if (!chartEl) return;
if (chartInstances.value.languageTrendChart) {
chartInstances.value.languageTrendChart.dispose();
}
const myChart = echarts.init(chartEl);
chartInstances.value.languageTrendChart = myChart;
// 准备堆叠面积图数据
const series = languageTrendData.value.series.map(item => ({
name: item.name,
type: 'line',
stack: '总量',
areaStyle: {
color: item.color
},
lineStyle: {
width: 1,
color: item.color
},
symbol: 'none',
data: item.data
}));
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
formatter: (params: any) => {
let res = `<div>${params[0].name}年</div>`;
params.forEach((p: any) => {
res += `<div style="display:flex;align-items:center;"><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${p.color};"></span>${p.seriesName}: ${p.value}%</div>`;
});
return res;
}
},
legend: {
top: 0,
textStyle: { fontSize: 12 },
data: languageTrendData.value.series.map(item => item.name)
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: [
{
type: 'category',
boundaryGap: false,
data: languageTrendData.value.years,
axisLabel: { fontSize: 12 }
}
],
yAxis: [
{
type: 'value',
name: '使用率(%)',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
}
],
series: series
});
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', () => {
if (chartInstances.value.languageTrendChart) {
chartInstances.value.languageTrendChart.resize();
}
});
}, 0);
};
// 在组件挂载后获取数据并初始化图表
onMounted(async () => {
await fetchLanguageTrendData();
nextTick(() => {
initLanguageTrendChart();
});
});
// 监听数据变化,重新初始化图表
watch(languageTrendData, () => {
if (!isEmpty.value.languageTrend) {
nextTick(() => {
initLanguageTrendChart();
});
}
}, { deep: true });
// 在组件卸载前清理资源
onUnmounted(() => {
if (chartInstances.value.languageTrendChart) {
chartInstances.value.languageTrendChart.dispose();
}
});
defineExpose({
initLanguageTrendChart
});
</script>
<style scoped>
.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: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: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
.mb-\[1\.5rem\] {
margin-bottom: 1.5rem;
}
/* 与示例一致的空数据样式 */
.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%;
}
</style>

View File

@@ -0,0 +1,285 @@
<template>
<div class="index-module__NV_5cW__chartCard mb-[1.5rem]">
<h3 class="chartCard__title">漏洞等级分布</h3>
<div class="chartCard__container">
<div class="num-empty" v-if="isEmpty.vulnerabilitySeverity"></div>
<div v-else id="vulnerabilitySeverityChart" class="chart"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue';
import * as echarts from 'echarts';
import { vulnerabilitySeverityPage1 } from '@/api/jyh/situation';
// 空数据标识
const isEmpty = ref({
vulnerabilitySeverity: false
});
// 图表实例存储用于resize时销毁重绘
const chartInstances = ref<{ [key: string]: echarts.ECharts | null }>({
vulnerabilitySeverityChart: null
});
// 漏洞等级分布数据 - 响应式数据
const vulnerabilitySeverityData = ref({
categories: [] as string[],
series: [] as any[]
});
// 获取后端数据
const fetchVulnerabilitySeverityData = async() => {
try {
console.log('开始请求漏洞等级分布数据...');
const response = await vulnerabilitySeverityPage1();
console.log('API响应:', response);
const severityData = response.data; // Axios响应格式业务数据在response.data中
if (severityData?.code === 200) {
console.log('API返回的数据:', severityData.data);
// 处理后端返回的数据格式
const processedData = processBackendSeverityData(severityData.data);
vulnerabilitySeverityData.value = processedData;
// 更新空数据标识
isEmpty.value.vulnerabilitySeverity = !processedData.categories || processedData.categories.length === 0;
} else {
console.error('获取漏洞等级分布数据失败:', severityData?.msg || '未知错误');
// 设置默认数据
setDefaultSeverityData();
}
} catch (error) {
console.error('获取漏洞等级分布数据异常:', error);
// 发生异常时设置默认数据
setDefaultSeverityData();
}
};
// 处理后端返回的数据格式
const processBackendSeverityData = (backendData: any[]) => {
// 提取季度数据
const categories = backendData.map(item => item.quarter);
// 生成系列数据
const series = [
{
name: '高危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#FF4D4F' },
barWidth: 16,
data: backendData.map(item => item.highRisk)
},
{
name: '中危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#FAAD14' },
barWidth: 16,
data: backendData.map(item => item.mediumRisk)
},
{
name: '低危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#36CFC9' },
barWidth: 16,
data: backendData.map(item => item.lowRisk)
}
];
return {
categories: categories,
series: series
};
};
// 设置默认数据
const setDefaultSeverityData = () => {
vulnerabilitySeverityData.value = {
categories: ['2024Q1', '2024Q2', '2024Q3', '2024Q4', '2025Q1', '2025Q2', '2025Q3', '2025Q4'],
series: [
{
name: '高危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#FF4D4F' },
barWidth: 16,
data: [35, 42, 58, 45, 67, 78, 62, 85]
},
{
name: '中危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#FAAD14' },
barWidth: 16,
data: [65, 72, 88, 95, 112, 105, 120, 135]
},
{
name: '低危漏洞',
type: 'bar',
stack: 'total',
itemStyle: { color: '#36CFC9' },
barWidth: 16,
data: [120, 135, 150, 142, 165, 180, 175, 190]
}
]
};
isEmpty.value.vulnerabilitySeverity = false;
};
// 初始化漏洞等级分布图表
const initVulnerabilitySeverityChart = () => {
const el = document.getElementById('vulnerabilitySeverityChart');
if (!el) return;
// 延迟执行以确保DOM已渲染
setTimeout(() => {
const chartEl = document.getElementById('vulnerabilitySeverityChart');
if (!chartEl) return;
if (chartInstances.value.vulnerabilitySeverityChart) {
chartInstances.value.vulnerabilitySeverityChart.dispose();
}
const myChart = echarts.init(chartEl);
chartInstances.value.vulnerabilitySeverityChart = myChart;
myChart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params: any) => {
let res = `<div>${params[0].name}</div>`;
let total = 0;
params.forEach((p: any) => {
total += p.value;
});
params.forEach((p: any) => {
const percentage = ((p.value / total) * 100).toFixed(1);
res += `<div style="display:flex;align-items:center;"><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${p.color};"></span>${p.seriesName}: ${p.value} (${percentage}%)</div>`;
});
return res;
}
},
legend: {
top: 0,
textStyle: { fontSize: 12 },
data: vulnerabilitySeverityData.value.series.map(item => item.name)
},
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: [
{
type: 'category',
data: vulnerabilitySeverityData.value.categories,
axisLabel: { fontSize: 12 }
}
],
yAxis: [
{
type: 'value',
name: '漏洞数量',
nameTextStyle: { fontSize: 12 },
axisLabel: { fontSize: 12 }
}
],
series: vulnerabilitySeverityData.value.series
});
// 监听窗口大小变化,重新调整图表大小
window.addEventListener('resize', () => {
if (chartInstances.value.vulnerabilitySeverityChart) {
chartInstances.value.vulnerabilitySeverityChart.resize();
}
});
}, 0);
};
// 在组件挂载后获取数据并初始化图表
onMounted(async () => {
await fetchVulnerabilitySeverityData();
nextTick(() => {
initVulnerabilitySeverityChart();
});
});
// 监听数据变化,重新初始化图表
watch(vulnerabilitySeverityData, () => {
if (!isEmpty.value.vulnerabilitySeverity) {
nextTick(() => {
initVulnerabilitySeverityChart();
});
}
}, { deep: true });
// 在组件卸载前清理资源
onUnmounted(() => {
if (chartInstances.value.vulnerabilitySeverityChart) {
chartInstances.value.vulnerabilitySeverityChart.dispose();
}
});
defineExpose({
initVulnerabilitySeverityChart
});
</script>
<style scoped>
.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:hover {
border-color: #3b82f64d;
transform: translateY(-4px);
box-shadow: 0 8px 24px #0000001f;
}
.chartCard__title {
color: #333;
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 1rem 0;
text-align: left;
}
.chartCard__container {
width: 100%;
height: 300px; /* 图表高度 */
position: relative;
}
.mb-\[1\.5rem\] {
margin-bottom: 1.5rem;
}
/* 与示例一致的空数据样式 */
.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%;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ import { defineConfig, type PluginOption, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { visualizer } from "rollup-plugin-visualizer";
import dayjs from "dayjs";
import basicSSL from '@vitejs/plugin-basic-ssl';
import legacy from '@vitejs/plugin-legacy'
// import basicSSL from '@vitejs/plugin-basic-ssl';
// import legacy from '@vitejs/plugin-legacy'
const fs = require('node:fs')
export default (config: any) => {
@@ -20,10 +20,10 @@ export default (config: any) => {
}
}
}),
// 浏览器兼容性
legacy({
targets: ['defaults', 'chrome >= 78', 'safari >= 14'],
}),
// // 浏览器兼容性
// legacy({
// targets: ['defaults', 'chrome >= 78', 'safari >= 14'],
// }),
visualizer({
open: config.mode === 'analysis',
}) as PluginOption,
@@ -38,11 +38,17 @@ export default (config: any) => {
},
proxy: {
// 配置跨域代理
'/gateway': {
// '/gateway/official/u-auth/index/getCaptcha': {
// // target: 'http://223.76.236.155:20011', // 目标服务器地址
// target: 'http://222.20.126.217:9700', // 目标服务器地址
// changeOrigin: true, // 允许跨域
// rewrite: (path) => path.replace('/gateway/official/u-auth', '/gateway/u-auth') // 重写路径
// },
'/api': {
// target: 'http://223.76.236.155:20011', // 目标服务器地址
target: 'http://222.20.126.217:10010', // 目标服务器地址
target: 'http://222.20.126.217:8100', // 目标服务器地址
changeOrigin: true, // 允许跨域
rewrite: (path) => path.replace(/^\/gateway/, '/gateway'), // 重写路径
rewrite: (path) => path.replace(/^\/api/, '/api'), // 重写路径
}
}
},
@@ -60,16 +66,23 @@ export default (config: any) => {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
optimizeDeps: {
include: ['vue', 'dayjs', 'axios'], // 强制预构建常用包
},
build: {
outDir: 'openRepoPortal', // 将打包后的文件夹名改为 openRepoPortal
outDir: 'SAP', // 将打包后的文件夹名改为 openRepoPortal
// sourcemap: config.mode !== 'production',
minify: 'terser', // 确保使用 terser 进行压缩
terserOptions: {
compress: {
drop_console: true, // 移除 console.log
},
},
// 减少 chunk 大小计算,加快打包速度
reportCompressedSize: false,
// 提高超大静态资源临界值
assetsInlineLimit: 4096,
minify: 'esbuild',
// minify: 'terser', // 确保使用 terser 进行压缩
// terserOptions: {
// compress: {
// drop_console: true, // 移除 console.log
// },
// },
},
define: {
@@ -83,7 +96,7 @@ export default (config: any) => {
) {
if (type === 'asset') {
// return env.VITE_STATIC_HOST + '/' + filename;
return `/openRepoPortal/${filename}`;
return `/SAP/${filename}`;
}
}
}

222
yarn.lock
View File

@@ -2,6 +2,7 @@
# yarn lockfile v1
\
"@aashutoshrathi/word-wrap@^1.2.3":
version "1.2.6"
resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz"
@@ -20,6 +21,26 @@
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@ant-design/colors@^6.0.0":
version "6.0.0"
resolved "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz"
integrity sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==
dependencies:
"@ctrl/tinycolor" "^3.4.0"
"@ant-design/icons-svg@^4.2.1":
version "4.4.2"
resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz"
integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
"@ant-design/icons-vue@^7.0.0":
version "7.0.1"
resolved "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz"
integrity sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons-svg" "^4.2.1"
"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2":
version "7.24.2"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz"
@@ -913,7 +934,7 @@
resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.8.4":
"@babel/runtime@^7.10.5", "@babel/runtime@^7.8.4":
version "7.24.1"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz"
integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==
@@ -958,11 +979,26 @@
resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz"
integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
version "3.6.1"
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
"@devui-design/icons@^1.3.0", "@devui-design/icons@^1.4.0":
version "1.4.0"
resolved "https://mirrors.cloud.tencent.com/npm/@devui-design/icons/-/icons-1.4.0.tgz"
integrity sha512-taAX1RNW0QHUqQTRPqLTYTB2PZIqUplhWeF4hcmWkSTjpWlDNI40DssG/WRb3sISkfBk/4BMUxxC5XeTL3jo7A==
"@emotion/hash@^0.9.0":
version "0.9.2"
resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
"@emotion/unitless@^0.8.0":
version "0.8.1"
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz"
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
"@esbuild/win32-x64@0.18.20":
version "0.18.20"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz"
@@ -1235,6 +1271,14 @@
"@sentry/types" "7.108.0"
"@sentry/utils" "7.108.0"
"@simonwep/pickr@~1.8.0":
version "1.8.2"
resolved "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz"
integrity sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==
dependencies:
core-js "^3.15.1"
nanopop "^2.1.0"
"@tsconfig/node18@^2.0.1":
version "2.0.1"
resolved "https://registry.npmjs.org/@tsconfig/node18/-/node18-2.0.1.tgz"
@@ -1706,6 +1750,34 @@ ansi-styles@^6.1.0:
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
ant-design-vue@4.x:
version "4.2.6"
resolved "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz"
integrity sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons-vue" "^7.0.0"
"@babel/runtime" "^7.10.5"
"@ctrl/tinycolor" "^3.5.0"
"@emotion/hash" "^0.9.0"
"@emotion/unitless" "^0.8.0"
"@simonwep/pickr" "~1.8.0"
array-tree-filter "^2.1.0"
async-validator "^4.0.0"
csstype "^3.1.1"
dayjs "^1.10.5"
dom-align "^1.12.1"
dom-scroll-into-view "^2.0.0"
lodash "^4.17.21"
lodash-es "^4.17.15"
resize-observer-polyfill "^1.5.1"
scroll-into-view-if-needed "^2.2.25"
shallow-equal "^1.0.0"
stylis "^4.1.3"
throttle-debounce "^5.0.0"
vue-types "^3.0.0"
warning "^4.0.0"
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz"
@@ -1737,6 +1809,11 @@ array-buffer-byte-length@^1.0.1:
call-bind "^1.0.5"
is-array-buffer "^3.0.4"
array-tree-filter@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz"
integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
@@ -1756,7 +1833,14 @@ arraybuffer.prototype.slice@^1.0.3:
is-array-buffer "^3.0.4"
is-shared-array-buffer "^1.0.2"
async-validator@^4.0.7:
asn1@^0.2.6:
version "0.2.6"
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
dependencies:
safer-buffer "~2.1.0"
async-validator@^4.0.0, async-validator@^4.0.7:
version "4.2.5"
resolved "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz"
integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==
@@ -1823,6 +1907,13 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz"
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
dependencies:
tweetnacl "^0.14.3"
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
@@ -2040,6 +2131,11 @@ commander@7:
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
compute-scroll-into-view@^1.0.20:
version "1.0.20"
resolved "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz"
integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==
computeds@^0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz"
@@ -2062,7 +2158,7 @@ core-js-compat@^3.31.0, core-js-compat@^3.36.1:
dependencies:
browserslist "^4.23.0"
core-js@^3.31.1:
core-js@^3.15.1, core-js@^3.31.1:
version "3.36.1"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz"
integrity sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==
@@ -2107,7 +2203,7 @@ cssfilter@0.0.10:
resolved "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz"
integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==
csstype@^3.1.3:
csstype@^3.1.1, csstype@^3.1.3:
version "3.1.3"
resolved "https://mirrors.cloud.tencent.com/npm/csstype/-/csstype-3.1.3.tgz"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
@@ -2649,7 +2745,7 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.13, dayjs@^1.11.3:
dayjs@^1.10.5, dayjs@^1.11.13, dayjs@^1.11.3:
version "1.11.13"
resolved "https://mirrors.cloud.tencent.com/npm/dayjs/-/dayjs-1.11.13.tgz"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
@@ -2760,6 +2856,16 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
dom-align@^1.12.1:
version "1.12.4"
resolved "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz"
integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==
dom-scroll-into-view@^2.0.0:
version "2.0.1"
resolved "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz"
integrity sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==
dompurify@^3.0.5:
version "3.0.11"
resolved "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz"
@@ -2775,6 +2881,14 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
echarts@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz"
integrity sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==
dependencies:
tslib "2.3.0"
zrender "6.0.0"
echarts@5.3.3:
version "5.3.3"
resolved "https://registry.npmjs.org/echarts/-/echarts-5.3.3.tgz"
@@ -3629,6 +3743,11 @@ is-path-inside@^3.0.3:
resolved "https://mirrors.cloud.tencent.com/npm/is-path-inside/-/is-path-inside-3.0.3.tgz"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
is-plain-object@3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz"
integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz"
@@ -3708,7 +3827,7 @@ js-cookie@^3.0.5:
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
js-tokens@^4.0.0:
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@@ -3720,6 +3839,11 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsencrypt@^3.5.4:
version "3.5.4"
resolved "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.5.4.tgz"
integrity sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
@@ -3856,7 +3980,7 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash-es@^4.17.20, lodash-es@^4.17.21:
lodash-es@^4.17.15, lodash-es@^4.17.20, lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
@@ -3876,6 +4000,13 @@ lodash@^4.17.15, lodash@^4.17.21:
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lottie-web@^5.12.2:
version "5.12.2"
resolved "https://registry.npmjs.org/lottie-web/-/lottie-web-5.12.2.tgz"
@@ -4077,6 +4208,11 @@ nanoid@^3.3.8:
resolved "https://mirrors.cloud.tencent.com/npm/nanoid/-/nanoid-3.3.11.tgz"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
nanopop@^2.1.0:
version "2.4.2"
resolved "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz"
integrity sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==
natural-compare-lite@^1.4.0:
version "1.4.0"
resolved "https://mirrors.cloud.tencent.com/npm/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz"
@@ -4519,6 +4655,11 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://mirrors.cloud.tencent.com/npm/resolve-from/-/resolve-from-4.0.0.tgz"
@@ -4605,7 +4746,7 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -4619,6 +4760,13 @@ sass@*, sass@^1.63.6:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
scroll-into-view-if-needed@^2.2.25:
version "2.2.31"
resolved "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz"
integrity sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==
dependencies:
compute-scroll-into-view "^1.0.20"
select@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/select/-/select-1.1.2.tgz"
@@ -4687,6 +4835,11 @@ set-function-name@^2.0.1:
functions-have-names "^1.2.3"
has-property-descriptors "^1.0.2"
shallow-equal@^1.0.0:
version "1.2.1"
resolved "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz"
integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz"
@@ -4797,6 +4950,17 @@ ssf@~0.11.2:
dependencies:
frac "~1.1.2"
ssh2@^1.17.0:
version "1.17.0"
resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz"
integrity sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.10"
nan "^2.23.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@@ -4893,7 +5057,7 @@ strip-json-comments@^3.1.1:
resolved "https://mirrors.cloud.tencent.com/npm/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
stylis@^4.0.10:
stylis@^4.0.10, stylis@^4.1.3:
version "4.3.1"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz"
integrity sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==
@@ -4932,7 +5096,7 @@ supports-preserve-symlinks-flag@^1.0.0:
swiper@^10.3.1:
version "10.3.1"
resolved "https://mirrors.cloud.tencent.com/npm/swiper/-/swiper-10.3.1.tgz"
resolved "https://registry.npmmirror.com/swiper/-/swiper-10.3.1.tgz"
integrity sha512-24Wk3YUdZHxjc9faID97GTu6xnLNia+adMt6qMTZG/HgdSUt4fS0REsGUXJOgpTED0Amh/j+gRGQxsLayJUlBQ==
systemjs@^6.14.1:
@@ -4997,6 +5161,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
throttle-debounce@^5.0.0:
version "5.0.2"
resolved "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz"
integrity sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz"
@@ -5036,6 +5205,11 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
tweetnacl@^0.14.3:
version "0.14.5"
resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz"
integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
@@ -5259,6 +5433,11 @@ vue-eslint-parser@^9.1.1, vue-eslint-parser@^9.4.2:
lodash "^4.17.21"
semver "^7.3.6"
vue-fast-marquee@^1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/vue-fast-marquee/-/vue-fast-marquee-1.0.6.tgz"
integrity sha512-VMqaeuBSS3L55Q84EuLJPHZ4Zv4UVBDXmh+2hb1IBTLKt/FmurJnWVXv8BNnbQSupA4kHbPlWDLwp+kvEMOF7A==
vue-router@^4.0.3, vue-router@^4.2.2:
version "4.3.0"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz"
@@ -5283,7 +5462,14 @@ vue-tsc@^1.6.5:
"@vue/language-core" "1.8.27"
semver "^7.5.4"
"vue@^2.6.0 || ^3.2.0", "vue@^2.6.14 || ^3.3.0", "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.3, vue@^3.3.4, vue@^3.5.13, "vue@2.x || 3.x", vue@3.5.13:
vue-types@^3.0.0:
version "3.0.2"
resolved "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz"
integrity sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==
dependencies:
is-plain-object "3.0.1"
"vue@^2.6.0 || ^3.2.0", "vue@^2.6.14 || ^3.3.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.3, vue@^3.3.4, vue@^3.5.13, vue@>=3.0.3, vue@>=3.2.0, "vue@2.x || 3.x", vue@3.5.13:
version "3.5.13"
resolved "https://mirrors.cloud.tencent.com/npm/vue/-/vue-3.5.13.tgz"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
@@ -5294,6 +5480,13 @@ vue-tsc@^1.6.5:
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
warning@^4.0.0:
version "4.0.3"
resolved "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
dependencies:
loose-envify "^1.0.0"
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"
@@ -5447,3 +5640,10 @@ zrender@5.3.2:
integrity sha512-8IiYdfwHj2rx0UeIGZGGU4WEVSDEdeVCaIg/fomejg1Xu6OifAL1GVzIPHg2D+MyUkbNgPWji90t0a8IDk+39w==
dependencies:
tslib "2.3.0"
zrender@6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz"
integrity sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==
dependencies:
tslib "2.3.0"