first commit
29
.commitlintrc.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ["@commitlint/config-conventional"],
|
||||||
|
rules: {
|
||||||
|
"type-enum": [
|
||||||
|
2,
|
||||||
|
"always",
|
||||||
|
[
|
||||||
|
"feat", // 功能
|
||||||
|
"fix", // bug
|
||||||
|
"test", // 测试
|
||||||
|
"perf", // 优化
|
||||||
|
"refactor", // 重构
|
||||||
|
"docs", // 文档
|
||||||
|
"chore", // 辅助工具配置
|
||||||
|
"style", // 格式 (适合lint fix...)
|
||||||
|
"revert", // 回滚
|
||||||
|
"merge", // 合并
|
||||||
|
"sync", // 同步(同步主线或分支上的fix修复等)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"type-case": [2, "always", "lower-case"],
|
||||||
|
"type-empty": [2, "never"],
|
||||||
|
"scope-empty": [0],
|
||||||
|
"scope-case": [0],
|
||||||
|
"subject-full-stop": [0, "never"],
|
||||||
|
"subject-case": [0, "never"],
|
||||||
|
"header-max-length": [0, "always", 72],
|
||||||
|
},
|
||||||
|
};
|
||||||
1
.env.local
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_BASE_API=http://192.168.1.194:1020
|
||||||
2
.env.production
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_BASE_API=http://dvapi.prod.zhongshuai2023.com
|
||||||
|
|
||||||
16
.eslintrc.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
"plugins": ['@typescript-eslint'],
|
||||||
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
|
rules: {
|
||||||
|
eqeqeq: 0, // 必须使用全等
|
||||||
|
'no-unused-vars': 1, // 不能有声明后未被使用的变量或参数
|
||||||
|
'no-throw-literal': 0, // 0可以/2不可以 抛出字面量错误 throw "error";
|
||||||
|
'no-sparse-arrays': 2, // 数组中不允许出现空位置
|
||||||
|
'no-empty': 0, // 禁止出现空语句块
|
||||||
|
'no-console': ['error', { allow: ['warn', 'error', 'info', "log"] }],
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-useless-escape': 0,
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"no-async-promise-executor": 0
|
||||||
|
},
|
||||||
|
}
|
||||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.js linguist-detectable=false
|
||||||
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
5
.husky/pre-commit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
|
|
||||||
8
.prettierrc.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
"printWidth": 80,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"spaceBeforeFunctionParen": true
|
||||||
|
}
|
||||||
BIN
3d-earth.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 GhostCat
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
6
README.en.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Webpack 5 + Typescript 4 + Three.js 基础模板
|
||||||
|
|
||||||
|
- Webpack 5
|
||||||
|
- Typescript 4
|
||||||
|
- Three.js 130
|
||||||
|
- lodash
|
||||||
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 3d-earth
|
||||||
|
本项目使用 [three-ts-webpack](https://github.com/GhostCatcg/three-ts-webpack) 构建
|
||||||
|
|
||||||
|
[Live Demo](https://gcat.cc/demo/earth)
|
||||||
|
|
||||||
|

|
||||||
|
## Todolist
|
||||||
|
1. - [x] 加载效果[loading...]
|
||||||
|
2. - [x] 地球、以及星空背景🌏
|
||||||
|
3. - [x] 辉光以及大气层✨
|
||||||
|
4. - [x] 地球标点以及城市标签🇨🇳
|
||||||
|
5. - [x] 卫星环绕旋转🛰
|
||||||
|
6. - [x] 国家/城市之前的飞线🪐
|
||||||
|
7. - [ ] 飞机沿飞线飞行🛫
|
||||||
144
d/zy/three/3d-earth/src/ts/Utils/common.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { CatmullRomCurve3, DoubleSide, Group, Mesh, MeshBasicMaterial, PlaneGeometry, Texture, TubeGeometry, Vector3 } from "three";
|
||||||
|
import { punctuation } from "../world/Earth";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经纬度坐标转球面坐标
|
||||||
|
* @param {地球半径} R
|
||||||
|
* @param {经度(角度值)} longitude
|
||||||
|
* @param {维度(角度值)} latitude
|
||||||
|
*/
|
||||||
|
export const lon2xyz = (R:number, longitude:number, latitude:number): Vector3 => {
|
||||||
|
let lon = longitude * Math.PI / 180; // 转弧度值
|
||||||
|
const lat = latitude * Math.PI / 180; // 转弧度值
|
||||||
|
lon = -lon; // js坐标系z坐标轴对应经度-90度,而不是90度
|
||||||
|
|
||||||
|
// 经纬度坐标转球面坐标计算公式
|
||||||
|
const x = R * Math.cos(lat) * Math.cos(lon);
|
||||||
|
const y = R * Math.sin(lat);
|
||||||
|
const z = R * Math.cos(lat) * Math.sin(lon);
|
||||||
|
// 返回球面坐标
|
||||||
|
return new Vector3(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建波动光圈
|
||||||
|
export const createWaveMesh = (options: { radius: number, lon: number, lat: number, textures: Record<string, Texture> }) => {
|
||||||
|
const geometry = new PlaneGeometry(1, 1); //默认在XOY平面上
|
||||||
|
const texture = options.textures.aperture;
|
||||||
|
|
||||||
|
const material = new MeshBasicMaterial({
|
||||||
|
color: 0xe99f68,
|
||||||
|
map: texture,
|
||||||
|
transparent: true, //使用背景透明的png贴图,注意开启透明计算
|
||||||
|
opacity: 1.0,
|
||||||
|
depthWrite: false, //禁止写入深度缓冲区数据
|
||||||
|
});
|
||||||
|
const mesh = new Mesh(geometry, material);
|
||||||
|
// 经纬度转球面坐标
|
||||||
|
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat);
|
||||||
|
const size = options.radius * 0.12; //矩形平面Mesh的尺寸
|
||||||
|
mesh.scale.set(size, size, size); //设置mesh大小
|
||||||
|
mesh.userData['size'] = size; //自顶一个属性,表示mesh静态大小
|
||||||
|
mesh.userData['scale'] = Math.random() * 1.0; //自定义属性._s表示mesh在原始大小基础上放大倍数 光圈在原来mesh.size基础上1~2倍之间变化
|
||||||
|
mesh.position.set(coord.x, coord.y, coord.z);
|
||||||
|
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize();
|
||||||
|
const meshNormal = new Vector3(0, 0, 1);
|
||||||
|
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建柱状
|
||||||
|
export const createLightPillar = (options: { radius: number, lon: number, lat: number, index: number, textures: Record<string, Texture>, punctuation: punctuation }) => {
|
||||||
|
const height = options.radius * 0.3;
|
||||||
|
const geometry = new PlaneGeometry(options.radius * 0.05, height);
|
||||||
|
geometry.rotateX(Math.PI / 2);
|
||||||
|
geometry.translate(0, 0, height / 2);
|
||||||
|
const material = new MeshBasicMaterial({
|
||||||
|
map: options.textures.light_column,
|
||||||
|
color:
|
||||||
|
options.index == 0
|
||||||
|
? options.punctuation.lightColumn.startColor
|
||||||
|
: options.punctuation.lightColumn.endColor,
|
||||||
|
transparent: true,
|
||||||
|
side: DoubleSide,
|
||||||
|
depthWrite: false, //是否对深度缓冲区有任何的影响
|
||||||
|
});
|
||||||
|
const mesh = new Mesh(geometry, material);
|
||||||
|
const group = new Group();
|
||||||
|
// 两个光柱交叉叠加
|
||||||
|
group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)); //几何体绕x轴旋转了,所以mesh旋转轴变为z
|
||||||
|
// 经纬度转球面坐标
|
||||||
|
const SphereCoord = lon2xyz(options.radius, options.lon, options.lat); //SphereCoord球面坐标
|
||||||
|
group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z); //设置mesh位置
|
||||||
|
const coordVec3 = new Vector3(
|
||||||
|
SphereCoord.x,
|
||||||
|
SphereCoord.y,
|
||||||
|
SphereCoord.z
|
||||||
|
).normalize();
|
||||||
|
const meshNormal = new Vector3(0, 0, 1);
|
||||||
|
group.quaternion.setFromUnitVectors(meshNormal, coordVec3);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 光柱底座矩形平面
|
||||||
|
export const createPointMesh = (options: {
|
||||||
|
radius: number, lon: number,
|
||||||
|
lat: number, material: MeshBasicMaterial
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const geometry = new PlaneGeometry(1, 1); //默认在XOY平面上
|
||||||
|
const mesh = new Mesh(geometry, options.material);
|
||||||
|
// 经纬度转球面坐标
|
||||||
|
const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat);
|
||||||
|
const size = options.radius * 0.05; // 矩形平面Mesh的尺寸
|
||||||
|
mesh.scale.set(size, size, size); // 设置mesh大小
|
||||||
|
|
||||||
|
// 设置mesh位置
|
||||||
|
mesh.position.set(coord.x, coord.y, coord.z);
|
||||||
|
const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize();
|
||||||
|
const meshNormal = new Vector3(0, 0, 1);
|
||||||
|
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);
|
||||||
|
return mesh;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取点
|
||||||
|
export const getCirclePoints = (option: { number?: number, radius?: number, closed?: boolean }) => {
|
||||||
|
const list = [];
|
||||||
|
for (
|
||||||
|
let j = 0;
|
||||||
|
j < 2 * Math.PI - 0.1;
|
||||||
|
j += (2 * Math.PI) / (option.number || 100)
|
||||||
|
) {
|
||||||
|
list.push([
|
||||||
|
parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)),
|
||||||
|
0,
|
||||||
|
parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (option.closed) list.push(list[0]);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建线
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态的线
|
||||||
|
*/
|
||||||
|
export const createAnimateLine = (option: { pointList: number[][], number?: number, radius?: number, radialSegments?: number, material: MeshBasicMaterial }) => {
|
||||||
|
// 由多个点数组构成的曲线 通常用于道路
|
||||||
|
const l: Vector3[] = [];
|
||||||
|
option.pointList.forEach((e) =>
|
||||||
|
l.push(new Vector3(e[0], e[1], e[2]))
|
||||||
|
);
|
||||||
|
const curve = new CatmullRomCurve3(l); // 曲线路径
|
||||||
|
|
||||||
|
// 管道体
|
||||||
|
const tubeGeometry = new TubeGeometry(
|
||||||
|
curve,
|
||||||
|
option.number || 50,
|
||||||
|
option.radius || 1,
|
||||||
|
option.radialSegments
|
||||||
|
);
|
||||||
|
return new Mesh(tubeGeometry, option.material);
|
||||||
|
}
|
||||||
50
d/zy/three/3d-earth/src/ts/world/Assets.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 资源文件
|
||||||
|
* 把模型和图片分开进行加载
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ITextures {
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResources {
|
||||||
|
textures?: ITextures[],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建基础纹理数据
|
||||||
|
const createBasicTexture = (name: string): string => {
|
||||||
|
// 创建一个基础的canvas纹理
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 256;
|
||||||
|
canvas.height = 256;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.font = '20px Arial';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(name, canvas.width / 2, canvas.height / 2);
|
||||||
|
}
|
||||||
|
return canvas.toDataURL();
|
||||||
|
};
|
||||||
|
|
||||||
|
const textures = [
|
||||||
|
{ name: 'gradient', url: createBasicTexture('gradient') },
|
||||||
|
{ name: 'redCircle', url: createBasicTexture('redCircle') },
|
||||||
|
{ name: 'label', url: createBasicTexture('label') },
|
||||||
|
{ name: 'aperture', url: createBasicTexture('aperture') },
|
||||||
|
{ name: 'glow', url: createBasicTexture('glow') },
|
||||||
|
{ name: 'light_column', url: createBasicTexture('light_column') },
|
||||||
|
{ name: 'aircraft', url: createBasicTexture('aircraft') },
|
||||||
|
{ name: 'earth', url: createBasicTexture('earth') }
|
||||||
|
];
|
||||||
|
|
||||||
|
const resources: IResources = {
|
||||||
|
textures
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
resources
|
||||||
|
}
|
||||||
91
d/zy/three/3d-earth/src/ts/world/Basic.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* 创建 threejs 四大天王
|
||||||
|
* 场景、相机、渲染器、控制器
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import {
|
||||||
|
OrbitControls
|
||||||
|
} from "three/examples/jsm/controls/OrbitControls";
|
||||||
|
|
||||||
|
class Basic {
|
||||||
|
public scene!: THREE.Scene;
|
||||||
|
public camera!: THREE.PerspectiveCamera;
|
||||||
|
public renderer!: THREE.WebGLRenderer
|
||||||
|
public controls!: OrbitControls;
|
||||||
|
public dom: HTMLElement;
|
||||||
|
|
||||||
|
constructor(dom: HTMLElement) {
|
||||||
|
this.dom = dom
|
||||||
|
this.initScenes()
|
||||||
|
this.setControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化场景
|
||||||
|
*/
|
||||||
|
initScenes() {
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
|
||||||
|
this.camera = new THREE.PerspectiveCamera(
|
||||||
|
45,
|
||||||
|
window.innerWidth / window.innerHeight,
|
||||||
|
1,
|
||||||
|
100000
|
||||||
|
);
|
||||||
|
this.camera.position.set(0, 30, -250)
|
||||||
|
|
||||||
|
|
||||||
|
this.renderer = new THREE.WebGLRenderer({
|
||||||
|
alpha: true, // 透明
|
||||||
|
antialias: true, // 抗锯齿
|
||||||
|
});
|
||||||
|
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置屏幕像素比
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器宽高
|
||||||
|
this.dom.appendChild(this.renderer.domElement); // 添加到dom中
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置控制器
|
||||||
|
*/
|
||||||
|
setControls() {
|
||||||
|
// 鼠标控制 相机,渲染dom
|
||||||
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||||
|
|
||||||
|
this.controls.autoRotateSpeed = 3
|
||||||
|
// 使动画循环使用时阻尼或自转 意思是否有惯性
|
||||||
|
this.controls.enableDamping = true;
|
||||||
|
// 动态阻尼系数 就是鼠标拖拽旋转灵敏度
|
||||||
|
this.controls.dampingFactor = 0.05;
|
||||||
|
// 是否可以缩放
|
||||||
|
this.controls.enableZoom = true;
|
||||||
|
// 设置相机距离原点的最远距离
|
||||||
|
this.controls.minDistance = 100;
|
||||||
|
// 设置相机距离原点的最远距离
|
||||||
|
this.controls.maxDistance = 300;
|
||||||
|
// 是否开启右键拖拽
|
||||||
|
this.controls.enablePan = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法,用于清理资源
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
// 移除DOM中的渲染器
|
||||||
|
if (this.dom && this.renderer && this.renderer.domElement && this.dom.contains(this.renderer.domElement)) {
|
||||||
|
this.dom.removeChild(this.renderer.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理渲染器
|
||||||
|
if (this.renderer) {
|
||||||
|
this.renderer.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理控制器
|
||||||
|
if (this.controls) {
|
||||||
|
this.controls.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Basic; // 添加默认导出
|
||||||
200
d/zy/three/3d-earth/src/ts/world/Earth.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import {
|
||||||
|
BufferAttribute, BufferGeometry, Color, DoubleSide, Group, Material, Mesh, MeshBasicMaterial, NormalBlending,
|
||||||
|
Object3D,
|
||||||
|
Points, PointsMaterial, ShaderMaterial,
|
||||||
|
SphereGeometry, Sprite, SpriteMaterial, Texture, TextureLoader, Vector3
|
||||||
|
} from "three";
|
||||||
|
|
||||||
|
import html2canvas from "html2canvas";
|
||||||
|
|
||||||
|
// import img_bg from '../../../static/images/earth/gradient.png'
|
||||||
|
import img_earth from '../../../static/images/earth/earth.jpg'
|
||||||
|
// import img_redCircle from '../../../static/images/earth/redCircle.png'
|
||||||
|
|
||||||
|
import img_a from '../../../static/images/earth/aircraft.png'
|
||||||
|
|
||||||
|
import { createAnimateLine, createLightPillar, createPointMesh, createWaveMesh, getCirclePoints, lon2xyz } from "../Utils/common";
|
||||||
|
import gsap from "gsap";
|
||||||
|
import { flyArc } from "../Utils/arc";
|
||||||
|
|
||||||
|
// 直接嵌入着色器代码
|
||||||
|
const earthVertex = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying vec3 vp;
|
||||||
|
varying vec3 vPositionNormal;
|
||||||
|
void main(void){
|
||||||
|
vUv = uv;
|
||||||
|
vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
|
||||||
|
vp = position;
|
||||||
|
vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const earthFragment = `
|
||||||
|
uniform vec3 glowColor;
|
||||||
|
uniform float bias;
|
||||||
|
uniform float power;
|
||||||
|
uniform float time;
|
||||||
|
varying vec3 vp;
|
||||||
|
varying vec3 vNormal;
|
||||||
|
varying vec3 vPositionNormal;
|
||||||
|
uniform float scale;
|
||||||
|
// 获取纹理
|
||||||
|
uniform sampler2D map;
|
||||||
|
// 纹理坐标
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main(void){
|
||||||
|
float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power );
|
||||||
|
if(vp.y > time && vp.y < time + 20.0) {
|
||||||
|
float t = smoothstep(0.0, 0.8, (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0 );
|
||||||
|
gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t );
|
||||||
|
}
|
||||||
|
gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a);
|
||||||
|
float b = 0.8;
|
||||||
|
gl_FragColor = gl_FragColor + texture2D( map, vUv );
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type punctuation = {
|
||||||
|
circleColor: number,
|
||||||
|
lightColumn: {
|
||||||
|
startColor: number, // 起点颜色
|
||||||
|
endColor: number, // 终点颜色
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type options = {
|
||||||
|
data: {
|
||||||
|
startArray: {
|
||||||
|
name: string,
|
||||||
|
E: number, // 经度
|
||||||
|
N: number, // 维度
|
||||||
|
},
|
||||||
|
endArray: {
|
||||||
|
name: string,
|
||||||
|
E: number, // 经度
|
||||||
|
N: number, // 维度
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
dom: HTMLElement,
|
||||||
|
textures: Record<string, Texture>, // 贴图
|
||||||
|
earth: {
|
||||||
|
radius: number, // 地球半径
|
||||||
|
rotateSpeed: number, // 地球旋转速度
|
||||||
|
isRotation: boolean // 地球组是否自转
|
||||||
|
}
|
||||||
|
satellite: {
|
||||||
|
show: boolean, // 是否显示卫星
|
||||||
|
rotateSpeed: number, // 旋转速度
|
||||||
|
size: number, // 卫星大小
|
||||||
|
number: number, // 一个圆环几个球
|
||||||
|
},
|
||||||
|
punctuation: punctuation,
|
||||||
|
flyLine: {
|
||||||
|
color: number, // 飞线的颜色
|
||||||
|
speed: number, // 飞机拖尾线速度
|
||||||
|
flyLineColor: number // 飞行线的颜色
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type uniforms = {
|
||||||
|
glowColor: { value: Color; }
|
||||||
|
scale: { type: string; value: number; }
|
||||||
|
bias: { type: string; value: number; }
|
||||||
|
power: { type: string; value: number; }
|
||||||
|
time: { type: string; value: any; }
|
||||||
|
isHover: { value: boolean; };
|
||||||
|
map: { value: Texture }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Earth {
|
||||||
|
|
||||||
|
public group: Group;
|
||||||
|
public earthGroup: Group;
|
||||||
|
|
||||||
|
public around: BufferGeometry
|
||||||
|
public aroundPoints: Points<BufferGeometry, PointsMaterial>;
|
||||||
|
|
||||||
|
public options: options;
|
||||||
|
public uniforms: uniforms
|
||||||
|
public timeValue: number;
|
||||||
|
|
||||||
|
public earth: Mesh<SphereGeometry, ShaderMaterial>;
|
||||||
|
public punctuationMaterial: MeshBasicMaterial;
|
||||||
|
public markupPoint: Group;
|
||||||
|
public waveMeshArr: Object3D[];
|
||||||
|
|
||||||
|
public circleLineList: any[];
|
||||||
|
public circleList: any[];
|
||||||
|
public x: number;
|
||||||
|
public n: number;
|
||||||
|
public isRotation: boolean;
|
||||||
|
public flyLineArcGroup: Group;
|
||||||
|
|
||||||
|
constructor(options: options) {
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.group = new Group()
|
||||||
|
this.group.name = "group";
|
||||||
|
this.group.scale.set(0, 0, 0)
|
||||||
|
this.earthGroup = new Group()
|
||||||
|
this.group.add(this.earthGroup)
|
||||||
|
this.earthGroup.name = "EarthGroup";
|
||||||
|
|
||||||
|
// 标注点效果
|
||||||
|
this.markupPoint = new Group()
|
||||||
|
this.markupPoint.name = "markupPoint"
|
||||||
|
this.waveMeshArr = []
|
||||||
|
|
||||||
|
// 卫星和标签
|
||||||
|
this.circleLineList = []
|
||||||
|
this.circleList = [];
|
||||||
|
this.x = 0;
|
||||||
|
this.n = 0;
|
||||||
|
|
||||||
|
// 地球自转
|
||||||
|
this.isRotation = this.options.earth.isRotation
|
||||||
|
|
||||||
|
// 扫光动画 shader
|
||||||
|
this.timeValue = 100
|
||||||
|
this.uniforms = {
|
||||||
|
glowColor: {
|
||||||
|
value: new Color(0x0cd1eb),
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
type: "f",
|
||||||
|
value: -1.0,
|
||||||
|
},
|
||||||
|
bias: {
|
||||||
|
type: "f",
|
||||||
|
value: 1.0,
|
||||||
|
},
|
||||||
|
power: {
|
||||||
|
type: "f",
|
||||||
|
value: 3.3,
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
type: "f",
|
||||||
|
value: this.timeValue,
|
||||||
|
},
|
||||||
|
isHover: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
|
||||||
|
this.createEarth(); // 创建地球
|
||||||
|
this.createStars(); // 添加星星
|
||||||
|
this.createEarthGlow() // 创建地球辉光
|
||||||
|
this.createEarthAperture() // 创建地球的大气层
|
||||||
|
await this.createMarkupPoint() // 创建柱状点位
|
||||||
73
d/zy/three/3d-earth/src/ts/world/Resources.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* 资源管理和加载
|
||||||
|
*/
|
||||||
|
import { LoadingManager, Texture, TextureLoader } from 'three';
|
||||||
|
import { resources } from './Assets'
|
||||||
|
|
||||||
|
export class Resources {
|
||||||
|
private manager!: LoadingManager
|
||||||
|
private callback: () => void;
|
||||||
|
private textureLoader!: InstanceType<typeof TextureLoader>;
|
||||||
|
public textures: Record<string, Texture>;
|
||||||
|
constructor(callback: () => void) {
|
||||||
|
this.callback = callback // 资源加载完成的回调
|
||||||
|
|
||||||
|
this.textures = {} // 贴图对象
|
||||||
|
|
||||||
|
this.setLoadingManager()
|
||||||
|
this.loadResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理加载状态
|
||||||
|
*/
|
||||||
|
private setLoadingManager() {
|
||||||
|
|
||||||
|
this.manager = new LoadingManager()
|
||||||
|
// 开始加载
|
||||||
|
this.manager.onStart = () => {
|
||||||
|
console.log('开始加载资源文件')
|
||||||
|
}
|
||||||
|
// 加载完成
|
||||||
|
this.manager.onLoad = () => {
|
||||||
|
this.callback()
|
||||||
|
}
|
||||||
|
// 正在进行中
|
||||||
|
this.manager.onProgress = (url) => {
|
||||||
|
console.log(`正在加载:${url}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manager.onError = url => {
|
||||||
|
console.log('加载失败:' + url)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载资源
|
||||||
|
*/
|
||||||
|
private loadResources(): void {
|
||||||
|
this.textureLoader = new TextureLoader(this.manager)
|
||||||
|
resources.textures?.forEach((item) => {
|
||||||
|
this.textureLoader.load(item.url, (t) => {
|
||||||
|
this.textures[item.name] = t
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法,用于清理资源
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
// 清理所有纹理
|
||||||
|
Object.keys(this.textures).forEach(key => {
|
||||||
|
const texture = this.textures[key];
|
||||||
|
if (texture && texture.dispose) {
|
||||||
|
texture.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空纹理对象
|
||||||
|
this.textures = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
119
d/zy/three/3d-earth/src/ts/world/World.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { resources } from "./Assets";
|
||||||
|
import { IWord } from "../interfaces/IWord";
|
||||||
|
import { Resources } from "./Resources";
|
||||||
|
import Basic from "./Basic"; // 修改导入,从命名导入改为默认导入
|
||||||
|
import Earth from "./Earth";
|
||||||
|
import Sizes from "../Utils/Sizes";
|
||||||
|
|
||||||
|
export default class World extends Basic {
|
||||||
|
public sizes: Sizes;
|
||||||
|
public res: Resources;
|
||||||
|
public earth: Earth;
|
||||||
|
|
||||||
|
constructor(option: IWord) {
|
||||||
|
super(option.dom)
|
||||||
|
|
||||||
|
this.sizes = new Sizes({ dom: option.dom })
|
||||||
|
|
||||||
|
this.res = new Resources(() => {
|
||||||
|
this.earth = new Earth({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
startArray: {
|
||||||
|
name: "北京",
|
||||||
|
E: 116.404,
|
||||||
|
N: 39.915,
|
||||||
|
},
|
||||||
|
endArray: [
|
||||||
|
{
|
||||||
|
name: "上海",
|
||||||
|
E: 121.4737,
|
||||||
|
N: 31.2304,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "广州",
|
||||||
|
E: 113.2806,
|
||||||
|
N: 23.1258,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "杭州",
|
||||||
|
E: 120.1614,
|
||||||
|
N: 30.2792,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dom: option.dom,
|
||||||
|
textures: this.res.textures,
|
||||||
|
earth: {
|
||||||
|
radius: 50, // 地球半径
|
||||||
|
rotateSpeed: 0.001, // 地球旋转速度
|
||||||
|
isRotation: true, // 地球组是否自转
|
||||||
|
},
|
||||||
|
satellite: {
|
||||||
|
show: true, // 是否显示卫星
|
||||||
|
rotateSpeed: 0.002, // 旋转速度
|
||||||
|
size: 2, // 卫星大小
|
||||||
|
number: 3, // 一个圆环几个球
|
||||||
|
},
|
||||||
|
punctuation: {
|
||||||
|
circleColor: 0x0cd1eb,
|
||||||
|
lightColumn: {
|
||||||
|
startColor: 0x0cd1eb, // 起点颜色
|
||||||
|
endColor: 0x00aaff, // 终点颜色
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flyLine: {
|
||||||
|
color: 0xff0000, // 飞线的颜色
|
||||||
|
speed: 0.05, // 飞机拖尾线速度
|
||||||
|
flyLineColor: 0xffffff // 飞行线的颜色
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.earth.init().then(() => {
|
||||||
|
this.scene.add(this.earth.earthGroup); // 使用 this.scene 而不是 this.earthGroup
|
||||||
|
|
||||||
|
this.tick();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染函数
|
||||||
|
*/
|
||||||
|
tick = () => {
|
||||||
|
this.controls.update(); // 更新控制器
|
||||||
|
|
||||||
|
if (this.earth) {
|
||||||
|
this.earth.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderer.render(this.scene, this.camera); // 渲染页面
|
||||||
|
|
||||||
|
requestAnimationFrame(this.tick); // 使页面一直执行
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁方法,用于清理资源
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
// 停止渲染循环
|
||||||
|
cancelAnimationFrame(this.tick as any);
|
||||||
|
|
||||||
|
// 销毁子组件
|
||||||
|
if (this.earth) {
|
||||||
|
this.earth.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.res) {
|
||||||
|
this.res.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sizes) {
|
||||||
|
this.sizes.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁基础组件
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
dist/assets/20251231114626_961_154-3c8fdbef.png
vendored
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
dist/assets/earth-74238849.jpg
vendored
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
dist/assets/favicon-6c6593f1.ico
vendored
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
dist/assets/glow-ef19d813.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
dist/assets/gradient-b59efedb.png
vendored
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
15
dist/assets/gsap-54c24ddd.js
vendored
Normal file
313
dist/assets/index-766f4bf0.js
vendored
Normal file
1
dist/assets/index-9962e5e1.css
vendored
Normal file
BIN
dist/assets/jitu-0ae999cf.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
dist/assets/label-f2c3f6ed.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
dist/assets/light_bg-DEu33pwq-09f9d8c7.png
vendored
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
dist/assets/light_column-aa132c89.png
vendored
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
dist/assets/order1-85843c41.png
vendored
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
dist/assets/order2-dcefcc30.png
vendored
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
dist/assets/order3-c3910ed9.png
vendored
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
dist/assets/pageBg-0f2bff15.png
vendored
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
dist/assets/redCircle-61b74946.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
dist/assets/ssssss-c7e62842.png
vendored
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
3112
dist/assets/three-6ab3b202.js
vendored
Normal file
BIN
dist/assets/titles-0941b966.png
vendored
Normal file
|
After Width: | Height: | Size: 126 KiB |
17
dist/assets/vue-3f711dbc.js
vendored
Normal file
BIN
dist/images/20251231114626_961_154.png
vendored
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
dist/images/earth/aircraft.png
vendored
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
dist/images/earth/aperture.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
dist/images/earth/earth.jpg
vendored
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
dist/images/earth/earths.jpg
vendored
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
dist/images/earth/glow.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
dist/images/earth/gradient.png
vendored
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
dist/images/earth/label-old.png
vendored
Normal file
|
After Width: | Height: | Size: 504 KiB |
BIN
dist/images/earth/label.png
vendored
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
dist/images/earth/light_column.png
vendored
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
dist/images/earth/redCircle.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
dist/images/light_bg-DEu33aaa.png
vendored
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
dist/images/light_bg-DEu33aaas.png
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
dist/images/light_bg-DEu33pwq.png
vendored
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
dist/images/pageBg.png
vendored
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
dist/images/title.png
vendored
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
dist/images/titles.png
vendored
Normal file
|
After Width: | Height: | Size: 126 KiB |
22
dist/index.html
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/assets/favicon-6c6593f1.ico">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/qweather-icons@1.3.0/font/qweather-icons.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>即途充电站大数据可视化系统</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-766f4bf0.js"></script>
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/vue-3f711dbc.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/three-6ab3b202.js">
|
||||||
|
<link rel="modulepreload" crossorigin href="/assets/gsap-54c24ddd.js">
|
||||||
|
<link rel="stylesheet" href="/assets/index-9962e5e1.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 110 KiB |
141
flexible.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
(function() {
|
||||||
|
// flexible.css
|
||||||
|
var cssText =
|
||||||
|
'' +
|
||||||
|
'@charset "utf-8";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-overflow-scrolling:touch}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:transparent}body,html{font-family:"Microsoft YaHei",sans-serif,Tahoma,Arial}article,aside,blockquote,body,button,code,dd,details,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,input,legend,li,menu,nav,ol,p,pre,section,td,textarea,th,ul{margin:0;padding:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}input,input[type=button],input[type=reset],input[type=submit]{resize:none;border:none;-webkit-appearance:none;border-radius:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:after,q:before{content:\'\'}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}a,ins{text-decoration:none}a:active,a:hover,a:link,a:visited{background:0 0;-webkit-tap-highlight-color:transparent;-webkit-tap-highlight-color:transparent;outline:0;text-decoration:none}';
|
||||||
|
// cssText end
|
||||||
|
|
||||||
|
var styleEl = document.createElement('style');
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(styleEl);
|
||||||
|
if (styleEl.styleSheet) {
|
||||||
|
if (!styleEl.styleSheet.disabled) {
|
||||||
|
styleEl.styleSheet.cssText = cssText;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
styleEl.innerHTML = cssText;
|
||||||
|
} catch (e) {
|
||||||
|
styleEl.innerText = cssText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
(function(win, lib) {
|
||||||
|
var doc = win.document;
|
||||||
|
var docEl = doc.documentElement;
|
||||||
|
var metaEl = doc.querySelector('meta[name="viewport"]');
|
||||||
|
var flexibleEl = doc.querySelector('meta[name="flexible"]');
|
||||||
|
var dpr = 0;
|
||||||
|
var scale = 0;
|
||||||
|
var tid;
|
||||||
|
var flexible = lib.flexible || (lib.flexible = {});
|
||||||
|
|
||||||
|
if (metaEl) {
|
||||||
|
console.warn('将根据已有的meta标签来设置缩放比例');
|
||||||
|
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
|
||||||
|
if (match) {
|
||||||
|
scale = parseFloat(match[1]);
|
||||||
|
dpr = parseInt(1 / scale);
|
||||||
|
}
|
||||||
|
} else if (flexibleEl) {
|
||||||
|
var content = flexibleEl.getAttribute('content');
|
||||||
|
if (content) {
|
||||||
|
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
|
||||||
|
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
|
||||||
|
if (initialDpr) {
|
||||||
|
dpr = parseFloat(initialDpr[1]);
|
||||||
|
scale = parseFloat((1 / dpr).toFixed(2));
|
||||||
|
}
|
||||||
|
if (maximumDpr) {
|
||||||
|
dpr = parseFloat(maximumDpr[1]);
|
||||||
|
scale = parseFloat((1 / dpr).toFixed(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dpr && !scale) {
|
||||||
|
var isAndroid = win.navigator.appVersion.match(/android/gi);
|
||||||
|
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
|
||||||
|
var devicePixelRatio = win.devicePixelRatio;
|
||||||
|
if (isIPhone) {
|
||||||
|
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
|
||||||
|
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
|
||||||
|
dpr = 3;
|
||||||
|
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
|
||||||
|
dpr = 2;
|
||||||
|
} else {
|
||||||
|
dpr = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其他设备下,仍旧使用1倍的方案
|
||||||
|
dpr = 1;
|
||||||
|
}
|
||||||
|
scale = 1 / dpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
docEl.setAttribute('data-dpr', dpr);
|
||||||
|
if (!metaEl) {
|
||||||
|
metaEl = doc.createElement('meta');
|
||||||
|
metaEl.setAttribute('name', 'viewport');
|
||||||
|
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
|
||||||
|
if (docEl.firstElementChild) {
|
||||||
|
docEl.firstElementChild.appendChild(metaEl);
|
||||||
|
} else {
|
||||||
|
var wrap = doc.createElement('div');
|
||||||
|
wrap.appendChild(metaEl);
|
||||||
|
doc.write(wrap.innerHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshRem() {
|
||||||
|
var width = docEl.getBoundingClientRect().width;
|
||||||
|
if (width / dpr > 540) {
|
||||||
|
width = width * dpr;
|
||||||
|
}
|
||||||
|
var rem = width / 10;
|
||||||
|
docEl.style.fontSize = rem + 'px';
|
||||||
|
flexible.rem = win.rem = rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
win.addEventListener('resize', function() {
|
||||||
|
clearTimeout(tid);
|
||||||
|
tid = setTimeout(refreshRem, 300);
|
||||||
|
}, false);
|
||||||
|
win.addEventListener('pageshow', function(e) {
|
||||||
|
if (e.persisted) {
|
||||||
|
clearTimeout(tid);
|
||||||
|
tid = setTimeout(refreshRem, 300);
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (doc.readyState === 'complete') {
|
||||||
|
doc.body.style.fontSize = 12 * dpr + 'px';
|
||||||
|
} else {
|
||||||
|
doc.addEventListener('DOMContentLoaded', function(e) {
|
||||||
|
doc.body.style.fontSize = 12 * dpr + 'px';
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
refreshRem();
|
||||||
|
|
||||||
|
flexible.dpr = win.dpr = dpr;
|
||||||
|
flexible.refreshRem = refreshRem;
|
||||||
|
flexible.rem2px = function(d) {
|
||||||
|
var val = parseFloat(d) * this.rem;
|
||||||
|
if (typeof d === 'string' && d.match(/rem$/)) {
|
||||||
|
val += 'px';
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
flexible.px2rem = function(d) {
|
||||||
|
var val = parseFloat(d) / this.rem;
|
||||||
|
if (typeof d === 'string' && d.match(/px$/)) {
|
||||||
|
val += 'rem';
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
})(window, window['lib'] || (window['lib'] = {}));
|
||||||
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/qweather-icons@1.3.0/font/qweather-icons.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>即途充电站大数据可视化系统</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16832
package-lock.json
generated
Normal file
77
package.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"name": "3d-earth",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "3d-earth",
|
||||||
|
"keywords": [
|
||||||
|
"GhostCat",
|
||||||
|
"3d",
|
||||||
|
"threejs",
|
||||||
|
"typescript",
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"author": "GhostCat",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"serve": "vite preview",
|
||||||
|
"lint": "eslint",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"src/*.{js,jsx,tsx,ts,vue}": "eslint --fix"
|
||||||
|
},
|
||||||
|
"homepage": "https://gcat.cc",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.10",
|
||||||
|
"@babel/preset-env": "^7.12.11",
|
||||||
|
"@commitlint/config-conventional": "^15.0.0",
|
||||||
|
"@size-limit/preset-small-lib": "^7.0.3",
|
||||||
|
"@types/lodash": "^4.14.172",
|
||||||
|
"@types/minimatch": "^6.0.0",
|
||||||
|
"@types/three": "^0.144.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||||
|
"@typescript-eslint/parser": "^5.13.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"babel-loader": "^8.2.2",
|
||||||
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
|
"copy-webpack-plugin": "^9.0.1",
|
||||||
|
"core-js": "^3.8.1",
|
||||||
|
"css-loader": "^6.2.0",
|
||||||
|
"eslint": "^8.10.0",
|
||||||
|
"eslint-webpack-plugin": "^3.1.1",
|
||||||
|
"html-webpack-plugin": "^4.5.0",
|
||||||
|
"husky": "^7.0.0",
|
||||||
|
"lint-staged": "^12.1.2",
|
||||||
|
"style-loader": "^3.2.1",
|
||||||
|
"ts-loader": "^8.0.12",
|
||||||
|
"ts-shader-loader": "^1.0.6",
|
||||||
|
"typescript": "^4.1.3",
|
||||||
|
"vite": "^4.0.0",
|
||||||
|
"vue-tsc": "^1.0.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dataview/datav-vue3": "^0.0.0-test.1672506674342",
|
||||||
|
"@jiaminghi/data-view": "^2.10.0",
|
||||||
|
"@kjgl77/datav-vue3": "^1.7.4",
|
||||||
|
"@tweakpane/core": "^1.0.6",
|
||||||
|
"autofit.js": "^3.2.8",
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"element-plus": "^2.13.0",
|
||||||
|
"eslint-webpack-plugin": "^3.1.1",
|
||||||
|
"gsap": "^3.7.1",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"pietile-eventemitter": "^1.0.1",
|
||||||
|
"postcss-px-to-viewport": "^1.1.1",
|
||||||
|
"postcss-pxtorem": "^6.1.0",
|
||||||
|
"sass": "^1.97.1",
|
||||||
|
"three": "^0.145.0",
|
||||||
|
"vue": "^3.2.45",
|
||||||
|
"vue-seamless-scroll": "^1.1.23",
|
||||||
|
"vue3-scale-box": "^0.1.9",
|
||||||
|
"vue3-seamless-scroll": "^3.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/images/20251231114626_961_154.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/images/earth/aircraft.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/earth/aperture.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/earth/earth.jpg
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
public/images/earth/earths.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
public/images/earth/glow.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/images/earth/gradient.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
public/images/earth/label-old.png
Normal file
|
After Width: | Height: | Size: 504 KiB |
BIN
public/images/earth/label.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/earth/light_column.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/earth/redCircle.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/images/light_bg-DEu33aaa.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/images/light_bg-DEu33aaas.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/images/light_bg-DEu33pwq.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/images/pageBg.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
public/images/title.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
public/images/titles.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
257
src/App.vue
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<!-- <ScaleBox :width="2220" :height="1050" bgc="transparent" :delay="100" :isFlat="false"> -->
|
||||||
|
<!-- <sals> -->
|
||||||
|
<div style="position: fixed;top: 0;left: 0;width: 100%;">
|
||||||
|
<img src="/public/images/titles.png" style="width: 100%;height: 55px;">
|
||||||
|
<img src="/public/images/light_bg-DEu33pwq.png"
|
||||||
|
style="width: 500px;height: 100px;position: absolute;left: 400px;animation: light-go-503410de 3s ease-in-out infinite forwards;">
|
||||||
|
<!-- 添加的时间显示 -->
|
||||||
|
<div
|
||||||
|
style="position: absolute;right: 30px;top: 5px;color:white;font-size:20px;display: flex;align-items: center;color: #3060F7;"
|
||||||
|
:key="currentTime">
|
||||||
|
<span v-if="now.text"> <i :class="`qi-${now.icon}`"></i>
|
||||||
|
|
||||||
|
丨
|
||||||
|
{{ now.text }}
|
||||||
|
丨
|
||||||
|
{{ now.windDir }}
|
||||||
|
丨
|
||||||
|
{{ now.temp }}°c
|
||||||
|
丨 </span>
|
||||||
|
{{ currentTime }}
|
||||||
|
</div>
|
||||||
|
<img src="/public/images/20251231114626_961_154.png" style="position: absolute;left: 5%;top: 5px;" alt="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<left style="position: fixed;top: 3%;left: 30px;" />
|
||||||
|
<center ></center>
|
||||||
|
<right style="position: fixed;top: 3%;right: 30px;" />
|
||||||
|
|
||||||
|
<div style="position: fixed;top: 25%;right: 26%;">
|
||||||
|
<img src="/public/images/light_bg-DEu33aaa.png"
|
||||||
|
style="width: 100%;height: 10px;position: absolute;left: -3%;animation: light-go-5034104 3s ease-in-out infinite forwards;">
|
||||||
|
<img style="width: 80%;" src="@/assets/img/jitu.png" alt="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div id="html2canvas" style="position: absolute; left: -9999px; top: -9999px;"></div>
|
||||||
|
<div ref="earthContainer" id="earth-container"
|
||||||
|
style="width: 100%; height: 100%;display: flex;align-items: center;justify-content: center;"></div>
|
||||||
|
</div>
|
||||||
|
<!-- </sals> -->
|
||||||
|
|
||||||
|
<!-- </ScaleBox> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import autofit from 'autofit.js'
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import World from './ts/world/World'
|
||||||
|
import left from './left.vue'
|
||||||
|
import right from './right.vue'
|
||||||
|
import center from './center.vue'
|
||||||
|
import Api from '@/api/index';
|
||||||
|
import axios from 'axios';
|
||||||
|
const earthContainer = ref<HTMLElement | null>(null)
|
||||||
|
let worldInstance: World | null = null
|
||||||
|
|
||||||
|
// 新增:用于保存当前时间的响应式变量
|
||||||
|
const currentTime = ref<string>(new Date().toLocaleTimeString())
|
||||||
|
const now = ref<any>({})
|
||||||
|
|
||||||
|
|
||||||
|
function getRandomRecords(arr: any, count = 10) {
|
||||||
|
if (count >= arr.length) return [...arr];
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
const usedIndices = new Set();
|
||||||
|
|
||||||
|
while (result.length < count) {
|
||||||
|
const index = Math.floor(Math.random() * arr.length);
|
||||||
|
if (!usedIndices.has(index)) {
|
||||||
|
usedIndices.add(index);
|
||||||
|
result.push(arr[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
|
||||||
|
getLocation().then(async (res: any) => {
|
||||||
|
const response = await axios({
|
||||||
|
url: `https://devapi.qweather.com/v7/weather/now?location=${res.longitude},${res.latitude}&key=fd9afe7f4325414c809baef7e86b907c`,
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
now.value = response.data.now
|
||||||
|
})
|
||||||
|
|
||||||
|
if (earthContainer.value) {
|
||||||
|
|
||||||
|
const res = await Api.get('/cityStats/list')
|
||||||
|
worldInstance = new World({
|
||||||
|
dom: earthContainer.value,
|
||||||
|
data: getRandomRecords(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
worldInstance?.updateEarthData(getRandomRecords(res));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update earth data:', error);
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 新增:每秒钟更新一次时间
|
||||||
|
setInterval(() => {
|
||||||
|
currentTime.value = new Date().toLocaleTimeString()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getLocation() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
reject(new Error('浏览器不支持地理定位'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
resolve({
|
||||||
|
latitude: position.coords.latitude,
|
||||||
|
longitude: position.coords.longitude,
|
||||||
|
accuracy: position.coords.accuracy
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
let message = '获取位置失败'
|
||||||
|
switch (error.code) {
|
||||||
|
case error.PERMISSION_DENIED:
|
||||||
|
message = '用户拒绝授权'
|
||||||
|
break
|
||||||
|
case error.POSITION_UNAVAILABLE:
|
||||||
|
message = '位置不可用'
|
||||||
|
break
|
||||||
|
case error.TIMEOUT:
|
||||||
|
message = '请求超时'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
reject(new Error(message))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 10000,
|
||||||
|
maximumAge: 60000
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (worldInstance) {
|
||||||
|
worldInstance.destroy()
|
||||||
|
worldInstance = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
autofit.init({
|
||||||
|
dh: 1080,
|
||||||
|
dw: 1920,
|
||||||
|
el: "body",
|
||||||
|
resize: true
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-image: url('/public/images/pageBg.png');
|
||||||
|
/* background-repeat: no-repeat; */
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 500px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes light-go-503410de {
|
||||||
|
0% {
|
||||||
|
left: 400px
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
left: 1100px;
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes light-go-5034104 {
|
||||||
|
0% {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
top: 100px;
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: 0px;
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.fire-div {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
border-top: 3px solid #0cd1eb;
|
||||||
|
padding: 6px 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
background: rgba(40, 108, 181, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
39
src/api/index.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// src/utils/request.js
|
||||||
|
import axios from 'axios';
|
||||||
|
import { ElMessage } from 'element-plus' // 引入Element Plus消息组件(如果使用Element UI)
|
||||||
|
|
||||||
|
// 创建 axios 实例
|
||||||
|
const Api = axios.create({
|
||||||
|
baseURL: 'http://39.105.28.231:1020', // 后端 API 基础路径
|
||||||
|
timeout: 5000 // 请求超时时间
|
||||||
|
});
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
Api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// 在发送请求之前做一些操作,比如添加 token
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// 请求错误处理
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
Api.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
const res = response.data;
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
// ElMessage.error(error.message || '请求失败');
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Api;
|
||||||
BIN
src/assets/bei.jpg
Normal file
|
After Width: | Height: | Size: 674 KiB |
29674
src/assets/china.json
Normal file
103310
src/assets/chinaProvince.json
Normal file
BIN
src/assets/img/-s-bg_.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
src/assets/img/-s-icon-保安队.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/assets/img/-s-icon-城管员.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/img/-s-icon-巡防队.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
src/assets/img/-s-点缀-1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/img/-s-点缀.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/img/1.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
src/assets/img/2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/img/big-data/center-details-data1.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/img/big-data/center-details-data2.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/img/big-data/center-details-data3.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
src/assets/img/big-data/center-details-data4.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
src/assets/img/big-data/center-details-data5.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/img/big-data/center-details-data6.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
src/assets/img/chatKuang.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
src/assets/img/headers/juxing1.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |