• vue3+three.js实现疫情可视化


    前言

    此系列已完结,共3部分:

    • part1:https://blog.csdn.net/xi1213/article/details/126824752
    • part2:https://blog.csdn.net/xi1213/article/details/127719364
    • part3:https://blog.csdn.net/xi1213/article/details/128072446

    自成都九月份以来疫情原因被封了一两周,居家着实无聊,每天都是盯着微信公众号发布的疫情数据看,那种页面,就我一个前端仔来说,看着是真的丑啊!(⊙_⊙)?既然丑,那就自己动手开整!项目是2022.9.5开始的,截止2022.9.12我完成了大概有八成。主要是想让数据更加直观,而且可离线下载(当然还有装逼!┑( ̄Д  ̄)┍)。

    项目描述

    为证明是有料的,先看效果图(提前装逼!┗|`O′|┛ 嗷~~):

    请添加图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    项目我是公开了的( ̄m ̄)有兴趣的可以下下来玩玩,这是我第一次使用vue3+ts构建项目,肯定还有不足的地方(比如ts中疯狂的:any,一直any一直爽^o^/)。
    这里是浏览链接:http://xisite.top/original/bigScreen/28/index.html#/
    这里是项目链接:https://gitee.com/xi1213/covid19-visualization
    (欢迎star!欢迎star!欢迎star!(●’◡’●)嘿嘿嘿~)
    项目中使用到的技术有:vue3、TypeScript、Three.js、Echarts、elementPlus。

    项目目标

    1、以为3D形式展示全球疫情分布。
    2、显示实时疫情数值。
    3、以图表形式分析疫情数据
    4、允许下载各地疫情excel表格。
    5、自动获取用户位置。
    6、分析当地疫情数据。
    7、生成当地疫情word报告。

    api说明

    本项目数据来源:新浪公共疫情api(新浪的数据来源于国家卫健委、各省市区卫健委、各省市区政府、港澳台官方渠道等公开数据。这也是够权威官方了)。我主要使用了两个新浪的api和一个太平洋网络ip地址查询web接口。
    1、https://news.sina.com.cn/project/fymap/ncp2020_full_data.json
    get方式,无入参。该api可获取全球各国大致疫情数据,以及国内的详情疫情数据,这里就api中的字段做一下说明,字段是我自己推测出来的含义,不会100%全而准(→_→):
    在这里插入图片描述

    {
        "add_daily(国内今日数据)": {
            "addcon(今日确诊新增数)": "",
            "addcure(今日治愈新增)": "",
            "adddeath(今日死亡新增)": "",
            "addjwsr(今日境外输入新增)": "",
            "addlocIncrNum(今日本土新增)"
        },
        "cachetime(数据缓存时间)": "",
        "curetotal(国内治愈总数)": "",
        "deathtotal(国内死亡总数)": "",
        "gntotal(国内确诊总数)": "",
        "highAndMiddle(中高风险地列表)": [
            {
                "allname(全名)": "",
                "list(城市列表)": "",
                "province(省名)": "",
                "province_high_areas(高风险区域)": "",
                "province_middle_areas(中风险区域)": "",
                "province_high_num(高风险区域数)": "",
                "province_middle_num(中风险区域数)": "",
                "province_total(风险地总数)": ""
            }
        ],
        "historylist(国内疫情历史数据)": [
            {
                "cn_conNum(确诊总数)": "",
                "cn_cureNum(治愈总数)": "",
                "cn_deathNum(死亡数)": "",
                "cn_jwsrNum(境外输入)": "",
                "ymd(当前时间)": ""
            }
        ],
        "jwsrTop(境外数据前10列表)": [],
        "list(全国各省疫情数据列表)": [
            {
                "asymptomNum(较昨日新增数)": "",
                "city(城市列表)": [],
                "cureNum(治愈数)": "",
                "deathNum(死亡数)": "",
                "econNum(现存确诊数)": "",
                "ename(英文省名)": "",
                "jwsrNum(境外输入数)": "",
                "name(省名)": "",
                "value(累计数)": ""
            }
        ],
        "locIncrProTop(本土新增前十列表)": [],
        "othertotal(其他总数)": {
            "certain(全球现存确诊)": "",
            "certain_inc(今日确诊新增数)": "",
            "die(全球死亡数)": "",
            "die_inc(死亡新增数)": "",
            "ecertain(全球治愈数)": "",
            "ecertain_inc(治愈新增数)": ""
        },
        "times(数据截止时间)": "",
        "worldlist(世界各国疫情列表)": [
            {
                "name(国名)": "",
                "value(累计数)": "",
                "econNum(确诊数)": "",
                "deathNum(死亡数)": "",
                "cureNum(治愈数)": ""
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    2、https://gwpre.sina.cn/interface/news/ncp/data.d.json
    get方式,入参:

    {
    	mod:"province",
    	province(英文省名):""
    }
    
    • 1
    • 2
    • 3
    • 4

    该api可获取国内指定省份的疫情数据,字段我就不推断了,可自行根据上一个api和部分英文单词大概推断出来(没错!就是我不想打字了!太累了!ಥ_ಥ其实这还不算折磨人的,后面使用api的时候那才叫个曲折)。
    3、https://whois.pconline.com.cn/ipJson.jsp
    get方式,无入参。该api可获取使用者ip地址、省份、城市。返回结果如下:
    在这里插入图片描述

    数据使用

    刚开始开发的时候,我跟以前项目开发一样,跨域嘛,直接整个vue代理(不会vue代理模式的看这儿:https://blog.csdn.net/xi1213/article/details/125950052)不就完事儿了,果然,数据一经过代理,回来是回来了,但汉字全是\n什么什么鬼?乱码?费了一番功夫查了下,不是乱码,是unicode解码的问题。然后我又整了个解码的方法:

    //解码返回的unicode
    function decodingStr(str: any) {
      let repStr: any = str.replace(/\\/g, "%");//用%替换\
      let str1 = repStr.split("jsoncallback(")[1]
      let str2 = str1.split(");")[0]//截取出需要的字符串
      let unStr = unescape(str2);//解码出汉字
      let jsonObj = JSON.parse(unStr);//转换成json对象
      return jsonObj;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这下应该可以了吧?一切很顺利,开发差不多了,npm run build、git add . 、git commit -m""、git push,行云流水!直接上gitee Pages部署发布,完成!打开页面一看?嗯?f12。404?直到后面我又在网上扒拉后才明白,vue的代理在打包成dist后会被抽离失效,在gitee Pages中是不能使用vue的代理模式获取数据的!接下来就是各种尝试跨域,直到看到跨域两个字人都麻了。最后发现不同域下,使用jsonp的方式来处理跨域最为简单,jsonp原理和使用方法在这里:https://blog.csdn.net/xi1213/article/details/126827525?spm=1001.2014.3001.5502

    项目开始

    项目是vue3的,首先你得创建啊。这里建议使用vue脚手架的图形化界面创建项目,命令为:vue ui
    选择手动配置:
    在这里插入图片描述
    打开TypeScript支持:
    在这里插入图片描述
    选择vue3选项:
    在这里插入图片描述

    安装依赖

    1、npm install echarts@4.9.0(安装echarts的指定版本,因为项目中需要使用中国地图,在4.9.0之后echarts官方移除了地图支持,之后的版本需要下载chain.js,还得手动下载引入一遍,麻烦,这里直接用老版本)
    2、npm i element-plus(vue3对应的element-ui就是element-plus,这是官方使用文档:https://element-plus.gitee.io/zh-CN/guide/design.html )
    3、npm i three(看见首页那个大地球了吧?没错,它就是three.js做的,感兴趣的可以看这儿,我以前做的太阳系:https://blog.csdn.net/xi1213/article/details/125726054?spm=1001.2014.3001.5502
    4、npm i xlsx(这个是下载excel表格的必备插件,具体使用方法看这里:https://blog.csdn.net/xi1213/article/details/126216054?spm=1001.2014.3001.5502 )

    首页球体

    1、创建宇宙(叼不叼!是不是感觉自己就是创世主!( ̄_, ̄ )):初始化场景时一定记得设置alpha: true。这里创建宇宙我使用了我以前这篇文章创建背景的第三种方法:https://blog.csdn.net/xi1213/article/details/123244316?spm=1001.2014.3001.5502

    import * as THREE from "three";
    
    //初始化球体
    function init(data: any) {
      dom = document.getElementById("sphereDiv"); //获取dom
      let width = dom.clientWidth;
      let height = dom.clientHeight;
      scene = new THREE.Scene(); //场景场景
      camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); //创建透视相机(视场、长宽比、近面、远面)
      camera.position.set(0, 0, 270); //设置相机位置
      camera.lookAt(0, 0, 0);
      //创建渲染器
      renderer = new THREE.WebGLRenderer({
        antialias: true, //抗锯齿
        alpha: true, //透明
      });
      renderer.setClearColor(0x000000, 0.1); //设置场景透明度
      renderer.setSize(width, height); //设置渲染区域尺寸
      dom.appendChild(renderer.domElement); //将渲染器添加到dom中形成canvas
      createUniverse(); //创建宇宙
      createStars(); //创建星辰
      createLight(); //创建光源
      createSphere(data); //创建球体
      createOrbitControls();
      render();
    };
    
    //创建宇宙(球形宇宙)
    function createUniverse() {
      let universeGeometry = new THREE.SphereGeometry(500, 100, 100);
      let universeMaterial = new THREE.MeshLambertMaterial({
        //高光材质
        map: new THREE.TextureLoader().load(universeImg),
        side: THREE.DoubleSide, //双面显示
      });
      //宇宙网格
      let universeMesh = new THREE.Mesh(universeGeometry, universeMaterial);
      universeMesh.name = "宇宙";
      scene.add(universeMesh);
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    2、创建光源:为了效果,我使用了环境光与平行光源,这两种光都会影响贴图原本颜色,建议光源颜色设置为白色。

    //创建光源
    function createLight() {
      let lightColor = new THREE.Color(0xffffff);
      let ambient = new THREE.AmbientLight(lightColor); //环境光
      ambient.name = "环境光";
      scene.add(ambient);
      let directionalLight1 = new THREE.DirectionalLight(lightColor);
      directionalLight1.position.set(0, 0, 1000);
      scene.add(directionalLight1); //平行光源添加到场景中
      let directionalLight2 = new THREE.DirectionalLight(lightColor);
      directionalLight2.position.set(0, 0, -1000);
      scene.add(directionalLight2); //平行光源添加到场景中
      let directionalLight3 = new THREE.DirectionalLight(lightColor);
      directionalLight3.position.set(1000, 0, 0);
      scene.add(directionalLight3); //平行光源添加到场景中
      let directionalLight4 = new THREE.DirectionalLight(lightColor);
      directionalLight4.position.set(-1000, 0, 0);
      scene.add(directionalLight4); //平行光源添加到场景中
      let directionalLight5 = new THREE.DirectionalLight(lightColor);
      directionalLight5.position.set(0, 1000, 0);
      scene.add(directionalLight5); //平行光源添加到场景中
      let directionalLight6 = new THREE.DirectionalLight(lightColor);
      directionalLight6.position.set(0, -1000, 0);
      scene.add(directionalLight6); //平行光源添加到场景中
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3、创建球体:

    //创建球体
    function createSphere(data: any) {
      let earthSize = 100; //地球尺寸
      let earthGroup = new THREE.Group(); //地球的组
      let earthGeometry = new THREE.SphereGeometry(earthSize, 100, 100); //地球几何体
      let nightColor = new THREE.Color(0x999999);
      let dayColor = new THREE.Color(0x444444);
      //地球材质
      let earthMaterial = new THREE.MeshPhongMaterial({
        map: new THREE.TextureLoader().load(
          isDay ? earthImg : earthNightImg //区分昼夜纹理
        ),
        color: isDay ? dayColor : nightColor,
        // metalness: 1, //生锈的金属外观(MeshStandardMaterial材质时使用)
        // roughness: 0.5, // 材料的粗糙程度(MeshStandardMaterial材质时使用)
        normalScale: new THREE.Vector2(0, 5), //凹凸深度
        normalMap: new THREE.TextureLoader().load(normalImg), //法线贴图
      });
      let earthMesh = new THREE.Mesh(earthGeometry, earthMaterial); //地球网格
      earthMesh.name = "地球";
      earthGroup.add(earthMesh); //将地球网格添加到地球组中
      earthGroup.name = "地球组";
      scene.add(earthGroup);
      createVirus(data, earthSize); //创建球面病毒
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    放大项目中的地球你会发现球体表面是有凹凸而且反光的(就像稀泥巴一样≡(▔﹏▔)≡),这是因为使用了three中MeshPhongMaterial材质同时设置了属性normalScale与normalMap。
    在这里插入图片描述
    4、渲染:创建完成后记得渲染,否则是不会生效的。

    //渲染
    function render() {
      anId.value = requestAnimationFrame(render);
      document.getElementById("sphereDiv") &&
        document
          .getElementById("sphereDiv")!
          .addEventListener("mousemove", onMousemove, false);
    
      orbitControls.update(); //鼠标控件实时更新
      renderer.render(scene, camera);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    控制球体

    首页的3D球体是可以进行鼠标控制的。
    在这里插入图片描述
    这是使用的three.js自带的鼠标控件OrbitControls ,它的参数可以自己设置,这是鼠标控制的方法:

    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
    
    //创建鼠标控件
    function createOrbitControls() {
      orbitControls = new OrbitControls(camera, renderer.domElement);
      orbitControls.enablePan = false; //右键平移拖拽
      orbitControls.enableZoom = true; //鼠标缩放
      orbitControls.enableDamping = true; //滑动阻尼
      orbitControls.dampingFactor = 0.05; //(默认.25)
      orbitControls.minDistance = 150; //相机距离目标最小距离
      orbitControls.maxDistance = 500; //相机距离目标最大距离
      orbitControls.autoRotate = true; //自转(相机)
      orbitControls.autoRotateSpeed = 1; //自转速度
      orbitControls.enableRotate = true;//鼠标左键控制旋转
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    创建病毒

    被瞎说!covid19可不是我创造的!(;´д`)ゞ

    //创建病毒
    function createVirus(data: any, earthSize: any) {
      let colors = [
        new THREE.Color(0xf9b8b8),
        new THREE.Color(0xfe4242),
        new THREE.Color(0xff0000),
      ]; //病毒颜色列表
      let virSize = 4; //病毒大小
      let pointsGroup = new THREE.Group(); //点的组
      let list = JSON.parse(JSON.stringify(data));
      list.forEach((e: { value: number; color: any; position: any[]; }) => {
        e.value >= 10000000 && (e.color = colors[2]); //根据病毒数赋予不同颜色
        e.value >= 500000 && e.value < 10000000 && (e.color = colors[1]);
        e.value < 500000 && (e.color = colors[0]);
        if (e.position) {
          let virusMaterial = new THREE.SpriteMaterial({
            color: e.color,
            map: new THREE.TextureLoader().load(virusImg),
            side: THREE.FrontSide, //只显示前面
          }); //病毒材质
          let Sprite = new THREE.Sprite(virusMaterial); //点精灵材质
          Sprite.scale.set(virSize, virSize, 1); //点大小
          let lat = e.position[1]; //纬度
          let lon = e.position[0]; //经度
          let s = latLongToVector3(lat, lon, earthSize, 1); //坐标转换
          Sprite.position.set(s.x, s.y, s.z); //设置点的位置
          Sprite.dotData = e; //将点的数据添加到dotData属性中
          Sprite.name = "病毒";
          pointsGroup.add(Sprite); //添加进点的组中
        }
      });
      pointsGroup.name = "病毒组";
      scene.add(pointsGroup); //点的组添加到旋转组中
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    球面上那些红的、粉的、白的玩意儿就是病毒。其实就是利用three的SpriteMaterial材质加入点精灵Sprite中,再遍历病毒的坐标列表数据,循环设置Sprite的position属性,最后再将创建好的病毒组添加到宇宙场景中,值得注意的是,你一般获取到的数据均为经纬度坐标,需要转换为three能用的三维向量坐标,这是坐标转换的方法:

    //经纬度坐标变换(传入e:纬度、a经度、t球半径、o球额外距离)
    function latLongToVector3(e: any, a: any, t: any, o: any) {
      var r = (e * Math.PI) / 180,
        i = ((a - 180) * Math.PI) / 180,
        n = -(t + o) * Math.cos(r) * Math.cos(i),
        s = (t + o) * Math.sin(r),
        l = (t + o) * Math.cos(r) * Math.sin(i);
      return new THREE.Vector3(n, s, l); //计算三维向量
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    数值增加动画

    在首页右侧和“国内分析”右侧,有一排数字,那个数字在加载时是有数值增加动画的。
    在这里插入图片描述

    本来想直接使用vue-count-to或者vue-countupjs的,但网上我扒拉了一下,发现原理好像不是很难,无非就是利用vue的数据响应式原理,但我看到的大多封装的组件都是vue2的,vue3好像没有。这里我就整一个vue3+ts的版本:

    <template>
        <span :data-time="time" :data-value="value">{{addNum}}</span>
    </template>
    
    <script lang='ts' setup>
    import { ref, computed, watch, onMounted } from 'vue';
    let props = defineProps({
        //动画时间
        time: {
            type: Number,
            default: 2
        },
        //停止时的值
        value: {
            type: Number,
            default: 0
        },
        //千位的逗号
        thousandSign: {
            type: Boolean,
            default: () => false
        }
    }),
        oldValue: any = ref(0),
        addNum: any = ref(0);//响应式的数值
    
    watch(
        () => props.value,
        () => {
            startAnimation();//值改变时开始动画
        })
    
    function startAnimation() {
        let value: number = props.value - oldValue.value;
        let step = (value * 10) / (props.time * 100);
        let current = 0;
        let start = oldValue.value;
        //定时器
        let t: any = setInterval(() => {
            start += step;
            if (start > value) {
                clearInterval(t);
                start = value;
                t = null;
            }
            if (current === start) {
                return;
            }
            current = Math.floor(start);//取整
            oldValue.value = current;
            if (props.thousandSign) {
                addNum.value = current.toString().replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, '$1,');//添加千位符
            } else {
                addNum.value = current.toString();//无千位符
            }
        }, 10)
    }
    
    onMounted(() => {
        startAnimation();
    })
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    value是动画终止时数值,time是动画时间,thousandSign表示是否添加千分位符,必须保证value与time类型为Number,这是使用方法:

    <addNumber class="addcure-div" :value="addcure" :time="10" :thousandSign="true" />
    
    • 1

    表格展示

    页面上的所有表格都是使用的element-plus的表格组件,这是具体使用方法:https://element-plus.gitee.io/zh-CN/component/table.html
    当表格做出来之后发现样式颜色之类的并不是自己想要的。其实可以更改css变量。注意,不是全局修改,是局部修改样式。比如你想让表格变成这样透明的:
    在这里插入图片描述
    那么你可以这样做:

    <el-table :data="tabData" style="width: 100%;height: calc(100vh - 100px);
                --el-table-bg-color:rgba(0,0,0,.8);
                --el-table-tr-bg-color:transparent;
                --el-table-header-bg-color:#333;
                --el-table-header-text-color:#fff;
                --el-table-text-color:#fff;
                --el-table-row-hover-bg-color:#333;
                --el-table-border-color:#333">
                    <el-table-column type="index" label="序号" width="100" />
                    <el-table-column prop="name" label="国家" />
                    <el-table-column prop="value" label="累计数" sortable />
                    <el-table-column prop="deathNum" label="死亡数" sortable />
                    <el-table-column prop="cureNum" label="治愈数" sortable />
                    <el-table-column prop="citycode" label="地区代码" />
                    <el-table-column label="坐标">
                        <template #default="scope">{{ scope.row.position ? scope.row.position : "-" }}</template>
                    </el-table-column>
                </el-table>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中的css变量名,你可以f12获取到。

    横向柱状图

    首页左侧和“国内分析”这样的横向柱状图。
    在这里插入图片描述
    在echarts里面配置项是这样的:

    //柱状图数据
      let option: any = {
        title: {
          text: titName + sliceNum,
          left: "center",
          textStyle: {
            color: "#fff",
          },
        },
        tooltip: {
          backgroundColor: "rgba(0,0,0,.5)",
          borderWidth: "0",
          trigger: 'axis',
          textStyle: {
            color: "#fff",
            fontWeight: "bolder"
          },
        },
        grid: {
          top: "10%",
          left: "10%",
          right: "10%",
          bottom: "0%",
        },
        xAxis: {
          type: 'value',
          show: false,
        },
        yAxis: {
          type: 'category',
          axisLabel: {
            color: "#fff",
          },
          data: [],
        },
        series: [
          {
            data: [],
            type: 'bar',
            showBackground: true,
            backgroundStyle: {
              color: 'rgba(180, 180, 180, 0.2)'
            },
            itemStyle: {
              color: color,
            },
            label: {
              color: "#fff",
              fontWeight: "bolder",
              show: true,
              align: "left",
              formatter: "{c}",
            },
          }
        ]
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    颜色、位置、标题可自己设置。

    中国地图

    在“国内分析”中有一个中国地图,用来展示国内现存确诊分布,支持缩放拖拽。
    在这里插入图片描述

    如果你下载的最新的echarts的话,那么你得折腾下,自己另外单独找chain.js下载,然后引入项目,4.9.0是内置地图的,可以直接使用。这是地图的配置项:

      let option: any = {
        title: {
          text: '国内各省现存分布',
          left: "center",
          top: '1%',
          textStyle: {
            color: "#fff",
          },
        },
        visualMap: {
          min: 0,
          max: 500,
          left: '5%',
          bottom: '5%',
          text: ['高', '低'],
          textStyle: {
            color: '#fff',
          },
          calculable: true,
          inRange: {
            color: ['#fff', '#f00'],//颜色范围
          },
        },
        tooltip: {
          padding: 10,
          enterable: true,
          transitionDuration: 0,//动画时间
          backgroundColor: "rgb(0,0,0,.8)",
          borderRadius: 20,
          textStyle: {
            color: '#fff',
          },
          formatter: function (params: any) {
            let tipString = "";
            if (params.data.value) {
              tipString =
                "
    " + params.data.name + "
    "
    + "
    现存:" + params.data.value + "
    "
    + "
    累计:" + params.data.allNum + "
    "
    + "
    死亡:" + params.data.deathNum + "
    "
    + "
    治愈:" + params.data.cureNum + "
    "
    + "
    较昨日新增:" + params.data.asymptomNum + "
    "
    + "
    境外输入:" + params.data.jwsrNum + "
    "
    } return tipString; } }, series: [{ name: '接入医院数量', type: 'map', mapType: 'china', zoom: 1.2,//缩放 roam: true, scaleLimit: { min: 1.2,//缩放限制 max: 2 }, itemStyle: { normal: { label: { show: true } }, emphasis: { show: true, areaColor: '#6eb5ff',//鼠标滑过区域颜色 label: { show: true } } }, label: { normal: { //静态的时候展示样式 show: true, //是否显示地图省份得名称 textStyle: { color: "#000", fontSize: 12 } }, emphasis: { //动态展示的样式 color: '#fff', }, }, data: [] }] };
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    formatter也就是hover出来的信息框,可以自定义。

    历史分析

    “国内分析”是有个历史数据展示的折线图的,它是允许缩放调节的。
    在这里插入图片描述
    这是它的echarts配置项:

      let option: any = {
        // backgroundColor: "",
        grid: {
          // top: "15%",
          // left: "5%",
          // right: "5%",
          // bottom: "10%",
        },
        title: {
          text: '国内历史数据',
          left: "center",
          top: '5%',
          textStyle: {
            color: "#fff",
          },
        },
        tooltip: {
          backgroundColor: "rgba(0,0,0,.5)",
          borderWidth: "0",
          trigger: 'axis',
          textStyle: {
            color: "#fff",
            fontWeight: "bolder"
          },
          axisPointer: {
            type: 'cross'
          },
        },
        legend: {
          data: ['确诊数', '治愈数', '死亡数', '境外输入'],
          textStyle: {
            color: "#fff"
          },
          orient: "vertical",
          top: "15%",
          right: "2%"
        },
        xAxis: {
          data: lineData.map(function (item: any) {
            return item.ymd;
          }),
          textStyle: {
            color: "#fff"
          }
        },
        yAxis: {
          textStyle: {
            color: "#fff",
          },
        },
        dataZoom: [
          {
            startValue: ''
          },
          {
            type: 'inside'
          }
        ],
        series: [
          {
            name: '确诊数',
            type: 'line',
            lineStyle: {
              color: '#f4c25e'
            },
            itemStyle: {
              color: '#f4c25e'
            },
            data: lineData.map(function (item: any) {
              return item.cn_conNum;
            }),
            zlevel: 1,
            z: 1,
          },
          {
            name: '治愈数',
            type: 'line',
            lineStyle: {
              color: '#48c56b'
            },
            itemStyle: {
              color: '#48c56b'
            },
            data: lineData.map(function (item: any) {
              return item.cn_cureNum;
            }),
            zlevel: 1,
            z: 1,
          },
          {
            name: '死亡数',
            type: 'line',
            lineStyle: {
              color: '#f00'
            },
            itemStyle: {
              color: '#f00'
            },
            data: lineData.map(function (item: any) {
              return item.cn_deathNum;
            }),
            zlevel: 1,
            z: 1
          },
          {
            name: '境外输入',
            type: 'line',
            lineStyle: {
              color: '#8903ba'
            },
            itemStyle: {
              color: '#8903ba'
            },
            data: lineData.map(function (item: any) {
              return item.cn_jwsrNum;
            }),
            zlevel: 1,
            z: 1
          }
        ]
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121

    表格下载

    主要是利用xlsx插件,好用得很,直接传入数据就出表格了,这是我的使用方法:

    import * as XLSX from "xlsx";
    
    //入参示例
    let eg = {
        fileName: "测试",//文件名
        tabHead: ["国家", "人口", "测试"],//表头列表
        keyList: ["name", "population", "test"],//表头对应的属性名,顺序必须与表头对应
        tabData: [
            { name: "中国", population: "11", test: "t1" },
            { name: "美国", population: "22", test: "t2" },
            { name: "日本", population: "35", test: "t3" }
        ]//对象数组
    }
    
    //导出数据表格
    export default async function downloadXlsx(tabObj: any) {
        let aoaList: any = [];
        aoaList[0] = tabObj.tabHead; //赋值表头列表
        tabObj.tabData.forEach((tabItem: any, tabIndex: number) => {
            aoaList[tabIndex + 1] = [];//该二维度数组必须多加一个元素,因为表头占第一个元素
            tabObj.keyList.forEach((keyItem: any, keyIndex: number) => {
                let val = tabItem[keyItem];//获取表格属性的值
                ((typeof val == "undefined") || (val == "")) ?
                    (aoaList[tabIndex + 1][keyIndex] = "-") ://数据未定义或者为空则用"-"代替
                    (aoaList[tabIndex + 1][keyIndex] = val + "");//添加空字符串,防类型为非字符串
            })
        });
        let workSheet = null;
        workSheet = XLSX.utils.aoa_to_sheet(aoaList); //将列表数据添加到工作表
        let workBook = XLSX.utils.book_new(); //创建一个工作薄
        XLSX.utils.book_append_sheet(workBook, workSheet, "1"); //将工作表添加到工作薄中
        await XLSX.writeFile(workBook, tabObj.fileName + ".xlsx"); //写入文件,下载工作薄
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    结语

    我做这个其实还是学到了很多东西,主要是vue2与vue3的区别。以前的this算是可以彻底抛弃了,还有就是组合式api配合setup语法糖,爽啊!就连组件引入后都不用注册了,直接使用。不过也有注意点,用ref()声明的响应式变量使用时需要加.vaule。至于ts,我感觉自己还是很菜啊,一直any一直爽。。。。。。ts类型系统直接被我无视了,后面还得观摩观摩其他大佬咋写的。截止到现在2022.9.13成都疫情好转要复工了,项目其实都是没有完成的,主要还差“省内分析”和“下载当地疫情报告”,我做了alert提示,反正数据获取到了,后面在搞,我项目是完全开源了的,有牛逼的可以直接clone下来开发完成(我想要白嫖!(╯▔皿▔)╯)。
    在这里插入图片描述
    在这里插入图片描述
    这篇文章我只在csdn和博客园发布过,最后还有句话,我知道某些人,某些网站,会直接把我文章扒过去,我不是不允许你们扒,你们至少得留个原作者和来源吧?!(ノ`Д)ノ(ノ`Д)ノ(ノ`Д)ノ

  • 相关阅读:
    面试宝典-Mysql篇
    MySQL的内置函数
    Go 怎么操作 OSS 阿里云对象存储
    JDK常用内部类
    【系统架构设计】计算机公共基础知识: 4 数据库系统
    青云霍秉杰:一文读懂Prometheus长期存储主流方案
    【JAVA多线程】AQS,JAVA并发包的核心
    SQL创建与删除索引
    10.21 - 每日一题 - 408
    力扣 # 27. 移除元素 JAVA实现
  • 原文地址:https://blog.csdn.net/xi1213/article/details/126824752