为什么选小程序
主要还是服务器的费用的问题,小程序开发结合云服务器可以省掉很多运维的问题,加上我这次开发主要是为了熟悉小程序的组件开发与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
,canvasid
和echartjs
一系列操作的绘制对象。
有一个问题是,官方的组件所绑定的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年多了,感觉自己还是有很多模糊的概念,很多时候都在想程序员的终点是什么? 在看完雷军的大学时期的经历和一些文章,其实也感觉的到程序员的终点也是交互设计工程师。
如果只是一个提供技术力的程序员是活不长久的。
在这次实习的时候,公司安排我一个人负责一个内部的设备管理系统,由于第一次做这种比较完整的项目,虽然这种系统并不复杂,但是在开发到后面的时候,你永远不知道会有什么需求会出现,所以如果我们设计的页面比较死板的话,就会导致后期的需求开发周期变长和变难。
无论多复杂的系统都是从最简单的开始的,都是一砖一石搭建出来的,如果你觉得你能够在产品开发前就已经设计好一个很完美的系统,我觉得是不可能的。因为每一个系统的需求都会因为它面对的用户而变得不同。(也有可能是我太菜了)
像这个疫情地图,从一开始只有一个页面,到后面的组件,代码的复杂度越来越大,但是我的目录文件和代码管理都不是很清晰。我感觉是当局者迷,过几天反思就会发现项目中的问题。
还有几个月就毕业了,祝大家能够获得自己心仪的工作。