從0開始搭建一個疫情地圖小程序——小程序篇

爲什麼選小程序

主要還是服務器的費用的問題,小程序開發結合雲服務器可以省掉很多運維的問題,加上我這次開發主要是爲了熟悉小程序的組件開發與echart在小程序繪製地圖的性能表現。

前端頁面展示

首頁

在這裏插入圖片描述

切換頁面

在這裏插入圖片描述

各地詳細頁面

在這裏插入圖片描述

源代碼

前端實現

這裏並不全部講前端的代碼,主要講我在開發的時候遇到的坑。

組件開發

拿我首頁上,每天數據的顯示,我將它封裝爲一個組件。
在這裏插入圖片描述
和VUE框架有很多的相同點,比如要使用的時候,需要聲明引用這個組件
不過小程序是在引用的文件對應的JSON文件下配置:
在這裏插入圖片描述
還需要在組件的對應的JSON文件聲明這個是一個組件
在這裏插入圖片描述
當然組件也可以引用其他組件,在usingComponents這個對象下,引用即可。
在這裏插入圖片描述

插槽

和VUE框架差不多,直接看小程序的文檔實例
在這裏插入圖片描述

wxss注意事項

在組件wxss中不應使用ID選擇器、屬性選擇器和標籤名選擇器。

component的構造函數

微信官方文檔

基本理解了生命週期、組件之間通信等,基本就可以完成大部分的組件開發,這一部分建議閱讀微信開發文檔學習。

每日疫情數據變化組件開發

wxml

在這裏插入圖片描述
通過綁定incrData數據進行顯示

js

// components/dailyData.js
Component({
  /**
   * 組件的屬性列表
   */
  properties: {
    // 傳遞過來的數據
    dailyTextContent:{
      type:Object
    }
  },
  // 監聽數據變化
  observers:{
    dailyTextContent: function (dailyTextContent){
    // 這裏相當於是入口,需要進行錯誤判斷,如果傳入的數據格式錯誤,應該提示使用
    try{
          // 更新時間
      this.getDate(dailyTextContent.updated_at)
      // 更新數據
      this.setIncr(dailyTextContent)
    }catch(e){
		console.log('daily commponet 傳入格式錯誤',e)
	}

    }
  },
  data: {
    updateTime:'',
    incrData:{
      curedIncr: '',
      deadIncr: '',
      seriousIncr: '',
      suspectedIncr: '',
      confirmedIncr: ''
    }
  },
  
  /**
   * 組件的方法列表
   */
  methods: {
    // 設置數據
    setIncr(result){
      let data = this.getIncr(result)
      for (let [key, value] of Object.entries(data)){
        if(value > 0) {
          data[key] = `+${value}`
        }else {
          data[key] = `-${value}`
        }
      }
      this.setData({
        incrData:data
      })
    },
    // 獲取需要的數據
    getIncr(result){
      let { curedIncr,
        deadIncr,
        seriousIncr,
        suspectedIncr,
        confirmedIncr } = result
      return {
        curedIncr,
        deadIncr,
        seriousIncr,
        suspectedIncr,
        confirmedIncr
      }
    },
    // 獲取時間格式
    getDate(time){
      let date = new Date()
      date.setTime(time)
      this.setData({
        updateTime: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
      })
      // return `${date.getYear()}-${date.getMount() + 1}-${date.getDate()}`
    }
  }
})

首頁使用

<!-- index.wxml -->
<view class="container">
  <view class="textContent">
    <daily-display dailyTextContent='{{dailyTextContent}}'></daily-display>
  </view>
</view>
Page({
	data:{
	   dailyTextContent: {},
	},
	onLoad(){
		let dailyData = {}
		this.setData({
			dailyTextContent : dailyData// 請求到的數據
		})
	}
})

使用echartjs

echartjs有一個官方的小程序demo,爲了適配小程序的框架,echartjs也封裝了一個組件供開發者使用。

echartjs小程序版本下載

下載好之後,複製到小程序目錄,然後在使用的頁面去使用這個組件就可以
在這裏插入圖片描述
使用ec-canvas組件
在這裏插入圖片描述
在這裏插入圖片描述
使用很簡單,只需要綁定3個屬性,組件id,canvasidechartjs一系列操作的繪製對象。
有一個問題是,官方的組件所綁定的ec是同步的,所以如果是數據請求的這種異步操作會導致無法顯示數據。
所以在綁定的ec需要設置lazyLoad取消這個即時繪製。
在這裏插入圖片描述

ec的onInit函數

這個相當於整個echart繪製的入口,我們只需要在這個函數中,聲明我們需要進行的繪製操作就可以繪製出我們需要的畫面。這個我在獲取數據之後再進行詳細描述。

如何進行異步渲染?

看了下ec-canvas組件,理解了這個組件運行的順序。由於繪製地圖需要一些地圖的數據,所以我在ec-cavnas組件增加了變量保存這些數據。並且新增一些方法去修改這些數據,刷新頁面和顯示/隱藏頁面的方法。
在這裏插入圖片描述

獲取mapData

    // 處理 mapData dataFormate是我提前設計好的一種格式
    // 封裝到utils.js文件中
    mapData = utils.handleData(dataDaily, dataFormate);
dataFormate

module.exports = [
    { en_name: 'guangdong', name: '廣東',long_name:'廣東省' },
    { en_name: 'beijing', name: '北京',long_name:'北京市' },
    { en_name: 'tianjin', name: '天津' ,long_name:'天津市'},
    { en_name: 'hebei', name: '河北',long_name:'河北省' },
    { en_name: 'liaoning', name: '遼寧',long_name:'遼寧省' },
    { en_name: 'jilin', name: '吉林',long_name:'吉林省' },
    { en_name: 'heilongjiang', name: '黑龍江',long_name:'黑龍江省' },
    { en_name: 'shandong', name: '山東',long_name:'山東省' },
    { en_name: 'jiangsu', name: '江蘇' ,long_name:'江蘇省'},
    { en_name: 'shanghai', name: '上海',long_name:'上海市' },
    { en_name: 'zhejiang', name: '浙江' ,long_name:'浙江省'},
    { en_name: 'anhui', name: '安徽',long_name:'安徽省' },
    { en_name: 'fujian', name: '福建' ,long_name:'福建省'},
    { en_name: 'jiangxi', name: '江西' ,long_name:'江西省'},
    { en_name: 'guangxi', name: '廣西' ,long_name:'廣西壯族自治區'},
    { en_name: 'hainan', name: '海南' ,long_name:'海南省'},
    { en_name: 'henan', name: '河南' ,long_name:'河南省'},
    { en_name: 'hunan', name: '湖南',long_name:'湖南省' },
    { en_name: 'shanxi2', name: '陝西',long_name:'陝西省' },
    { en_name: 'hubei', name: '湖北',long_name:'河北省' },
    { en_name: 'shanxi1', name: '山西',long_name:'山西省' },
    { en_name: 'neimenggu', name: '內蒙古' ,long_name:'內蒙古自治區'},
    { en_name: 'ningxia', name: '寧夏',long_name:'寧夏回族自治區' },
    { en_name: 'qinghai', name: '青海' ,long_name:'青海省'},
    { en_name: 'gansu', name: '甘肅',long_name:'甘肅省' },
    { en_name: 'xinjiang', name: '新疆',long_name:'新疆維吾爾自治區' },
    { en_name: 'sichuan', name: '四川',long_name:'四川省' },
    { en_name: 'guizhou', name: '貴州',long_name:'貴州省' },
    { en_name: 'yunnan', name: '雲南' ,long_name:'雲南省'},
    { en_name: 'chongqing', name: '重慶' ,long_name:'重慶市'},
    { en_name: 'xizang', name: '西藏',long_name:'西藏自治區' },
    { en_name: 'hongkong', name: '香港' ,long_name:'香港特別行政區'},
    { en_name: 'aomen', name: '澳門',long_name:'澳門特別行政區' },
    { en_name: 'taiwan', name: '臺灣',long_name:'臺灣省' }
  ]
dataDaily數據

全國各省各市的病例數據


module.exports = [
{ "provinceName": "湖北省",
 "provinceShortName": "湖北",
 "currentConfirmedCount": 19486, 
 "confirmedCount": 67707, 
 "suspectedCount": 0, 
 "curedCount": 45235,
  "deadCount": 2986, 
  "comment": "", 
  "locationId": 420000, 
  "statisticsData": "https://file1.dxycdn.com/2020/0223/618/3398299751673487511-135.json", 
  "cities": [{ "cityName": "武漢",
  "currentConfirmedCount": 17565, 
  "confirmedCount": 49912, 
  "suspectedCount": 0, "curedCount": 29977, "deadCount": 2370, "locationId": 420100 }, 
  { "cityName": "孝感", "currentConfirmedCount": 369, "confirmedCount": 3518, "suspectedCount": 0, "curedCount": 3024, "deadCount": 125, "locationId": 420900 },
  { "cityName": "鄂州", "currentConfirmedCount": 347, "confirmedCount": 1394, "suspectedCount": 0, "curedCount": 993, "deadCount": 54, "locationId": 420700 },
 ……………………………………
 "https://file1.dxycdn.com/2020/0223/353/3398299755968039885-135.json", "cities": [{ "cityName": "拉薩", "currentConfirmedCount": 0, "confirmedCount": 1, "suspectedCount": 0, "curedCount": 1, "deadCount": 0, "locationId": 540100 }] }]

handleData()
    // getTextName 獲取對應的城市名字
function getTextName(dataFormate, data) {
  for (let item of dataFormate) {
    if (data.provinceShortName === item.name) {
    // 如果城市名稱一樣
      return {
        name: `${item.name}`,
        en_name: item.en_name
      };
    }
  }
  return false;
}
 handleData(dataDaily, dataFormate) {
    let res = [];
    for (let item of dataDaily) {

      let { name, en_name } = getTextName(dataFormate, item);
      if (name) {
        let data = item;
        let cities = [];
        // 由於臺灣 澳門 香港 上海等沒有市級地區 所以自身就是一個城市
        if (data.cities.length < 1) {
          cities.push({
            cityName: data.provinceShortName,
            confirmedCount: data.confirmedCount,
            curedCount: data.curedCount,
            deadCount: data.deadCount,
            locationId: data.locationId,
            suspectedCount: data.suspectedCount
          });
        } else {
          cities = data.cities;
        }
        // 地圖繪製的數據
        res.push({
          name,
          key: en_name,
          value: data.confirmedCount,
          cities: cities
        });
      }
    }
    return res;
  },
輸出結果

在這裏插入圖片描述

erchatjs

在開發的時候,我把關於echartjs相關的樣式操作,統一封裝到一個chart.js文件中去。
在前文中,我們知道了首頁綁定的ec中的onInit變量是一個回調函數,我們所有關於繪製的操作都在這個函數裏面封裝。
所以在我們封裝的chartjs文件中,最重要的函數也是這個。
這裏面的函數主要都是一些api的調用與樣式的option的修改,我都會在註釋一些說明,大家只要熟悉一些echartjs的使用,就可以看的明白了。
注意:我修改了ec-canvas的組件中的init的原方法:
在這裏插入圖片描述
我修改的方法,使用的是我們之前保存的mapData數據,裏面保存了所有省市的病例數據。
在這裏插入圖片描述

// 地圖 svg數據
import geoJson from "./mapData";
// api 文件
import echarts from "../ec-canvas/echarts";
module.exports = {
  // 繪製操作 seriesData這個參數是我修改了ec-canvas組件  裏面保存了所有省市的病例數據
  initChart: function(canvas, width, height, seriesData) {
  // 初始化echart
    const chart = echarts.init(canvas, null, {
      width: width,
      height: height
    });
    // 綁定canvas
    canvas.setChart(chart);
    // 繪製地圖
    echarts.registerMap("china", geoJson);
    // 這裏是map的option的數據顯示
    const option = {
    // 點擊顯示的 浮動框
      tooltip: {
        // 下面代碼
      },
      // 地圖value的分級與樣式
      visualMap: {
      	// 下面代碼
      },
      textStyle: {
        color: "#ecf7f7"
      },
	// 地圖樣式
      series: [
        {
          type: "map",
          mapType: "china",
          label: {
            show: true,
            emphasis: {
              textStyle: {
                color: "#fff",
                fontSize: 8
              }
            },
            textStyle: {
              color: "#000",
              fontSize: 8
            },
          },
          itemStyle: {
            normal: {
              borderColor: "#AAAAAA",
              areaColor: "#fff"
              //   fontSize:8,
            },
            emphasis: {
              areaColor: "#0000AA",
              borderWidth: 0
              //   fontSize:8,
            }
          },
          animation: false,
		// 數據綁定
          data: seriesData
        }
      ]
    };
    // 將option設置到echart中去
    chart.setOption(option);
    return chart;
  }
};

點擊浮動框
      tooltip: {
        trigger: "item",
        formatter:function(obj){
            return `${obj.data.name} 確診人數:${obj.data.value}`
        },
        textStyle:{
            fontWeight:'bold',
            width:'30px'
        },
        // 自適應點擊位置,避免因爲數據過長導致看不全浮動框
        position:function(point,dom,rect,size){
            let windowWidth =wx.getSystemInfoSync().windowWidth
            if(point[0]> windowWidth /2 ){
                return [parseInt(point[0]-size.width),point[1]]
            }else {
                return [point[0],point[1]]
            }
        }
      },

在這裏插入圖片描述

地圖分級樣式
  	valueMap:{
  		min: 0,
        max: 34000,
        // splitNumber: 5,
        top: "top",
        pieces: [
          { min: 5001 }, // 不指定 max,表示 max 爲無限大(Infinity)。
          { min: 3001, max: 5000 },
          { min: 1001, max: 3000 },
          { min: 101, max: 1000 },
          { min: 11, max: 100 },
          // {value: 123, label: '123(自定義特殊顏色)', color: 'grey'}, // 表示 value 等於 123 的情況。
          { max: 10 } // 不指定 min,表示 min 爲無限大(-Infinity)。
        ],
        // 顏色會自動匹配
        color: [
          "#c0e3e4",
          "#92bfc7",
          "#639aa9",
          "#35768c",
          "#06516e"
        ].reverse(),
       	// 字體樣式
        textStyle: {
          color: "#000",
          fontSize: 12
        }
  	}

在這裏插入圖片描述

canvas的層級問題

由於小程序中的canvas是原生的,微信提供的標籤是在真機上無法覆蓋canvas的。
爲了解決這個問題,我們需要使用微信官方提供的cover-view
在這裏插入圖片描述
原理是底層覆蓋了一個黑色透明的遮罩層,在覆蓋一個顯示區域即可。主要區別是使用cover-view替換所有的view
定位直接使用fixed就可以實現下面的效果
在這裏插入圖片描述

全國各地數據:手風琴

這個使用的是一個小程序的組件庫,由於這些組件庫是可以按需引入的,所以這個大家自己去找自己需要的組件,直接複製到小程序目錄就可以使用了。
這個是我使用的小程序ui庫
iView是TalkingData發佈的一款高質量的基於Vue.js組件庫,而iView weapp則是它們的小程序版本。
GitHub地址
npm下載:npm i iview-weapp
在這裏插入圖片描述

總結

這個項目大概花了3天左右的時間開發,雖然由於微信官方的原因,導致無法上線我覺得是挺可惜的。因爲我後面都已經打算繼續把國外的疫情地圖,根據用戶定位結合騰訊的地圖定位去做一個顯示用戶當地的疫情地圖這2個功能的,可惜沒有通過,導致沒有動力做下去。

在這次開發中,主要還是學習到了小程序的組件開發和echartjs中的小程序的用法和api的使用,還是感嘆這個echartjs的強大之處,就是非常容易上手,功能豐富。也不知道未來能不能參與這種庫的開發。

最近也在公司實習,學習前端也有2年多了,感覺自己還是有很多模糊的概念,很多時候都在想程序員的終點是什麼? 在看完雷軍的大學時期的經歷和一些文章,其實也感覺的到程序員的終點也是交互設計工程師。

如果只是一個提供技術力的程序員是活不長久的。

在這次實習的時候,公司安排我一個人負責一個內部的設備管理系統,由於第一次做這種比較完整的項目,雖然這種系統並不複雜,但是在開發到後面的時候,你永遠不知道會有什麼需求會出現,所以如果我們設計的頁面比較死板的話,就會導致後期的需求開發週期變長和變難。

無論多複雜的系統都是從最簡單的開始的,都是一磚一石搭建出來的,如果你覺得你能夠在產品開發前就已經設計好一個很完美的系統,我覺得是不可能的。因爲每一個系統的需求都會因爲它面對的用戶而變得不同。(也有可能是我太菜了)

像這個疫情地圖,從一開始只有一個頁面,到後面的組件,代碼的複雜度越來越大,但是我的目錄文件和代碼管理都不是很清晰。我感覺是當局者迷,過幾天反思就會發現項目中的問題。

還有幾個月就畢業了,祝大家能夠獲得自己心儀的工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章