Ts+vue3疫情可视化项目

疫情可视化

申明:学习自B站up:小满zs(https://space.bilibili.com/99210573),仅记录一下学习笔记

数据分析

数据地址:实时更新:新冠肺炎疫情最新动态 (qq.com)

在控制台网络中查看Fetch/XHR里的list 即为疫情数据

初始化项目

服务器端

npm install ts-node -g
npm init -y
npm install @types/node -D  //node 申明文件
npm install express -S
npm install @types/express -D  //express 申明文件
npm install axios -S

Index.ts

import express,{Express,Router,Request,Response} from 'express'
import axios from 'axios'
const app:Express = express()
// 设置跨域允许
app.use('*',(req,res,next) => {
    res.header('Access-Control-Allow-Origin','*')
    next();
})

const router:Router = express.Router()
app.use('/api',router)

router.get('/list',async (req:Request, res:Response) => {
    const result = await axios.post('https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=localCityNCOVDataList,diseaseh5Shelf')
    res.json({
        data:result.data
    })
})

app.listen(3333,()=>{
    console.log('server listening on 3333!')
})

疫情数据接口:http://localhost:3333/api/list(个人的)

添加运行脚本

前端

安装库

npm init vue@latest

npm install

要同时安装less和lessload,其实在vite中只需要安装less即可,另一个可以不用安装,vite官网有说明.sass同理

npm install less

1.基本样式:App.vue

<script setup lang="ts">
// 由于assets文件夹编译后会不见,因此用import
import bg from "./assets/bg.jpg"
</script>

<template>
  <div class="box" :style="{background:`url(${bg})`}">
    <div class="box-left"></div>
    <div class="box-center"></div>
    <div class="box-right"></div>
  </div>
</template>

<style lang="less">
* {
  padding: 0px;
  margin: 0px;
}

html,
body,
#app {
  height: 100%;
  overflow: hidden;
}

.box {
  height: 100%;
  display: flex;
  overflow: hidden;

  &-left {
    width: 400px;
    height: 100%;
  }

  &-center {
    flex: 1;
  }

  &-right {
    width: 400px;
    height: 100%;
  }
}
</style>

2.安装axios 创建文件夹

npm install axios

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruvbnK81-1666075107508)(C:\Users\Johnny\AppData\Roaming\Typora\typora-user-images\image-20221017162351001.png)]

import axios from 'axios'

const server = axios.create({
    baseURL: "http://localhost:3333",
})

export const getApiList = () => server.get('/api/list').then(res => res.data)

3.修改stores文件夹

在这里插入图片描述

import { defineStore } from 'pinia'
import {getApiList} from '../server'

export const useStore = defineStore({
  id:'counter',
  state:()=>({
    list:{},
  }),
  actions:{
    async getList(){
      const result = await getApiList()
      console.log(result)      
    }
  }
})

3.在app.vue调用

import {useStore} from "./stores"
const store = useStore();
store.getList()

绘制地图

前期准备

npm install echarts --save-dev

引入china.js包

请添加图片描述
包我放在了源代码里,可自行在最下面源代码里获取

新建geoMap.ts:

export const geoCoordMap: Record<string, Array<number>> = {
    
    
    '台湾': [121, 23],
    '黑龙江': [127, 48],
    '内蒙古': [110.3467, 41.4899],
    "吉林": [125.8154, 44.2584],
    '北京': [116.4551, 40.2539],
    "辽宁": [123.1238, 42.1216],
    "河北": [114.4995, 38.1006],
    "天津": [117.4219, 39.4189],
    "山西": [112.3352, 37.9413],
    "陕西": [109.1162, 34.2004],
    "甘肃": [103.5901, 36.3043],
    "宁夏": [106.3586, 38.1775],
    "青海": [99.4038, 36.8207],
    "新疆": [87.9236, 43.5883],
    "西藏": [88.388277, 31.56375],
    "四川": [103.9526, 30.7617],
    "重庆": [108.384366, 30.439702],
    "山东": [117.1582, 36.8701],
    "河南": [113.4668, 34.6234],
    "江苏": [118.8062, 31.9208],
    "安徽": [117.29, 32.0581],
    "湖北": [114.3896, 30.6628],
    "浙江": [119.5313, 29.8773],
    "福建": [119.4543, 25.9222],
    "江西": [116.0046, 28.6633],
    "湖南": [113.0823, 28.2568],
    "贵州": [106.6992, 26.7682],
    "云南": [102.9199, 25.4663],
    "广东": [113.12244, 23.009505],
    "广西": [108.479, 23.1152],
    "海南": [110.3893, 19.8516],
    '上海': [121.4648, 31.2891],
    '香港': [114.30, 22.9],
    '澳门': [113.5, 22.2]
  }; 

App.vue:绘制地图

<script setup lang="ts">
// 由于assets文件夹编译后会不见,因此用import
import {onMounted } from "vue"
import bg from "./assets/bg.jpg"
import {useStore} from "./stores"
import * as echarts from "echarts"
import './assets/china'
import {geoCoordMap} from "./assets/geoMap"
import type {Children} from "./stores/types"

onMounted(async()=>{
  const store = useStore();
  //处理数据
  await store.getList()
  const city = store.list.diseaseh5Shelf.areaTree[0].children
  const data  = city.map((v:Children)=>{
    return {name: v.name,value:geoCoordMap[v.name].concat(v.total.confirm)}
  })
  const chart = echarts.init(document.querySelector('#china') as HTMLElement)
  //生成地图
  chart.setOption({
    geo: {
          map: "china",
          aspectScale: 0.8,
          layoutCenter: ["50%", "50%"],
          layoutSize: "120%",
          itemStyle: {
            normal: {
              areaColor: {
                type: "linear-gradient",
                x: 0,
                y: 1200,
                x2: 1000,
                y2: 0,
                colorStops: [
                  {
                    offset: 0,
                    color: "#152E6E", // 0% 处的颜色
                  },
                  {
                    offset: 1,
                    color: "#0673AD", // 50% 处的颜色
                  },
                ],
                global: true, // 缺省为 false
              },
              shadowColor: "#0f5d9d",
              shadowOffsetX: 0,
              shadowOffsetY: 15,
              opacity: 0.5,
            },
            emphasis: {
              areaColor: "#0f5d9d",
            },
          },

          regions: [
            {
              name: "南海诸岛",
              itemStyle: {
                areaColor: "rgba(0, 10, 52, 1)",
                borderColor: "rgba(0, 10, 52, 1)",
                // normal: {
                  opacity: 0,
                  label: {
                    show: true,
                    color: "#009cc9",
                  },
                // },
              },
              label: {
                show: false,
                color: "#FFFFFF",
                fontSize: 12,
              },
            },
          ],
        },
        series: [
          {
            type: "map",
            // selectedMode: "multiple",
            mapType: "china",
            aspectScale: 0.8,
            layoutCenter: ["50%", "50%"], //地图位置
            layoutSize: "120%",
            zoom: 1, //当前视角的缩放比例
            // roam: true, //是否开启平游或缩放
            scaleLimit: {
              //滚轮缩放的极限控制
              min: 1,
              max: 2,
            },
            label: {
              show: false,
              color: "#FFFFFF",
              fontSize: 16,
            },
            itemStyle: {
              areaColor: "#0c3653",
              borderColor: "#1cccff",
              borderWidth: 1.8,
              emphasis: {
                areaColor: "#56b1da",
                label: {
                  show: true,
                  color: "#fff",
                },
              },
            },
            data: data,
          },
          {
              name: 'Top 5',
              type: 'scatter',
              coordinateSystem: 'geo',
              symbol: 'pin',
              symbolSize: [45,45],
              symbolOffset:[0, '-40%'] ,
              label: {
                show: true,
                // 修改pin起泡展示的数据
                formatter(v:any){
                  // console.log(v)
                  return v.data.value[2]
                },
              },
              itemStyle: {
                  // normal: {
                      color: '#73d13d', //标志颜色
                  // }
              },
              data: data,
              showEffectOn: 'render',
              rippleEffect: {
                  brushType: 'stroke'
              },
              zlevel: 1
          },
        ],
  })
})
</script>

解决stores 空对象全局变量的报错问题

使用vscode插件json2ts,将数据放入index.json后 ctrl + alt + v,即可根据数据自动生成接口

然后在stroes文件夹里引入,即可解决Typescript报错问题,因为如果定义空对象,会默认下面没有其他属性而报错。

绘制表格

1.修改store

添加childrenList数据,用于存储列表数据

2.点击事件

为图表添加监听事件,点击打印相关数据,这里点击将store的childrenLIst进行修改,后续用于列表的数据

  chart.on('click',(event:any)=>{
    
    
    // console.log(city)
    console.log(event)
    store.childrenList = event.data.source.children
    console.log(store.childrenList)
  }) 

3.绘制表格

html:

      <table id="customers">
        <tr>
          <th>名称</th>
          <th>治愈</th>
          <th>确诊</th>
          <th>本土确诊</th>
        </tr>
        <tr v-for="(item,index) in store.childrenList" :key="index">
          <td>{
   
   {item.name}}</td>
          <td>{
   
   {item.total.confirm}}</td>
          <td>{
   
   {item.today.confirm}}</td>
          <td>{
   
   {item.today.local_confirm_add}}</td>
        </tr>
      </table>

css:

#customers {
  font-family: Arial, Helvetica, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

#customers td, #customers th {
  border: 1px solid #ddd;
  padding: 8px;
}

#customers tr:nth-child(even){background-color: #f2f2f2;}

#customers tr:hover {background-color: #ddd;}
#customers tr {background-color: #ddd;text-align: center}
#customers th {
  padding-top: 12px;
  padding-bottom: 12px;
  text-align: left;
  background-color: #4CAF50;
  // color: rgb(255, 255, 255);
}

4.添加动画

安装animate.css: npm install animate.css -S

引入:import 'animate.css'

设置群体动画一定要设置key,且唯一,因为index重复后,有的动画不会展示,因此使用uuid解决唯一值

assets文件夹新建getUUID.ts:

export function getUuid () {
  if (typeof crypto === 'object') {
    if (typeof crypto.randomUUID === 'function') {
      return crypto.randomUUID();
    }
    if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
      const callback = (c) => {
        const num = Number(c);
        return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
      };
      return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, callback);
    }
  }
  let timestamp = new Date().getTime();
  let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    let random = Math.random() * 16;
    if (timestamp > 0) {
      random = (timestamp + random) % 16 | 0;
      timestamp = Math.floor(timestamp / 16);
    } else {
      random = (perforNow + random) % 16 | 0;
      perforNow = Math.floor(perforNow / 16);
    }
    return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
  });
};

引入:import {getUuid} from "./assets/getUUID"

修改点击事件:

  chart.on('click',(event:any)=>{
    
    
    // console.log(city)
    // console.log(event)
    event.data.source.children.map((child:any)=>{
    
    
      child['index']= getUuid()
    })
    store.childrenList = event.data.source.children
    // console.log(store.childrenList)
  }) 

html:

        <transition-group enter-active-class="animate__animated animate__backInRight" tag="tbody">
          <tr v-for="(item) in store.childrenList" :key="item.index">
            <td>{
   
   {item.name}}</td>
            <td>{
   
   {item.total.confirm}}</td>
            <td>{
   
   {item.today.confirm}}</td>
            <td>{
   
   {item.today.local_confirm_add}}</td>
          </tr>
        </transition-group>

其他和源代码

其他图表实现原理一样,因此就不展示了,走了一遍流程。

源代码

猜你喜欢

转载自blog.csdn.net/qq_41003479/article/details/127388244
今日推荐