ZhangXianQiang
2024-04-17 7d8fabb5feddf7d2daffd2452781c515d2eb13cd
Merge branch 'dev-threejs'
7个文件已修改
19个文件已添加
2个文件已删除
141443 ■■■■■ 已修改文件
package.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/index.html 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/circle-point.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/gz-map-fx.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/gz-map.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/map_texture.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/rotating-point2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/rotatingAperture.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/texture/scene-bg2.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong.json 60219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong1.json 73667 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/map/zigong2.json 6748 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/home/data-view/components/data-map.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-cover/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/camera.js 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/index.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/renderer.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/eventEmitter.js 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/sizes.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/utils/time.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/enviroment.js 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/map.js 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/experience/world/world.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map-three/index.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-map/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/components/screen-wrapper/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/screen/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json
@@ -41,6 +41,7 @@
    "axios": "0.24.0",
    "clipboard": "2.0.8",
    "core-js": "3.25.3",
    "d3": "^7.9.0",
    "echarts": "5.4.0",
    "echarts-gl": "^2.0.9",
    "element-ui": "2.15.14",
@@ -55,6 +56,7 @@
    "quill": "1.3.7",
    "screenfull": "5.0.2",
    "sortablejs": "1.10.2",
    "three": "^0.163.0",
    "v-scale-screen": "1.0.0",
    "vue": "2.6.12",
    "vue-count-to": "1.0.13",
public/index.html
@@ -1,21 +1,27 @@
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= webpackConfig.name %></title>
    <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
      <style>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title>
    <%= webpackConfig.name %>
  </title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0px;
      padding: 0px;
      overflow: hidden;
    }
    .chromeframe {
      margin: 0.2em 0;
      background: #ccc;
@@ -92,6 +98,7 @@
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
@@ -105,6 +112,7 @@
        -ms-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
@@ -194,15 +202,17 @@
      opacity: 0.5;
    }
  </style>
  </head>
  <body>
    <div id="app">
        <div id="loader-wrapper">
            <div id="loader"></div>
            <div class="loader-section section-left"></div>
            <div class="loader-section section-right"></div>
            <div class="load_title">正在加载系统资源,请耐心等待</div>
        </div>
    </div>
  </body>
</html>
</head>
<body>
  <div id="app">
    <div id="loader-wrapper">
      <div id="loader"></div>
      <div class="loader-section section-left"></div>
      <div class="loader-section section-right"></div>
      <div class="load_title">正在加载系统资源,请耐心等待</div>
    </div>
  </div>
</body>
</html>
src/assets/map/texture/circle-point.png
src/assets/map/texture/gz-map-fx.jpg
src/assets/map/texture/gz-map.jpg
src/assets/map/texture/map_texture.jpg
src/assets/map/texture/rotating-point2.png
src/assets/map/texture/rotatingAperture.png
src/assets/map/texture/scene-bg2.png
src/assets/map/zigong.json
File was deleted
src/assets/map/zigong1.json
File was deleted
src/assets/map/zigong2.json
New file
Diff too large
src/permission.js
@@ -8,7 +8,7 @@
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register',]
const whiteList = ['/login', '/register','/screen']
router.beforeEach((to, from, next) => {
  NProgress.start()
src/views/home/data-view/components/data-map.vue
@@ -8,7 +8,7 @@
<script>
import * as echarts from 'echarts';
import 'echarts-gl';
import mapData from '@/assets/map/zigong.json';
import mapData from '@/assets/map/zigong2.json';
echarts.registerMap('zigong', mapData);
console.log(mapData);
let mapChart = null;
src/views/screen/components/screen-map-cover/index.vue
New file
@@ -0,0 +1,36 @@
<template>
  <div class="map-container">
    <wrapper-title :title="'区域地图'"></wrapper-title>
    <div class="map-content">
    </div>
  </div>
</template>
<script>
import WrapperTitle from '../wrapper-title/index';
export default {
  name: 'ScreenMapCover',
  components: {
    WrapperTitle
  }
}
</script>
<style lang="scss" scoped>
.map-container {
  width: 100%;
  flex: 1;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  .map-content {
    flex: 1;
    pointer-events: none;
  }
}
</style>
src/views/screen/components/screen-map-three/experience/camera.js
New file
@@ -0,0 +1,50 @@
import {MathUtils} from 'three';
import { PerspectiveCamera, CameraHelper } from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
export default class Camera {
    constructor(experience) {
        this.experience = experience;
        this.scene = this.experience.scene;
        this.canvas = this.experience.canvas;
        this.sizes = this.experience.sizes;
        this.setInstance();
        this.setOrbitControls();
    }
    // 设置透视相机
    setInstance() {
        this.instance = new PerspectiveCamera(45,this.sizes.width / this.sizes.height, 0.001, 90000000);
        this.instance.position.set(0, 45, 45);
        this.scene.add(this.instance);
        // const help = new CameraHelper(this.instance);
        // this.scene.add(help);
    }
    setOrbitControls() {
        this.controls = new OrbitControls(this.instance, this.canvas);
        this.controls.target.set(0, 0, 5);
        this.controls.enableDamping = true;
        this.controls.minDistance = 20;
        this.controls.maxDistance = 80;
        this.controls.maxPolarAngle = MathUtils.degToRad(80);
        // this.controls.maxPolarAngle = (-Math.PI / 2);
    }
    resize() {
        // 重新计算比例
        this.cameraAspect = (this.sizes.width / this.sizes.height) * this.frustrum;
        this.instance.left = -this.cameraAspect / 2;
        this.instance.right = this.cameraAspect / 2;
        this.instance.top = this.frustrum / 2;
        this.instance.bottom = -this.frustrum  / 2;
        this.instance.updateProjectionMatrix();
    }
    update() {
        this.controls.update();
    }
}
src/views/screen/components/screen-map-three/experience/index.js
New file
@@ -0,0 +1,45 @@
import { Scene, GridHelper,AxesHelper } from 'three';
import Stats from "three/examples/jsm/libs/stats.module";
import World from "./world/world";
import Camera from "./camera";
import Renderer from "./renderer";
// 工具类
import Sizes from "./utils/sizes";
import Time from "./utils/time";
export default class Experience {
  constructor(canvas) {
    this.canvas = canvas;
    this.sizes = new Sizes();
    this.time = new Time();
    this.scene = new Scene();
    this.camera = new Camera(this);
    this.renderer = new Renderer(this);
    this.world = new World(this);
    // const size = 200;
    // const divisions = 200;
    // const gridHelper = new GridHelper(size, divisions);
    // this.scene.add(gridHelper);
    this.stats = new Stats();
    document.querySelector('.map-container').appendChild(this.stats.dom);
    // 帧
    this.time.on('tick', () => {
      this.update();
    });
  }
  update() {
    this.camera.update();
    this.world.update();
    this.renderer.update();
    this.stats.update();
  }
}
src/views/screen/components/screen-map-three/experience/renderer.js
New file
@@ -0,0 +1,33 @@
import * as THREE from 'three';
export default class Renderer {
    constructor(experience) {
        this.experience = experience;
        this.canvas = this.experience.canvas;
        this.sizes = this.experience.sizes;
        this.scene = this.experience.scene;
        this.camera = this.experience.camera;
        this.setInstance();
    }
    setInstance() {
        this.instance = new THREE.WebGLRenderer({
            canvas: this.canvas,
            antialias: true,
            alpha: true,
            // logarithmicDepthBuffer: true
        });
        // this.instance.toneMapping = THREE.CineonToneMapping;
        // this.instance.toneMappingExposure = 1.75;
        // this.instance.shadowMap.enabled = true;
        // this.instance.shadowMap.type = THREE.PCFSoftShadowMap;
        this.instance.setSize(this.sizes.width, this.sizes.height);
        this.instance.setPixelRatio(this.sizes.pixelRatio);
    }
    resize() {
        this.instance.setSize(this.sizes.width, this.sizes.height);
        this.instance.setPixelRatio(this.sizes.pixelRatio);
    }
    update() {
        this.instance.render(this.scene, this.camera.instance);
    }
}
src/views/screen/components/screen-map-three/experience/utils/eventEmitter.js
New file
@@ -0,0 +1,197 @@
export default class EventEmitter
{
    constructor()
    {
        this.callbacks = {}
        this.callbacks.base = {}
    }
    on(_names, callback)
    {
        // Errors
        if(typeof _names === 'undefined' || _names === '')
        {
            console.warn('wrong names')
            return false
        }
        if(typeof callback === 'undefined')
        {
            console.warn('wrong callback')
            return false
        }
        // Resolve names
        const names = this.resolveNames(_names)
        // Each name
        names.forEach((_name) =>
        {
            // Resolve name
            const name = this.resolveName(_name)
            // Create namespace if not exist
            if(!(this.callbacks[ name.namespace ] instanceof Object))
                this.callbacks[ name.namespace ] = {}
            // Create callback if not exist
            if(!(this.callbacks[ name.namespace ][ name.value ] instanceof Array))
                this.callbacks[ name.namespace ][ name.value ] = []
            // Add callback
            this.callbacks[ name.namespace ][ name.value ].push(callback)
        })
        return this
    }
    off(_names)
    {
        // Errors
        if(typeof _names === 'undefined' || _names === '')
        {
            console.warn('wrong name')
            return false
        }
        // Resolve names
        const names = this.resolveNames(_names)
        // Each name
        names.forEach((_name) =>
        {
            // Resolve name
            const name = this.resolveName(_name)
            // Remove namespace
            if(name.namespace !== 'base' && name.value === '')
            {
                delete this.callbacks[ name.namespace ]
            }
            // Remove specific callback in namespace
            else
            {
                // Default
                if(name.namespace === 'base')
                {
                    // Try to remove from each namespace
                    for(const namespace in this.callbacks)
                    {
                        if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
                        {
                            delete this.callbacks[ namespace ][ name.value ]
                            // Remove namespace if empty
                            if(Object.keys(this.callbacks[ namespace ]).length === 0)
                                delete this.callbacks[ namespace ]
                        }
                    }
                }
                // Specified namespace
                else if(this.callbacks[ name.namespace ] instanceof Object && this.callbacks[ name.namespace ][ name.value ] instanceof Array)
                {
                    delete this.callbacks[ name.namespace ][ name.value ]
                    // Remove namespace if empty
                    if(Object.keys(this.callbacks[ name.namespace ]).length === 0)
                        delete this.callbacks[ name.namespace ]
                }
            }
        })
        return this
    }
    trigger(_name, _args)
    {
        // Errors
        if(typeof _name === 'undefined' || _name === '')
        {
            console.warn('wrong name')
            return false
        }
        let finalResult = null
        let result = null
        // Default args
        const args = !(_args instanceof Array) ? [] : _args
        // Resolve names (should on have one event)
        let name = this.resolveNames(_name)
        // Resolve name
        name = this.resolveName(name[ 0 ])
        // Default namespace
        if(name.namespace === 'base')
        {
            // Try to find callback in each namespace
            for(const namespace in this.callbacks)
            {
                if(this.callbacks[ namespace ] instanceof Object && this.callbacks[ namespace ][ name.value ] instanceof Array)
                {
                    this.callbacks[ namespace ][ name.value ].forEach(function(callback)
                    {
                        result = callback.apply(this, args)
                        if(typeof finalResult === 'undefined')
                        {
                            finalResult = result
                        }
                    })
                }
            }
        }
        // Specified namespace
        else if(this.callbacks[ name.namespace ] instanceof Object)
        {
            if(name.value === '')
            {
                console.warn('wrong name')
                return this
            }
            this.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
            {
                result = callback.apply(this, args)
                if(typeof finalResult === 'undefined')
                    finalResult = result
            })
        }
        return finalResult
    }
    resolveNames(_names)
    {
        let names = _names
        names = names.replace(/[^a-zA-Z0-9 ,/.]/g, '')
        names = names.replace(/[,/]+/g, ' ')
        names = names.split(' ')
        return names
    }
    resolveName(name)
    {
        const newName = {}
        const parts = name.split('.')
        newName.original  = name
        newName.value     = parts[ 0 ]
        newName.namespace = 'base' // Base namespace
        // Specified namespace
        if(parts.length > 1 && parts[ 1 ] !== '')
        {
            newName.namespace = parts[ 1 ]
        }
        return newName
    }
}
src/views/screen/components/screen-map-three/experience/utils/sizes.js
New file
@@ -0,0 +1,33 @@
/**
 * 计算大小
 */
import EventEmitter from './eventEmitter';
export default class Sizes extends EventEmitter {
    constructor() {
        super();
        this.width = document.body.clientWidth;
        this.height = document.body.clientHeight;
        this.device = document.body.clientWidth <= 968 ? 'mobile' : 'pc';
        // 设备像素
        this.pixelRatio = Math.min(window.devicePixelRatio, 2);
        // 宽高变化
        window.addEventListener('resize', () => {
            // this.width = window.innerWidth;
            // this.height = window.innerHeight;
            this.width = document.body.clientWidth;
            this.height = document.body.clientHeight;
            this.pixelRatio = Math.min(window.devicePixelRatio, 2);
            this.trigger('resize');
            if(this.width < 968 && this.device !== 'mobile') {
                this.device = 'mobile';
                this.trigger('devicechange');
            } else if(this.width >= 968 && this.device !== 'pc') {
                this.device = 'pc';
                this.trigger('devicechange');
            }
        });
    }
}
src/views/screen/components/screen-map-three/experience/utils/time.js
New file
@@ -0,0 +1,25 @@
import EventEmitter from "./eventEmitter";
export default class Time extends EventEmitter {
    constructor() {
        super();
        this.start = Date.now();
        this.current = this.start;
        this.elapsed = 0;
        this.delta = 16;
        window.requestAnimationFrame(() => {
            this.tick();
        });
    }
    tick() {
        const currentTime = Date.now();
        this.delta = currentTime - this.current;
        this.current = currentTime;
        this.elapsed = this.current - this.start;
        this.trigger('tick');
        window.requestAnimationFrame(() => {
            this.tick();
        });
    }
}
src/views/screen/components/screen-map-three/experience/world/enviroment.js
New file
@@ -0,0 +1,114 @@
import * as THREE from 'three';
import rotatingAperture from '@/assets/map/texture/rotatingAperture.png';
import rotatingPoint from '@/assets/map/texture/rotating-point2.png';
import circlePoint from '@/assets/map/texture/circle-point.png';
import sceneBg from '@/assets/map/texture/scene-bg2.png';
export default class Enviroment {
  constructor(experience) {
    this.experience = experience;
    this.scene = this.experience.scene;
    this.textureLoader = new THREE.TextureLoader();
    this.setSunLight();
    this.setRotateHola();
    this.setBackground();
    this.setCirclePoint();
  }
  setSunLight() {
    //   平行光1
    let directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.6);
    directionalLight1.position.set(400, 200, 200);
    //   平行光2
    let directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
    directionalLight2.position.set(-400, -200, -300);
    // 环境光
    let ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    this.scene.add(directionalLight1);
    this.scene.add(directionalLight2);
    this.scene.add(ambientLight);
  }
  setRotateHola() {
    const rotatingApertureTexture = this.textureLoader.load(rotatingAperture);
    const rotatingPointTexture = this.textureLoader.load(rotatingPoint);
    const meshConfig1 = {
      width: 48,
      height: 48,
      texture: rotatingApertureTexture,
      positionList: [0, 0.4, 0],
      scaleList: [1, 1, 1],
      rotateList: [-Math.PI / 2, 0, 0]
    };
    const meshConfig2 = {
      width: 40,
      height: 40,
      texture: rotatingPointTexture,
      positionList: [0, 0.3, 0],
      scaleList: [1, 1, 1],
      rotateList: [-Math.PI / 2, 0, 0]
    };
    this.hola1 = this.createMesh(meshConfig1);
    this.hola2 = this.createMesh(meshConfig2);
    this.scene.add(this.hola1);
    this.scene.add(this.hola2);
  }
  setBackground() {
    const sceneBgTexture = this.textureLoader.load(sceneBg);
    const plane = new THREE.PlaneGeometry(120, 120);
    const material = new THREE.MeshPhongMaterial({
      // color: 0x061920,
      color: 0xffffff,
      map: sceneBgTexture,
      transparent: true,
      opacity: 1,
      // depthTest: true
    });
    this.background = new THREE.Mesh(plane, material);
    this.background.rotation.set(-Math.PI / 2, 0, 0);
    this.background.position.set(0, 0.1, 0);
    this.scene.add(this.background);
  }
  setCirclePoint() {
    const circleTexture = this.textureLoader.load(circlePoint);
    const plane = new THREE.PlaneGeometry(45, 45);
    const material = new THREE.MeshPhongMaterial({
      color: 0x00ffff,
      map: circleTexture,
      transparent: true,
      opacity: 1,
      depthTest: false,
    });
    this.circle = new THREE.Mesh(plane, material);
    this.circle.rotation.set(-Math.PI / 2, 0, 0);
    this.circle.position.set(0, 0.2, 0);
    this.scene.add(this.circle);
  }
  createMesh(config) {
    let { width, height, texture, positionList, rotateList, scaleList } = config;
    let plane = new THREE.PlaneGeometry(width, height);
    let material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      opacity: 1,
      depthTest: false,
    });
    let mesh = new THREE.Mesh(plane, material);
    mesh.position.set(...positionList);
    mesh.scale.set(...scaleList);
    mesh.rotation.set(...rotateList);
    return mesh;
  }
  update() {
    if (this.hola1) {
      this.hola1.rotation.z += 0.001;
    }
    if (this.hola2) {
      this.hola2.rotation.z -= 0.001;
    }
  }
}
src/views/screen/components/screen-map-three/experience/world/map.js
New file
@@ -0,0 +1,138 @@
import * as THREE from 'three';
import * as d3 from 'd3';
import mapData from '@/assets/map/zigong2.json';
import textureMapImage from '@/assets/map/texture/gz-map.jpg';
import textureMapFxImage from '@/assets/map/texture/gz-map-fx.jpg';
// 地图深度
const MAP_DEPTH = 0.2;
const projection = d3.geoMercator().center([104.779307, 29.33924]).translate([0, 0, 0]);
export default class Map {
    constructor(experience) {
        this.experience = experience;
        this.scene = this.experience.scene;
        this.material = null;
        this.textureLoader = new THREE.TextureLoader();
        this.setTexture();
        this.operationData(mapData);
    }
    setTexture() {
        const textureMap = this.textureLoader.load(textureMapImage);
        const textureMapFx = this.textureLoader.load(textureMapFxImage);
        textureMap.wrapS = textureMapFx.wrapS = THREE.RepeatWrapping;
        textureMap.wrapT = textureMapFx.wrapT = THREE.RepeatWrapping;
        textureMap.flipY = textureMapFx.flipY = false;
        textureMap.rotation = textureMapFx.rotation = THREE.MathUtils.degToRad(45);
        const scale = 0.128;
        textureMap.repeat.set(scale, scale);
        textureMapFx.repeat.set(scale, scale);
        this.topFaceMaterial = new THREE.MeshPhongMaterial({
            map: textureMap,
            color: 0xb4eeea,
            combine: THREE.MultiplyOperation,
            transparent: true,
            opacity: 1,
        });
        this.sideMaterial = new THREE.MeshLambertMaterial({
            color: 0x123024,
            transparent: true,
            opacity: 0.9,
        });
    }
    /**
     * 解析json数据,并绘制地图多边形
     * @param {*} jsondata 地图数据
     */
    operationData(jsondata) {
        this.map = new THREE.Object3D();
        // geo信息
        const features = jsondata.features;
        features.forEach((feature) => {
            // 单个省份 对象
            const province = new THREE.Object3D();
            // 地址
            province.properties = feature.properties.name;
            // 多个情况
            // console.log(feature.geometry.type);
            if (feature.geometry.type === "MultiPolygon") {
                console.log(feature.geometry.coordinates);
                feature.geometry.coordinates.forEach((coordinate) => {
                    coordinate.forEach((rows) => {
                        const line = this.drawBoundary(rows);
                        const mesh = this.drawExtrudeMesh(rows);
                        province.add(line);
                        province.add(mesh);
                    });
                });
            }
            // 单个情况
            if (feature.geometry.type === "Polygon") {
                feature.geometry.coordinates.forEach((coordinate) => {
                    const line = this.drawBoundary(coordinate);
                    const mesh = this.drawExtrudeMesh(coordinate);
                    province.add(line);
                    province.add(mesh);
                });
            }
            this.map.add(province);
        });
        this.map.position.set(1, 1, -2);
        this.map.scale.set(10, 10, 10);
        this.map.rotation.set(THREE.MathUtils.degToRad(-90), 0, THREE.MathUtils.degToRad(20));
        this.container = new THREE.Object3D();
        this.container.add(this.map);
        this.scene.add(this.container);
    }
    /**
     * 画区域分界线
     * @param {*} polygon 区域坐标点数组
     * @returns 区域分界线
     */
    drawBoundary(polygon) {
        const points = [];
        for (let i = 0; i < polygon.length; i++) {
            const [x, y] = projection(polygon[i]);
            points.push(new THREE.Vector3(x, -y, 0));
        }
        const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
        const lineMaterial = new THREE.LineBasicMaterial({
            color: 0xffffff,
            linewidth: 2,
            transparent: true,
            depthTest: false,
        });
        const line = new THREE.Line(lineGeometry, lineMaterial);
        line.translateZ(MAP_DEPTH + 0.001);
        return line;
    }
    /**
     * 绘制区域多边形
     * @param {*} polygon 区域坐标点数组
     * @returns 区域多边形
     */
    drawExtrudeMesh(polygon) {
        const shape = new THREE.Shape();
        for (let i = 0; i < polygon.length; i++) {
            const [x, y] = projection(polygon[i]);
            if (i === 0) {
                shape.moveTo(x, -y);
            }
            shape.lineTo(x, -y);
        }
        const geometry = new THREE.ExtrudeGeometry(shape, {
            depth: MAP_DEPTH,
            bevelEnabled: false,
            bevelSegments: 1,
            bevelThickness: 0.1,
        });
        return new THREE.Mesh(geometry, [
            this.topFaceMaterial,
            this.sideMaterial,
        ]);
    }
}
src/views/screen/components/screen-map-three/experience/world/world.js
New file
@@ -0,0 +1,17 @@
import EventEmitter from '../utils/eventEmitter';
import Enviroment from './enviroment';
import Map from './map';
export default class World extends EventEmitter  {
  constructor(experience) {
    super();
    this.experience = experience;
    this.scene = this.experience.scene;
    this.enviroment = new Enviroment(this.experience);
    this.map = new Map(this.experience);
  }
  update() {
    this.enviroment.update();
  }
}
src/views/screen/components/screen-map-three/index.vue
New file
@@ -0,0 +1,31 @@
<template>
  <div class="map-container" >
    <canvas class="world" ref="worldContainer"></canvas>
  </div>
</template>
<script>
import Experience from './experience/index';
let world = null;
export default {
  name: 'ScreenMapThree',
  mounted() {
    world = new Experience(this.$refs.worldContainer);
  }
}
</script>
<style lang="scss" scoped>
.map-container {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 0;
  .world {
    width: 100%;
    height: 100%;
  }
}
</style>
src/views/screen/components/screen-map/index.vue
@@ -12,7 +12,7 @@
<script>
import * as echarts from 'echarts';
import 'echarts-gl';
import mapData from '@/assets/map/zigong1.json';
import mapData from '@/assets/map/zigong2.json';
import WrapperTitle from '../wrapper-title/index';
import ScreenData from '../screen-data/index';
src/views/screen/components/screen-wrapper/index.vue
@@ -5,13 +5,14 @@
      返回
    </div>
    <div class="wrapper-content">
      <screen-map-three></screen-map-three>
      <div class="left-container wrapper">
        <screen-face class="enter-left" :class="{ 'animate-enter-x': isEnd }"></screen-face>
        <screen-car class="enter-left animate-delay-1" :class="{ 'animate-enter-x': isEnd }"></screen-car>
        <screen-video class="enter-left animate-delay-2" :class="{ 'animate-enter-x': isEnd }"></screen-video>
      </div>
      <div class="center-container center-wrapper">
        <screen-map></screen-map>
        <screen-map-cover></screen-map-cover>
        <screen-table class="enter-top" :class="{ 'animate-enter-y': isEnd }"></screen-table>
        <!-- <screen-detection></screen-detection> -->
@@ -30,16 +31,19 @@
import ScreenFace from '../screen-face/index';
import ScreenVideo from '../screen-video/index';
import ScreenCar from '../screen-car/index';
import ScreenMap from '../screen-map/index';
import ScreenMapCover from '../screen-map-cover/index';
import ScreenTable from '../screen-table/index';
import ScreenMapThree from '../screen-map-three/index';
import ScreenData from '../screen-data/index';
export default {
  name: 'ScreenWrapper',
  components: {
    SelectItem,
    ScreenExamine,
    ScreenMap,
    ScreenTable,
    ScreenMapCover,
    ScreenMapThree,
    ScreenFace,
    ScreenVideo,
    ScreenCar,
@@ -133,24 +137,30 @@
  flex-direction: column;
  justify-content: space-between;
}
.animate-enter-x {
  animation: enter-x 0.4s ease forwards;
}
.animate-enter-y {
  animation: enter-y 0.4s ease forwards;
}
.enter-left {
  transform: translateX(-100px);
  opacity: 0;
}
.enter-right {
  transform: translateX(100px);
  opacity: 0;
}
.enter-top {
  transform: translateY(100px);
  opacity: 0;
}
.animate-delay-1 {
  animation-delay: 0.1s;
}
@@ -164,14 +174,22 @@
}
@keyframes enter-x {
  100% {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
@keyframes enter-y {
  100% {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
src/views/screen/index.vue
@@ -1,6 +1,5 @@
<template>
  <div class="screen-container">
    <screen-title></screen-title>
    <v-scale-screen width="1920" height="1080" :autoScale="true" :delay="0" class="screen">
      <screen-wrapper></screen-wrapper>
@@ -13,6 +12,7 @@
import ScreenTitle from './components/screen-title/index.vue';
import ScreenWrapper from './components/screen-wrapper/index.vue';
export default {
  name: 'App',
  components: {