规划
上一期,做的工作主要是数据整合,数据处理,报表数据输出,已经做了大部分工作,现在的问题是如何 Web 化。
Web 化的主要目的是,让用户通过浏览器访问到报表数据,所以改造的重点在于将数据展示部分的用前端技术实现。
于是需要做的是,设计展示页面,定义与后端的 数据接口,选择服务器 部署。
而其中重点的工作是 展示页面 和 数据接口。
在进行具体工作之前,还有个一个重要工作就是 —— 选择技术路线,即用什么技术做 Web 化实现。
那需要基于 Python 的 Web 框架来说,可以选择 Flask 和 Django。
鉴于项目比较小,另外之前写了一系列 Flask 的文章[1],对 Flask 比较熟,所以选择了 Flask。
然后将之前实现的功能作为 Web 项目的外部模块引入,承担数据读取任务。
因为 Web 服务主要是通过数据读取的,对于打卡数据的抓取,和成员积分的计算,需要定时任务来完成,虽然可以在 Web 服务中利用 scheduled 来写计划任务,为了简便,直接使用 Linux 服务的 crontab[2] 命令来定时执行。
展示页面
之前写过一篇 nginx 日志可视化,其中用的是 Bootstrap 的前端框架,直接拿来用。
真是,平时多积累,用时显身手!
需要的页面有 打卡页面、成员数据、组长数据、开单记录。
从哪里开始呢?不是具体的页面,而是从制作模板页面开始。
模板页面
实现前台功能,比较省力的方式是使用模板,即,将页面共同的元素写在模板上,以重复利用。
创建一个页面模板 layout.html
,模板中写完页面主题框架,以及引入 css,js 文件,并且为可替换部分预留区域。
可替换部分有,样式,导航菜单、主体页面、脚本。
因为最后是用 Flask 作为 Web 服务的,所以采用 Jinja2[3] 做为模板引擎。
代码大致如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>训练营311班</title> <link href="../static/css/bootstrap.min.css" type='text/css' rel="stylesheet"> <link href="../static/css/dashboard.css" rel="stylesheet"> {% block head %} {% endblock %} </head> <body> <!--导航栏--> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">销售训练营 311班</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav"> {% block menu%} {% endblock %} </ul> </div> </div> </nav> <div class="container-fluid"> <div class="row"> {% block main%} {% endblock %} </div> </div> <script src="../static/js/bootstrap.min.js" type="text/javascript"></script> {% block script %} {% endblock %} </body> </html>
可以看出页面采用的是 Bootstrap 框架[4],加载了 Bootstrap 必要的 Css 和 js 库。
然后加入了页面框架,并预留了菜单、页面主体、定制的脚本位置。
打卡页面
打卡页面需要展示打卡率,最好的方式是用图表。
首选 ECharts[5],因为 ECharts 很成熟,而且易用。
然后在 ECharts 示例[6] 中找到合适的图表。
值得点赞的是,ECharts 示例中,提供了示例代码,直接复制使用即可。
我们选用 柱状图标签旋转[7]
柱状图标签旋转
将代码复制到页面中。
图表数据通过 Flask 视图加载时提供。
部分代码如下:
{% extends "layout.html" %} {% block menu %} <li class="active"><a href="/check_rate">打卡率</a></li> <li><a href="/memberdata">成员数据</a></li> <li><a href="/teamleader">组长数据</a></li> <li><a href="/sale">开单记录</a></li> {% endblock %} {% block main %} <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">{{title}}</h1> <div class="row placeholders"> <div class="col-xs-12 col-sm-8 col-lg-10 placeholder" style="height:500px;" id="main"></div> <div class="col-lg-4 col-lg-offset-4 col-sm-6 col-sm-offset-3 col-xs-8 col-xs-offset-2"> <div id="detail">点击图表查看 打卡详情</div> </div> </div> </div> {% endblock %} {% block script %} <script src="../static/js/jquery.2.0.3.min.js" type="text/javascript"></script> <script src="../static/js/echarts5.min.js" charset="utf-8"></script> <script type="text/javascript"> var app = {}; var chartDom = document.getElementById('main'); var myChart = echarts.init(chartDom); ...<省略>... var option = { title: { show: true, left: 'center', top: 'top' }, ...<省略>... dataset: { source: {{ data | safe }} }, ...<省略>... } ...<省略>... {% endblock %}
{% extends "layout.html" %}
引入模板页面layout.html
{% block menu %}
、{% block main %}
等 为替换的内容source: {{ data | safe }}
的意思是 使用视图中的值data
填充到这里,后面的safe
是一个过滤器,表示替换部分不做 Html 安全字符转化
数据缩放
这里还需要解决一个问题,就是随着日期的增加,图表就是很密,不好观看,ECharts 提供一种图表缩放工具 DataZoom
,
可以通过拖动和改变大小控制数据的展示范围,配置很简单,加在图表的配置中就好了,例如我的写法是:
dataZoom: [{ type: 'slider', start: 60, end: 100 }],
start
和end
表示数据范围开始和结束的百分比,这一点特别赞
详细数据
图表展示后,如果想知道某一天某个组详细的打卡数据怎么办?
为了便于使用,利用 EChart 的事件机制,当点击一个柱状图时,显示出详细信息。
实现很简单,就是个图表对象加载一个事件,在事件里,使用 Ajax[8] 加载并显示数据就好:
myChart.on('click', 'series', function(params) { console.log(params); $.ajax({ url: "/api/teamcheckdetail", data: { team: params.seriesName, date: params.name } }).done(function(response) { data = response.data; $("#detail").jsGrid({ editing: false, sorting: false, autoload: false, data: data, fields: [{ 'name': 'name', 'title': '昵称' }, { 'name': 'check', 'title': '打卡' }] }); }); });
- 事件针对于
系列
(series) 被点击 /api/teamcheckdetail
为后台数据接口(详见 API 设计)$("#detail").jsGrid()
是一个 jsGrid 做的表格,加载方法是重新炫耀一个表格
到此,最复杂的打卡页面就搞定了,看看效果吧:
打卡率