zoukankan      html  css  js  c++  java
  • 结对第二次作业——某次疫情统计可视化的实现

    这个作业属于哪个课程 2020春|S班
    这个作业要求在哪里 第二次结对作业要求
    结对学号 221701231 221701204
    这个作业的目标 结合寒假第二次作业的成果实现原型设计中的功能
    作业正文 我的第二次结对作业
    其他参考文献 《构建之法》

    一.Github 仓库地址和代码规范链接

    二.成品演示

    0.云服务器: http://radishbear.top/InfectStatisticWeb/#/

    1.选取不同日期查看截止该日全国疫情情况,全国地图上使用不同的颜色代表大概确诊人数区间

    2.点击省份后,右边的小图会显示该省的疫情概览

    3.同时下方会显示更加详细的数据

    4.边上是疫情焦点

    ### 5.下端的滚动条,自动翻滚各种疫情期间的注意事项

    三.结对讨论

    原型设计之后我们对项目的架构进行了讨论,最后决定使用Vue+php+MySQL的前后端分离的架构。

    ####搭建静态页面主要工作是:用vue+echarts+element-ui搭建起一个页面的基本模板,其中的数据部分先写死或者用空数据。

    四.设计实现过程:

    0.整体构架 Vue+php+MySQL的前后端分离的架构

    1.概述

      设计好系统架构后的下一阶段,我们进行了简单的分工:林羽希同学主要负责获取数据和构造php,朱鸿昊同学主要负责搭建静态页面。
      简单的分工阶段结束后我们开始一起实现前后端的数据交互。从php数组到json,再从json到JavaScript数组,期间我们要不断调整发送数据与解析数据的格式,让前后端能够完美对接。有一种飞船与空间站对接的感觉,两边的工作都准备好了,但是对接过程还是要不断微调。这一部分的代码实现也将在下文提及。
    ​  完成前后端数据交互后我们的工作就接近尾声了,我们对稍微成型的系统进行了一下测试和优化,并进行了一些补缺补漏。这期间有遇上些或小或大的bug,我们也选取了比较有代表性的在下文进行分析。

    2.框架与部件库:

      此次项目的前端部分是基于vue框架进行开发的。项目整体是用Vue-CLI构建起来的。前端部分采用Vue框架。主要出于两点考虑:1.Vue的模块化思想与多模块的原型相契合。2.Vue的事件总线机制让模块间通信变得快捷且易实现。3.Vue框架的全局变量那个很快速解决数据寄存的问题。

      因为页面图表较多,所以我们使用了Echarts组件库。除此之外,页面里几乎所有组件都是来自于ELement-ui,具体包括卡片、时间选择器、按钮、表格、提示框、弹窗。

    3.分析:

    3.1组件:

      在项目原型中我们使用了各种图表和一些较复杂的样式组件。所以我们在项目中引入了Echarts和Element-ui。而根据vue的思想,我们把这些小组件拼装成大组件,再把大组件拼装成页面。组件的代码将在下面提及。

    3.2数据部分:

      我们项目最根本的数据源来自于MySQL数据库。需要渲染数据时,前端组件通过ajax向后端的php文件发送请求,php文件再向MySQL查询数据,并把数据以json的形式传给前端页面,前端页面解析json后通过重新初始化或者数据绑定机制渲染组件。

    3.3事件部分:

      这次项目里组件间的交互较多。包含各种点击事件,有些还相互重叠形成更复杂的事件。所以我们选择使用vue框架的事件总线机制和全局变量机制来处理事件。部件间用事件总线进行通信,再把必要信息存入全局变量供其他事件调用。这部分代码也将在下面提及。

    遇到问题与困难

    细小问题集:

      在项目开发中,我们有遇到一些细小的问题,这些问题处理难度不大,但是比较经典。

    element-ui图标、字体丢失:

    描述:

      项目本地部署时一切正常,但是部署到云服务器后会出现图标、字体丢失的情况。

    产生原因:

      工程配置文件对应文件类型(eot、ttf、otf)loader了两次,造成了重复,因此脚手架无法加载这几种类型的文件。

    解决方案:

      去掉一个loader即可。

    分析:

      推测是因为工程创建时自带一个loader,之后安装element-ui依赖时又写入了一个loader,因此造成重复。

    可执行文件部署云服务器上时页面空白:

    描述:

      项目部署本地一切正常,部署云服务器后页面变成空白。

    控制台信息:

      控制台表示找不到4个文件。

    产生原因:

      vue框架生成的单页应用的可执行文件结构是这样:一个index.html文件+静态资源文件。index.html文 件就几行,主要作用是引用那些资源文件。也就是说,index.html完全只起一个入口的作用,那些资源文件 才负责渲染页面。但是index.html引用文件的路径方式在服务器上不可行,所以使得服务器上的网页一片空 白。

    解决方案:

      在index.html的四个url前加上一个“.”即可解决问题。

    分析:

      之所以在windows环境下和Linux环境下表现不一样,可能是因为两个系统,或者两个系统下的Apache对地址的识别不一样。

    bug集:

    数据延时:

      我们最早做的数据同步是底部的表格。这个表格的数据只要在vue的生命周期中初始化部分加载数据即可,整体做下面没有遇到多大麻烦。

      但是当我们做趋势表时,按照底部表的方法做,就会出现问题。具体是这样:比如点击治愈趋势,出现的表是新增疑似趋势,再点死亡趋势,出现的是治愈趋势,再点治愈趋势,出现的是死亡趋势。

      这个问题按照上面的文字描述看,可以跟直观地说成:数据总慢半拍。但是当时的情况,数据杂乱无章真的很不好对面。

      提到慢半拍,就联想到了异步与同步,又联想到了ajax的特性就是异步。于是找出原因了,趋势表的业务流程是这样:触发切换图表类型事件->(发动ajax请求->(得到数据->解析数据))->用解析完的数据渲染表格。这里惊呼,原来是渲染表格的位置错了。如果把渲染表格的步骤放到ajax之外,在ajax还没拿到数据时就会完成渲染,用的就是上一次的数据,就出现了慢半拍的问题。

      这个问题查错麻烦,解决起来简单。只要把渲染步骤放ajax里就行了。

    事件反复响应之bug:

      这个bug应该是这次项目里遇到最严重的了。

      bug初始描述:按照正常逻辑,点击地图后图表会变成对应地区的数据表。但是在这个bug里,echarts图表会反复渲染几次,最后才变回正确数据。

      这个bug最开始是在做右上方图表(下称一号图表)的数据沟通时出现的。具体情况是:点击左侧地图省份,右侧图表上的数据会反复渲染几次,最后才变成正确的数据。

      初步分析:地图和一号图表之间是通过Vue的事件总线进行沟通的,图表的反复渲染也许和事件总线错误有关。

      经过一番查找,果然,这个bug出现的原因是没对事件响应进行注销。所以每次一号图表接收到事件,都会产生一个新的事件响应,多个事件响应叠加在一起,就变成了重复渲染多次的bug。

      在加上事件注销后一切又归于平静了。

      但是当我们在做地图数据沟通的时候,新的bug又出现了(也可能是一直存在,只是之前没发现)。

      描述如下:地图组件有切换地图功能,点击按钮会切换一张数据地图。如果没点过切换地图,一切都是正常的。但是若点了切换地图,接下来再点省份,趋势图(下称二号图表)又会出现反复渲染的问题,且情况和一号图表的bug一模一样。

      又是总线事件没注销吗?但是在一号图表出bug后,所有与总线事件有关的组件都加上了事件注销。为什么还有bug呢?于是接着到csdn上找vue事件反复响应的问题。有查找到的博客说的解决方案都是在组件创建和销毁时加入事件注销。于是依葫芦画瓢照做了。

      都是无论如何处理总线事件,二号图表始终存在那个bug。

      这就着实令人困扰了。

      再次观察bug:不点切换按钮-点省份-二号图表渲染一次(正常);点一次切换按钮-点省份-二号图表渲染五次;点两下切换按钮-点省份-二号图表渲染9次;

      不难发现,每点一次切换按钮,图表就会多渲染4次。恍然大悟,原来是这个按钮有问题。于是把这个element-ui的按钮仔仔细细查了一遍,又往里面加入了所有的事件注销。但是bug依旧。

      重新整理思路:页面加载->初始化地图+初始化二号图表->点击切换按钮->再次初始化地图->点击省份->触发地图响应->初始化二号图表。

      从上面的流程里发现了关键点:或许不是事件响应了多次,是因为同时点到了很多个一样的地图。或者换句话说,很多张地图重叠在了一起,点击一次省份时触发了多个事件,所以造成了二号图表的多次渲染。也就是说,切换地图的时候,旧地图还在那个位置,只是被新地图挡住了。

      理到这里,所有表现都解释地通了,为什么只有切换了地图才会出现bug,为什么图表渲染次数是递增的。

      于是去找文档,找到了echarts的dispose方法。在所有初始化函数里加上对原始图表的注销后,bug消失了。

      解决bug所添加的代码高达3行(且一模一样)

      echarts.dispose(document.getElementById(id))

      之所以在这个(或者说这两个)bug上写这么多是因为,bug产生的原因有两种,解决方法也要两种,但是两种bug的表现一模一样。在解决完第一个bug后就会陷入一个思维陷阱,想之前的解决方法怎么就不行了。只有跳出这个陷阱重新分析才能正确解决。

    源码分析:

    Echarts图表:

      因为统计涉及大量图表,所以我们在项目里引用了Echarts。我们的Echarts是以本地依赖的方式install到了项目工程文件里,然后在需要调用的组件页引用。

      Echarts功能强大,属性繁多。所以要配较多的js代码。

      下面以趋势图标的相关代码为例进行说明:

    1.在vue部件里设置div来存放图表

    <template>
      <div id="LineChart"></div>
    </template>
    

    2.设置图表的初始化函数

    import echarts from 'echarts'
    import 'echarts/map/js/china'
    function init (id, name, data, area) {
      var dom = document.getElementById(id)
      echarts.dispose(document.getElementById(id))
      var myChart = echarts.init(dom)
      var option = null
      option = {
        title: {
          text: area + name + '趋势',
          subtext: '',
          left: 'center',
          align: 'right'
        },
        tooltip: {
          trigger: 'axis'
        },
        xAxis: {
          type: 'category',
          data: [...]
        },
        yAxis: {
          type: 'value'
        },
        grid: {
          x: 45,
          x2: 0,
          y2: 30
        },
        series: [{
          name: name,
          data: data,
          type: 'line',
          smooth: true
        }]
      }
      if (option && typeof option === 'object') {
        myChart.setOption(option, true)
      }
    }
    export {
      init
    }
    
    

    3.在vue组件里调用初始化函数

    init('LineChart', tempType, dataOfLineChart, msg)
    

    element-ui组件:

      此次项目用到许多element-ui的组件。和Echarts一样,工程文件里install了element-ui依赖,然后在需要的vue部件里使用组件。

      相比Echarts,element-ui的属性较少,也不用另设太多js代码,用起来相当方便快捷。同时element-ui的样式都较为美观,令人舒心。

      项目中element-ui组件的代码多形如下段:

    <div id="timeSelector">
          <el-tooltip class="item" effect="dark" content="由于数据局限性,请选择2.13-3.9间的日期" placement="right">
          <el-date-picker
            v-model="valueOfTimeSelector"
            value-format="yyyy-MM-dd"
            style=" 195px"
            type="date"
            @change="change"
            placeholder="选择日期">
          </el-date-picker>
          </el-tooltip>
        </div>
    

    自定义组件:

      vue的思想是把页面的区块封装成一个个组件,再用组件拼装出页面。我们的项目中也是用这样的组件思想来封装页面元素的。

      以地图组件(工程文件中名为Map.vue)为例。该组件样式如下,包括3大部分:Echarts地图、element-ui时间选择器、element-ui按钮。加上隐藏的提示框,共4个元素。

      而在代码中,四个元素被组合起来封装为一个Map组件供上一级组件调用。

    <template>
      <div id="back">
        <div id="map" class="block"></div>
          <div id="timeSelector">
            <el-tooltip class="item" effect="dark" 
                        content="由于数据局限性,请选择2.13-3.9间的日期" placement="right">
              <el-date-picker
            v-model="valueOfTimeSelector"	
            value-format="yyyy-MM-dd"
            style=" 195px"
            type="date"
            @change="change"
            placeholder="选择日期">
          </el-date-picker>
            </el-tooltip>
          </div>
          <Button style="margin: 10px auto"></Button>
      </div>
    </template>
    

    数据绑定:

      页面中一些组件用到了vue的数据绑定机制。基本逻辑是这样:在html部分写组件并为其绑定数据源;在js部分设置数据源。

      以页面底部的表格为例,html部分按照以下部分定义,其中第二行:data就是设置数据源代码,tableData就是数据源(这段代码里对数据源进行了截取):

    <el-table
          :data="tableData.slice((currentPage-1)*pagesize,currentPage*pagesize)"
          style=" 1100px;margin: 50px auto"
          :default-sort = "{prop: 'new_ip', order: 'descending'}">
          ...
    </el-table>
    

      而在同页面的js段,具体设置数据源内容:

    data() {
          return {
            multipleSelection: [],
            total: 50,
            pagesize: 8,
            currentPage: 1,
            tableData: dataOfProvinces	// tableData的内容来自于dataOfProvinces
          }
        },
    
     axios.get('api/getData_bottom.php', {
            params: {
            }
          }).then(function(res) {
            var ajaxi = res.data.length
            var i0 = 0
            while (i0 < ajaxi) {
              // window.console.log(res.data[i0]['治愈'])
              var temp = {province: res.data[i0]['省份名'],
                new_ip: res.data[i0]['新增确诊'],
                now_ip: res.data[i0]['现有确诊'],
                cure: res.data[i0]['治愈'],
                death: res.data[i0]['死亡']
              }
              dataOfProvinces.push(temp)	// 设置dataOfProvinces数组
              i0 += 1
            }
          }).catch(function (error) {
            console.log(error)
          })
    

    事件总线:

      页面部件间的沟通是通过Vue的事件总线进行的。工程内定义了一个总线对象EventBus。部件通过EventBus的emit方法和on方法发送和监听事件。

    import Vue from 'vue'
    export const EventBus = new Vue()	// 注册EventBus
    

      地图上的省份、各种按钮、时间选择器都是事件发送源。用户点击/选择时间都会向数据总线发送数据。以地图区域点击事件为例,其代码如下:

    myChart.on('click', function (params) {
        Vue.prototype.$Area = params.name
        EventBus.$emit('ChangeArea', params.name)	// 向事件总线发送消息
      })
    

      接收方部件在初始化时,注册监听事件,之后在其生命周期内监听总线对应事件。每当事件发生,就做出相应响应。以地图组件的“监听时间选择器变化”事件为例,其代码如下:

    mounted () {
        this.getData('2020-03-09')
        Vue.prototype.$TypeOfMap = '现有确诊'
        EventBus.$on('ChangeState', (msg) => {	// 监听事件总线上的某一消息/事件
          if(msg === '累计确诊') {
              Vue.prototype.$TypeOfMap = '累计确诊'
              this.getData(this.valueOfTimeSelector)
          } else {
              Vue.prototype.$TypeOfMap = '现有确诊'
              this.getData(this.valueOfTimeSelector)
          }
        })
      },
    

    数据交互:

      此次项目的数据交互部分略微复杂。使用的基于ajax的axios。

      主要流程是:页面向服务器php文件发送ajax请求->php响应并以json的形式返回数据->ajax接收数据并进行处理。

      以趋势图表为例,其ajax部分代码如下:

    axios.get('api/getData_mid.php', {	// 发动ajax get请求
            params: {	// 设置要传递给php的参数
              type: '新增疑似',
              province: this.$Area
            }
          }).then(function(res) {	// 请求成功后执行如下代码
            dataOfLineChart = []
            var ajaxi = res.data.length
            var i0 = 0
            while (i0 < ajaxi) {
              var temp = res.data[i0]['新增疑似']
              dataOfLineChart.push(temp)	// 设置数据
              i0 += 1
            }
            init('LineChart', '新增疑似', dataOfLineChart, '全国')
          }).catch(function (error) {
            console.log(error)
          })
    

    数据格式化发送与解析:

    ​ 我们的前后端数据是以json为载体进行通讯的。因此数据发送方要对数据进行格式化,而数据接收方要对格式化数据进行解析。下面以页面底部的统计表格为例进行说明。

    ​ 统计表格的json数据局部截图如下:

    ​ 项目中大部分json的格式都形如上:[{数据名:值,数据名:值...}...]。

    ​ 发送数据的php文件把从MySQL里查询所得的数据打包上述格式的json并转发给前端页面。

    		$part=array();
    		$part['省份名']=$row['p_name'];
    		
    		$part['现有确诊']=$row['ip']-$row['cure']-$row['dead'];
    		$part['确诊变动']=$row['n_ip']-$row['n_cure']-$row['n_dead'];
    		if ($part['确诊变动']>=0)$part['确诊变动']="+".$part['确诊变动'];
    
    		$part['累计确诊']=$row['ip'];
    		$part['新增确诊']=$row['n_ip'];
    		if ($part['新增确诊']>=0)$part['新增确诊']="+".$part['新增确诊'];
    		...
            array_push($json , $part);
    

    ​ 数据的接收方在接收到数据后对其进行解析,将其中所需信息提取出来并封装成新的数组供组件使用。(下面代码中的res为ajax响应结果,res.data为接收到的json)

    		var ajaxi = res.data.length
            var i0 = 0
            while (i0 < ajaxi) {
              var temp = {province: res.data[i0]['省份名'],
                new_ip: res.data[i0]['新增确诊'],
                now_ip: res.data[i0]['现有确诊'],
                cure: res.data[i0]['治愈'],
                death: res.data[i0]['死亡']
              }
              dataOfProvinces.push(temp)
              i0 += 1
            }
    

    六.心路历程和收获&评价

    • 221701204
        看到这个题目的时候,感觉特别复杂,心里也没什么底,但好在我的队友十分优秀,指出制作网页的思路,所以进展还算顺利。我负责的部分是寻找数据,数据存储,以及后端的相应响应。在开始也想尝试爬取网页,但由于本身不怎么了解python语言,试了一下还是打算从网页上寻找数据文件,再正则转换成想要的格式,读入数据库。最大的收获,应该是明白了做这些东西得早开始,多花时间多思考,才能有所进步。本来寒假开始得时候想学习安卓,但总是因为各种原因,导致进度十分缓慢,希望自己能更有计划和效率。
    • 221701231
        初见题目的时候是感觉头很大,那么多功能用什么实现、怎么实现,数据哪里来、怎么来。但是真正动手后又觉得这些问题没想象中可怕。不敢说它们是纸老虎,只能说这些老虎看起来没那么凶了。
        让问题不再可怕的原因还有一个就是我的队友很强大。那些我看了一眼就感觉头疼的问题,在队友那边不一会就解决了,效率高得惊人。事后队友也有和我分享解决方案,虽然现在只能假装听得懂,但是这些经验记录下来以后肯定会用上。上纲上线地讲结对作业也是一个互相学习的过程。
        这次项目是对vue框架的一个实践,但是因为经验知识不足,中间吃了很多亏。有的时候遇上了bug连百度都不知道怎么百度,真的是哑巴吃黄连。好在项目中遇到的问题最终都得到了解决。此外项目中的bug的解决方案都挺有意思,有的时候加一个点或者换一行代码的位置就能让顽固的bug消失,不得不感叹做软件也是个精细活。总想到一个词“惨淡经营”,我觉得软件项目都是要那样一步步打磨出来的,在有限的时间里不断雕琢出一个能够交付的作品。
  • 相关阅读:
    Python加载声音
    Python 的文件处理
    java学习总结
    Fiddler二次开发 C#
    开发工具 快捷键
    linux / shell /adb
    Java堆栈
    selenium获取接口 HAR
    服务端通过socket向安卓客户端发送shell
    设计模式
  • 原文地址:https://www.cnblogs.com/radishbear/p/12503625.html
Copyright © 2011-2022 走看看