首页
This commit is contained in:
1
assets/100000_full.json
Normal file
1
assets/100000_full.json
Normal file
File diff suppressed because one or more lines are too long
@@ -334,11 +334,26 @@ img {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.section-title > span, .section-title-s2 > span, .section-title-s4 > span {
|
||||
font-size: 14px;
|
||||
font-size:1.275rem;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.section-title > span, .section-title-s2 > span, .section-title-s3 > span {
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title > span, .section-title-s2 > span, .section-title-s4 > span {
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title h2, .section-title-s2 h2, .section-title-s3 h2 {
|
||||
@@ -347,11 +362,22 @@ img {
|
||||
margin: 0.2em 0 0;
|
||||
}
|
||||
|
||||
.section-title h2, .section-title-s2 h2, .section-title-s4 h2 {
|
||||
font-size: 36px;
|
||||
font-size: 2.25rem;
|
||||
margin: 0.2em 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.section-title h2, .section-title-s2 h2, .section-title-s3 h2 {
|
||||
font-size: 30px;
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.section-title h2, .section-title-s2 h2, .section-title-s4 h2 {
|
||||
font-size: 30px;
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@@ -375,6 +401,10 @@ img {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-title-s4 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theme-btn, .theme-btn-s2, .theme-btn-s3 {
|
||||
background-color: #1481ff;
|
||||
color: #fff;
|
||||
@@ -1441,6 +1471,7 @@ img {
|
||||
/* navigation open and close btn hide for width screen */
|
||||
/* style for navigation less than 992px */
|
||||
/*navbar collaps less then 992px*/
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.site-header .navigation {
|
||||
@@ -1500,6 +1531,7 @@ img {
|
||||
z-index: 10;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none !important;
|
||||
-webkit-transition: all 0.3s;
|
||||
-moz-transition: all 0.3s;
|
||||
-o-transition: all 0.3s;
|
||||
@@ -1508,6 +1540,14 @@ img {
|
||||
-webkit-box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.site-header #navbar > ul .sub-menu::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
}
|
||||
.site-header #navbar > ul > li .sub-menu li {
|
||||
border-bottom: 1px solid #efefef;
|
||||
}
|
||||
@@ -1526,7 +1566,7 @@ img {
|
||||
.site-header #navbar > ul > li > .sub-menu > .menu-item-has-children > a {
|
||||
position: relative;
|
||||
}
|
||||
.site-header #navbar > ul > li > .sub-menu > .menu-item-has-children > a:before {
|
||||
/* .site-header #navbar > ul > li > .sub-menu > .menu-item-has-children > a:before {
|
||||
font-family: "themify";
|
||||
content: "\e649";
|
||||
font-size: 11px;
|
||||
@@ -1539,16 +1579,95 @@ img {
|
||||
-o-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
} */
|
||||
.site-header #navbar > ul > li:hover > .sub-menu {
|
||||
top: 100%;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
.site-header #navbar .sub-menu > li:hover > .sub-menu {
|
||||
left: 100%;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
/* 产品中心下拉菜单 */
|
||||
.site-header #navbar > ul > li.product-menu > a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #1481ff;
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.site-header #navbar > ul > li.product-menu:hover > a::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
.site-header #navbar > ul > li.product-menu {
|
||||
position: static;
|
||||
}
|
||||
.site-header #navbar > ul > li.product-menu > .sub-menu {
|
||||
width: 100vw;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: calc(-50vw + 50%);
|
||||
padding: 20px 5%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.site-header #navbar > ul > li.product-menu:hover > .sub-menu {
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
top: 100%;
|
||||
pointer-events: auto;
|
||||
}
|
||||
/* 二级分类 */
|
||||
.site-header #navbar .product-menu > .sub-menu > li {
|
||||
width: 280px;
|
||||
border-bottom: none;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.site-header #navbar .product-menu > .sub-menu > li > a {
|
||||
padding: 10px 0;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
color: #102541 !important;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
/* 三级菜单 - 直接显示在二级下方 */
|
||||
.site-header #navbar .product-menu > .sub-menu .sub-menu {
|
||||
position: static;
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
width: auto;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.site-header #navbar .product-menu > .sub-menu .sub-menu li {
|
||||
border-bottom: none;
|
||||
}
|
||||
.site-header #navbar .product-menu > .sub-menu .sub-menu li::before {
|
||||
content: "•";
|
||||
margin-right: 8px;
|
||||
}
|
||||
.site-header #navbar .product-menu > .sub-menu .sub-menu a {
|
||||
display: inline-block;
|
||||
padding: 8px 0;
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
color: #666 !important;
|
||||
}
|
||||
.site-header #navbar .product-menu > .sub-menu .sub-menu a:hover {
|
||||
color: #1481ff !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1590,6 +1709,13 @@ img {
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
/* 移动端隐藏产品中心的子菜单和箭头 */
|
||||
.site-header #navbar .product-menu > .sub-menu {
|
||||
display: none !important;
|
||||
}
|
||||
.site-header #navbar .product-menu > a:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
@@ -2749,6 +2875,7 @@ img {
|
||||
top: -200px;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
-webkit-transition: all 0.7s;
|
||||
-moz-transition: all 0.7s;
|
||||
-o-transition: all 0.7s;
|
||||
@@ -2756,9 +2883,18 @@ img {
|
||||
transition: all 0.7s;
|
||||
}
|
||||
|
||||
.sticky-header .sub-menu {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.sticky-on {
|
||||
opacity: 1;
|
||||
top: 0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.sticky-on .sub-menu {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.header-style-1 .sticky-header, .header-style-2 .sticky-header, .header-style-3 .sticky-header {
|
||||
@@ -2803,7 +2939,7 @@ img {
|
||||
}
|
||||
|
||||
.features-section .feature-grids .grid {
|
||||
width: calc(33.33% - 30px);
|
||||
width: calc(25% - 30px);
|
||||
margin: 0 15px 30px;
|
||||
float: left;
|
||||
}
|
||||
@@ -2843,10 +2979,11 @@ img {
|
||||
|
||||
.features-section .details {
|
||||
background-color: #fff;
|
||||
padding: 35px 40px;
|
||||
padding: 30px 30px;
|
||||
-webkit-box-shadow: 0px 0px 36.8px 9.2px rgba(16, 37, 65, 0.1);
|
||||
box-shadow: 0px 0px 36.8px 9.2px rgba(16, 37, 65, 0.1);
|
||||
border-radius: 0 0 10px 10px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@@ -3869,12 +4006,12 @@ img {
|
||||
|
||||
.portfolio-section .portfolio-grids .grid h3 a {
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.portfolio-section .portfolio-grids .grid p {
|
||||
font-size: 14px;
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #1481ff;
|
||||
margin: 0;
|
||||
|
||||
BIN
assets/images/index/bk.jpg
Normal file
BIN
assets/images/index/bk.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
BIN
assets/images/index/top.png
Normal file
BIN
assets/images/index/top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 408 KiB |
@@ -428,10 +428,6 @@
|
||||
|
||||
1200 : {
|
||||
items: 4
|
||||
},
|
||||
|
||||
1400 : {
|
||||
items: 5
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
11
header.html
11
header.html
@@ -67,8 +67,8 @@
|
||||
<!--<li><a href="index-3.html">Home Style 3</a></li>-->
|
||||
<!--</ul>-->
|
||||
</li>
|
||||
<li class="menu-item-has-children">
|
||||
<a href="#">产品中心</a>
|
||||
<li class="menu-item-has-children product-menu">
|
||||
<a href="product_Logistics.html" target="_parent">产品中心</a>
|
||||
<ul class="sub-menu">
|
||||
<li class="menu-item-has-children">
|
||||
<a href="product_Logistics.html" target="_parent">物流供应链系列产品</a>
|
||||
@@ -91,6 +91,13 @@
|
||||
<li><a href="changzhan.html" target="_parent">集装箱场站管理系统</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu-item-has-children">
|
||||
<a href="product_port.html" target="_parent">航运数字化产品</a>
|
||||
<ul class="sub-menu">
|
||||
<li><a href="yunmatou.html" target="_parent">云码头智能管理系统</a></li>
|
||||
<li><a href="changzhan.html" target="_parent">集装箱场站管理系统</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="">
|
||||
|
||||
592
index.html
592
index.html
@@ -31,10 +31,19 @@
|
||||
<script src="assets/js/html5shiv.min.js"></script>
|
||||
<script src="assets/js/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.4/d3.min.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three@latest/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three@latest/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- start page-wrapper -->
|
||||
<div class="page-wrapper">
|
||||
@@ -70,10 +79,9 @@
|
||||
<p>岸基科技为航运物流供应链领域企事业单位,提供卓越的软件产品和技术服务。 </p>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<!--<div data-swiper-parallax="500" class="slide-btns">-->
|
||||
<!--<a href="product_port.html" class="theme-btn-s2">了解更多</a>-->
|
||||
<!--<!–<a href="https://www.youtube.com/embed/7e90gBu4pas?autoplay=1" class="hero-video-btn video-btn" data-type="iframe" tabindex="0"><i class="fi flaticon-play-button"></i>Watch About</a> –>-->
|
||||
<!--</div>-->
|
||||
<div data-swiper-parallax="500" class="slide-btns">
|
||||
<a href="product_port.html" class="theme-btn-s2">了解更多</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- end slide-inner -->
|
||||
</div> <!-- end swiper-slide -->
|
||||
@@ -88,10 +96,10 @@
|
||||
<p>为中小码头提供一体化服务的智能管理平台,租赁购买模式降低企业成本,助力企业数字化转型。</p>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<!--<div data-swiper-parallax="500" class="slide-btns">-->
|
||||
<!--<a href="http://imatou.com" target="view_window" class="theme-btn-s2">了解更多</a>-->
|
||||
<div data-swiper-parallax="500" class="slide-btns">
|
||||
<a href="http://imatou.com" target="view_window" class="theme-btn-s2">了解更多</a>
|
||||
<!--<!–<a href="https://www.youtube.com/embed/7e90gBu4pas?autoplay=1" class="hero-video-btn video-btn" data-type="iframe" tabindex="0"><i class="fi flaticon-play-button"></i>Watch About</a> –>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- end slide-inner -->
|
||||
</div> <!-- end swiper-slide -->
|
||||
@@ -145,12 +153,111 @@
|
||||
<a href="product_port.html" class="read-more" target="_parent">了解更多</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="header">
|
||||
<i class="fi flaticon-matou"></i>
|
||||
</div>
|
||||
<div class="details">
|
||||
<h3>云码头系列产品</h3>
|
||||
<p> 目前业内技术成熟、业务架构领先、面向集装箱、散货、件杂货、液化码头的业务完整解决方案的产品套件。 </p>
|
||||
<a href="product_port.html" class="read-more" target="_parent">了解更多</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- end container -->
|
||||
</section>
|
||||
<!-- end features-section -->
|
||||
|
||||
<!-- start portfolio-section -->
|
||||
<section class="section-padding">
|
||||
|
||||
<div class="portfolio-container" style="display: flex; align-items: center; justify-content: center; min-height: 400px; position: relative;">
|
||||
<img src="assets/images/index/bk.jpg" alt="" style="width: 100%; max-width: none;">
|
||||
<div style="position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-lg-6 col-lg-offset-3">
|
||||
<div class="section-title-s4">
|
||||
<h2>岸基物流供应链核心能力全景</h2>
|
||||
<span>基于"船、港、仓、运"一体化协同的全栈产品矩阵,构建高效、坚韧的现代供应链</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- end container -->
|
||||
<img src="assets/images/index/top.png" alt="" style="width: 55%; max-width: none; margin-top: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- end portfolio-section -->
|
||||
|
||||
<!-- start portfolio-section -->
|
||||
<section class="portfolio-section section-padding">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-lg-6 col-lg-offset-3">
|
||||
<div class="section-title-s4">
|
||||
<h2>模块化港航产品生态</h2>
|
||||
<span>满足港口、航运、物流等各类差异化需求</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- end container -->
|
||||
<div class="portfolio-container">
|
||||
<div class="portfolio-grids portfolio-slider">
|
||||
<div class="grid">
|
||||
<div class="img-holder">
|
||||
<img src="assets/images/portfolio/img-1.jpg" alt>
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class="inner">
|
||||
<h3><a href="#">大宗货物供应链解决方案</a></h3>
|
||||
<p class="cat">贸易/物流/港口</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="img-holder">
|
||||
<img src="assets/images/portfolio/img-2.jpg" alt>
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class="inner">
|
||||
<h3><a href="#">内河港航一体化解决方案</a></h3>
|
||||
<p class="cat">内河港航集团</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="img-holder">
|
||||
<img src="assets/images/portfolio/img-3.jpg" alt>
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class="inner">
|
||||
<h3><a href="#">港口供应链平台解决方案</a></h3>
|
||||
<p class="cat">大型港航集团</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="img-holder">
|
||||
<img src="assets/images/portfolio/img-4.jpg" alt>
|
||||
</div>
|
||||
<div class="details">
|
||||
<div class="inner">
|
||||
<h3><a href="#">全程物流链解决方案</a></h3>
|
||||
<p class="cat">大型物流集团</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="all-portfolio">
|
||||
<a href="#" class="theme-btn-s2">View all cases</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- end portfolio-section -->
|
||||
|
||||
<section class="services-section section-padding">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@@ -253,15 +360,10 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col col-md-7">
|
||||
<div class="img-holder">
|
||||
<img src="assets/images/ditu.png" alt style="max-width: 115%;">
|
||||
<!--<div class="video-holder">-->
|
||||
<!--<a href="https://www.youtube.com/embed/7e90gBu4pas?autoplay=1" class="video-btn" data-type="iframe" tabindex="0"><i class="fi flaticon-play-button-3"></i></a>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
<div id="map" style="height: 600px;"></div>
|
||||
</div>
|
||||
<div class="col col-md-5">
|
||||
<div class="details">
|
||||
<div class="details" style="margin-top: 100px;">
|
||||
<div class="section-title">
|
||||
<span>我们的行业价值</span>
|
||||
<h2>客户辐射面广,业务占比高</h2>
|
||||
@@ -860,5 +962,467 @@
|
||||
|
||||
<!-- Custom script for this template -->
|
||||
<script src="assets/js/script.js"></script>
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
||||
import { Line2 } from 'three/addons/lines/Line2.js';
|
||||
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
||||
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// const axesHelper = new THREE.AxesHelper(5);
|
||||
// scene.add(axesHelper);
|
||||
const ambientLight = new THREE.AmbientLight(0xd4e7fd, 4);
|
||||
scene.add(ambientLight);
|
||||
const directionalLight = new THREE.DirectionalLight(0xe8eaeb, 0.2);
|
||||
directionalLight.position.set(0, 10, 5);
|
||||
const directionalLight2 = directionalLight.clone();
|
||||
directionalLight2.position.set(0, 10, -5);
|
||||
const directionalLight3 = directionalLight.clone();
|
||||
directionalLight3.position.set(5, 10, 0);
|
||||
const directionalLight4 = directionalLight.clone();
|
||||
directionalLight4.position.set(-5, 10, 0);
|
||||
scene.add(directionalLight);
|
||||
scene.add(directionalLight2);
|
||||
scene.add(directionalLight3);
|
||||
scene.add(directionalLight4);
|
||||
|
||||
const container = document.getElementById("map");
|
||||
container.style.position = "relative";
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
container.clientWidth / container.clientHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
|
||||
// 设置相机位置: position.set(x, y, z)
|
||||
// x: 左右移动 (负值向左, 正值向右)
|
||||
// y: 上下移动 (值越大视角越高, 当前100为俯视角度)
|
||||
// z: 前后移动 (负值向前, 正值向后, 0.1接近正上方俯视)
|
||||
camera.position.set(0, 100, 10);
|
||||
camera.lookAt(0, 0, 0);
|
||||
camera.up.set(0, 1, 0);
|
||||
|
||||
|
||||
const labelRenderer = new CSS2DRenderer();
|
||||
labelRenderer.domElement.style.position = "absolute";
|
||||
labelRenderer.domElement.style.top = "0px";
|
||||
labelRenderer.domElement.style.pointerEvents = "none";
|
||||
labelRenderer.setSize(container.clientWidth, container.clientHeight);
|
||||
container.appendChild(labelRenderer.domElement);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); // 开启抗锯齿
|
||||
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
container.appendChild(renderer.domElement);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableRotate = false;
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
controls.update();
|
||||
|
||||
// objects tracked globally for interactions/animation
|
||||
const highlightMeshes = [];
|
||||
const pulseRings = [];
|
||||
|
||||
// tooltip for highlighted provinces
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.style.position = "absolute";
|
||||
tooltip.style.padding = "6px 10px";
|
||||
tooltip.style.background = "rgba(24, 118, 242, 0.92)";
|
||||
tooltip.style.color = "#fff";
|
||||
tooltip.style.fontSize = "13px";
|
||||
tooltip.style.borderRadius = "6px";
|
||||
tooltip.style.pointerEvents = "none";
|
||||
tooltip.style.boxShadow = "0 2px 6px rgba(0,0,0,0.25)";
|
||||
tooltip.style.display = "none";
|
||||
container.appendChild(tooltip);
|
||||
const animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
const t = performance.now() * 0.001;
|
||||
pulseRings.forEach((mesh) => {
|
||||
const { minScale, maxScale, period, phase, baseOpacity } = mesh.userData;
|
||||
|
||||
// 每个点以 3s 周期从中心向外扩散一次,使用平滑缓入缓出避免突兀
|
||||
const progress = (t / period + phase) % 1; // 0..1
|
||||
const eased = progress * progress * (3 - 2 * progress); // smoothstep(0..1)
|
||||
|
||||
const s = minScale + eased * (maxScale - minScale);
|
||||
mesh.scale.setScalar(s);
|
||||
|
||||
// 首尾使用平滑窗,外扩过程中逐渐淡出,避免突变
|
||||
// 尾部提早淡出,确保从最大到重新起始时更自然、不显跳变
|
||||
const fadeIn = progress < 0.12 ? progress / 0.12 : 1.0;
|
||||
const fadeOut = progress > 0.7 ? Math.max(0.0, 1.0 - (progress - 0.7) / 0.3) : 1.0;
|
||||
const alpha = baseOpacity * Math.max(0.0, Math.min(1.0, fadeIn * fadeOut));
|
||||
if (mesh.material && mesh.material.uniforms && mesh.material.uniforms.opacity) {
|
||||
mesh.material.uniforms.opacity.value = alpha;
|
||||
}
|
||||
});
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
labelRenderer.render(scene, camera);
|
||||
};
|
||||
animate();
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const mouse = new THREE.Vector2();
|
||||
let currentMap = null;
|
||||
let hoverMesh = null;
|
||||
const hoverColor = new THREE.Color().setHSL(210 / 360, 0.7, 0.55);
|
||||
|
||||
const showTooltip = (event) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
tooltip.style.left = `${event.clientX - rect.left + 12}px`;
|
||||
tooltip.style.top = `${event.clientY - rect.top + 12}px`;
|
||||
tooltip.textContent = "客户数:2";
|
||||
tooltip.style.display = "block";
|
||||
};
|
||||
|
||||
const hideTooltip = () => {
|
||||
tooltip.style.display = "none";
|
||||
};
|
||||
|
||||
const applyHoverState = (mesh) => {
|
||||
if (hoverMesh && hoverMesh !== mesh) {
|
||||
// reset previous hover color
|
||||
if (hoverMesh.material && hoverMesh.userData.baseColor) {
|
||||
hoverMesh.material.color.copy(hoverMesh.userData.baseColor);
|
||||
}
|
||||
}
|
||||
hoverMesh = mesh;
|
||||
if (hoverMesh && hoverMesh.material) {
|
||||
hoverMesh.material.color.copy(hoverColor);
|
||||
}
|
||||
};
|
||||
|
||||
const clearHoverState = () => {
|
||||
if (hoverMesh && hoverMesh.material && hoverMesh.userData.baseColor) {
|
||||
hoverMesh.material.color.copy(hoverMesh.userData.baseColor);
|
||||
}
|
||||
hoverMesh = null;
|
||||
};
|
||||
|
||||
const handlePointerMove = (event) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster.intersectObjects(highlightMeshes, false);
|
||||
if (intersects.length > 0) {
|
||||
applyHoverState(intersects[0].object);
|
||||
showTooltip(event);
|
||||
} else {
|
||||
clearHoverState();
|
||||
hideTooltip();
|
||||
}
|
||||
};
|
||||
|
||||
container.addEventListener("mousemove", handlePointerMove);
|
||||
container.addEventListener("mouseleave", () => {
|
||||
clearHoverState();
|
||||
hideTooltip();
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
camera.aspect = container.clientWidth / container.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
labelRenderer.setSize(container.clientWidth, container.clientHeight);
|
||||
// update LineMaterial resolution on resize to keep linewidth consistent
|
||||
if (lineMaterials && lineMaterials.length) {
|
||||
lineMaterials.forEach((m) => {
|
||||
if (m && m.resolution) {
|
||||
m.resolution.set(container.clientWidth, container.clientHeight);
|
||||
m.needsUpdate = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// collect LineMaterial instances so we can update resolution on resize
|
||||
const lineMaterials = [];
|
||||
|
||||
|
||||
const url = "/assets/100000_full.json";
|
||||
fetch(url)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const map = createMap(data, 0.05);
|
||||
currentMap = map;
|
||||
map.add(createPulseRings(data, 0.05));
|
||||
scene.add(map);
|
||||
});
|
||||
|
||||
|
||||
const offsetXY = d3.geoMercator();
|
||||
|
||||
|
||||
// 需要高亮的省份名称数组
|
||||
const highlightProvinces = ["山东省", "海南省", "天津市", "广东省", "浙江省", "江苏省", "北京市", "河北省", "辽宁省", "上海市"];
|
||||
|
||||
// 需要添加辐射圈的省份
|
||||
const pulseProvinces = ["北京市", "上海市", "广东省"];
|
||||
|
||||
const createPulseRings = (data, depth) => {
|
||||
const group = new THREE.Object3D();
|
||||
data.features.forEach((feature) => {
|
||||
const { centroid, center, name } = feature.properties;
|
||||
if (!pulseProvinces.includes(name)) return;
|
||||
const point = centroid || center || [0, 0];
|
||||
const [x, y] = offsetXY(point);
|
||||
// 覆盖到圆心(内圈一直到中心),外径保持略大的辐射圈
|
||||
const geometry = new THREE.RingGeometry(0.0, 18.0, 160);
|
||||
|
||||
// colorCenterHex: 中心颜色, colorEdgeHex: 边缘颜色, opacityVal: 整体透明度, alphaInner: 内圈透明度, alphaOuter: 外圈透明度
|
||||
const makeMaterial = (colorCenterHex, colorEdgeHex, opacityVal, alphaInner = 0.45, alphaOuter = 1.0) => {
|
||||
const m = new THREE.ShaderMaterial({
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
depthTest: false, // keep rings always visible above map
|
||||
side: THREE.DoubleSide,
|
||||
uniforms: {
|
||||
colorCenter: { value: new THREE.Color(colorCenterHex) },
|
||||
colorEdge: { value: new THREE.Color(colorEdgeHex) },
|
||||
opacity: { value: opacityVal },
|
||||
innerRatio: { value: geometry.parameters.innerRadius / geometry.parameters.outerRadius },
|
||||
alphaInner: { value: alphaInner },
|
||||
alphaOuter: { value: alphaOuter },
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec2 vUv;
|
||||
uniform vec3 colorCenter;
|
||||
uniform vec3 colorEdge;
|
||||
uniform float opacity;
|
||||
uniform float innerRatio;
|
||||
uniform float alphaInner;
|
||||
uniform float alphaOuter;
|
||||
void main() {
|
||||
vec2 p = vUv - vec2(0.5);
|
||||
float rawR = clamp(length(p) * 2.0, 0.0, 1.0);
|
||||
float r = clamp((rawR - innerRatio) / max(1.0 - innerRatio, 0.0001), 0.0, 1.0);
|
||||
float k = smoothstep(0.0, 1.0, r);
|
||||
vec3 color = mix(colorEdge, colorCenter, 1.0 - k);
|
||||
float alpha = opacity * mix(alphaInner, alphaOuter, k);
|
||||
if (alpha < 0.01) discard;
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
}
|
||||
`,
|
||||
});
|
||||
return m;
|
||||
};
|
||||
|
||||
// 固定底环:中心更深,向外变浅,且更高不透明度
|
||||
const baseRing = new THREE.Mesh(
|
||||
geometry.clone(),
|
||||
// 颜色统一,alpha: 内圈高、外圈低
|
||||
makeMaterial(0x31bbf7, 0x31bbf7, 0.1, 1.0, 1.0)
|
||||
);
|
||||
baseRing.rotation.x = 0;
|
||||
baseRing.position.set(x, -y, depth + 0.02);
|
||||
baseRing.renderOrder = 10; // render above map geometry
|
||||
group.add(baseRing);
|
||||
|
||||
// 扩散环:从固定底环处开始向外扩散
|
||||
// 扩散环:内圈浅、外圈深,整体更淡
|
||||
const pulseRing = new THREE.Mesh(
|
||||
geometry.clone(),
|
||||
// alpha: 内圈低、外圈高
|
||||
makeMaterial(0x9fdcff, 0x31bbf7, 0.45, 0.35, 1.0)
|
||||
);
|
||||
pulseRing.rotation.x = 0;
|
||||
pulseRing.position.set(x, -y, depth + 0.02);
|
||||
pulseRing.renderOrder = 11; // render above base ring
|
||||
pulseRing.userData = {
|
||||
minScale: 1.0,
|
||||
maxScale: 1.3,
|
||||
period: 3.0,
|
||||
phase: Math.random(),
|
||||
baseOpacity: 0.25,
|
||||
};
|
||||
pulseRings.push(pulseRing);
|
||||
group.add(pulseRing);
|
||||
});
|
||||
return group;
|
||||
};
|
||||
|
||||
const createMap = (data, depth) => {
|
||||
const map = new THREE.Object3D();
|
||||
const center = data.features[0].properties.centroid;
|
||||
offsetXY.center(center).translate([0, 0]);
|
||||
data.features.forEach((feature) => {
|
||||
const unit = new THREE.Object3D();
|
||||
const { centroid, center, name } = feature.properties;
|
||||
const { coordinates, type } = feature.geometry;
|
||||
const point = centroid || center || [0, 0];
|
||||
|
||||
// 判断是否需要高亮
|
||||
let color;
|
||||
if (highlightProvinces.includes(name)) {
|
||||
color = 0x2658F7; // 高亮颜色
|
||||
} else {
|
||||
// 白色
|
||||
color = new THREE.Color().setHSL(233 / 360, 0, 1).getHex(); // 普通颜色
|
||||
|
||||
}
|
||||
//
|
||||
|
||||
// const color = new THREE.Color().setHSL(233/360, (Math.random() * 30 + 55)/100, (Math.random() * 30 + 55)/100).getHex();
|
||||
|
||||
|
||||
coordinates.forEach((coordinate) => {
|
||||
if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
|
||||
if (type === "Polygon") fn(coordinate);
|
||||
|
||||
function fn(coordinate) {
|
||||
unit.name = name;
|
||||
unit.centroid = point;
|
||||
const mesh = createMesh(coordinate, color, depth);
|
||||
mesh.userData = { name, centroid: point, baseColor: new THREE.Color(color) };
|
||||
if (highlightProvinces.includes(name)) {
|
||||
highlightMeshes.push(mesh);
|
||||
}
|
||||
const line = createLine(coordinate, depth);
|
||||
unit.add(mesh, ...line);
|
||||
}
|
||||
});
|
||||
map.add(unit);
|
||||
setCenter(map);
|
||||
});
|
||||
|
||||
|
||||
return map;
|
||||
};
|
||||
const createMesh = (data, color, depth) => {
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
data.forEach((item, idx) => {
|
||||
const [x, y] = offsetXY(item);
|
||||
|
||||
if (idx === 0) shape.moveTo(x, -y);
|
||||
else shape.lineTo(x, -y);
|
||||
});
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: depth,
|
||||
bevelEnabled: false,
|
||||
};
|
||||
|
||||
|
||||
const materialSettings = {
|
||||
color: color,
|
||||
emissive: 0x000000,
|
||||
roughness: 0.45,
|
||||
metalness: 0.8,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
|
||||
};
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
var material1 = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
// 添加一个uniform变量来控制颜色渐变
|
||||
color1: { value: new THREE.Color(0x2e95fb) }, // 开始颜色
|
||||
color2: { value: new THREE.Color(0x0131a8) } // 结束颜色
|
||||
},
|
||||
vertexShader: `
|
||||
varying float vZPosition;
|
||||
|
||||
void main() {
|
||||
// 传递Z轴位置给片段着色器
|
||||
vZPosition = position.z;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 color1;
|
||||
uniform vec3 color2;
|
||||
varying float vZPosition;
|
||||
|
||||
void main() {
|
||||
// 使用线性插值根据Z轴位置计算颜色
|
||||
vec3 color = mix(color1, color2, vZPosition);
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
const material = new THREE.MeshStandardMaterial(materialSettings);
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
const createLine = (data, depth) => {
|
||||
// 使用 Line2/LineMaterial/LineGeometry 来支持更粗的线并且抗锯齿
|
||||
const positions = [];
|
||||
let firstX = null;
|
||||
let firstY = null;
|
||||
data.forEach((item, idx) => {
|
||||
const [x, y] = offsetXY(item);
|
||||
positions.push(x, -y, 0);
|
||||
if (idx === 0) {
|
||||
firstX = x;
|
||||
firstY = y;
|
||||
}
|
||||
});
|
||||
|
||||
// 闭合多边形,确保线条连贯
|
||||
if (firstX !== null && firstY !== null) {
|
||||
positions.push(firstX, -firstY, 0);
|
||||
}
|
||||
|
||||
const lineGeometry = new LineGeometry();
|
||||
lineGeometry.setPositions(positions);
|
||||
|
||||
// linewidth 单位为像素;略微收细并改为黑色,保持可读性
|
||||
const lineMaterialSettings = {
|
||||
color: 0xffffff,
|
||||
linewidth: 0.5,
|
||||
dashed: false,
|
||||
transparent: true,
|
||||
opacity: 0.9,
|
||||
};
|
||||
const uplineMaterial = new LineMaterial(lineMaterialSettings);
|
||||
const downlineMaterial = new LineMaterial(lineMaterialSettings);
|
||||
|
||||
// 设置分辨率(像素),用于正确计算线宽
|
||||
uplineMaterial.resolution.set(container.clientWidth, container.clientHeight);
|
||||
downlineMaterial.resolution.set(container.clientWidth, container.clientHeight);
|
||||
|
||||
// 记录以便在 resize 时更新
|
||||
lineMaterials.push(uplineMaterial, downlineMaterial);
|
||||
|
||||
const upLine = new Line2(lineGeometry, uplineMaterial);
|
||||
const downLine = new Line2(lineGeometry.clone(), downlineMaterial);
|
||||
downLine.position.z = -0.0001;
|
||||
upLine.position.z = depth + 0.0001;
|
||||
return [upLine, downLine];
|
||||
};
|
||||
const setCenter = (map) => {
|
||||
map.rotation.x = -Math.PI / 2;
|
||||
const box = new THREE.Box3().setFromObject(map);
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
const offset = [0, 0];
|
||||
map.position.x = map.position.x - center.x - offset[0];
|
||||
map.position.z = map.position.z - center.z - offset[1];
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user