本项目是基于 ESP32 实现的全栈物联网应用,将会手把手教用户实现 MQTT 环境的搭建、硬件传感器的驱动以及小程序的编写。
手把手教学配套视频地址
您将会学习到 HTML、CSS、JS、微信小程序官方开发语言、Echarts、NodeJs、Express、MySQL、ARDUINO 等技能,此文章篇幅较长,建议分为多次观看,一起往下看吧~
一、开发环境搭建和微信开发者工具安装
nodejs 环境安装
我们写 js 的环境一般都需要 nodejs 的支持,另外安装 npm(js 包管理工具)也需要 nodejs 环境,现在就教大家一起来安装
官网下载地址Node.js (nodejs.org) 选择长期维护版本并下载根据提示安装即可
腾讯软件管家下载地址 https://pc.qq.com/detail/5/detail_24845.html
验证是否安装成功
打开 cmd 输入 node-v 如图所示有版本号即为安装成功(版本号不一致不碍事)
接下来输入 npm -v 验证 npm 是否安装成功 npm 是 nodejs 附带的
至此 nodejs 已安装成功
微信开发者工具安装和配置
工具下载地址https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html
选择适合自己操作系统的版本下载
文档地址(用于学习和参考)
官方文档写的很详细 不懂的地方一定要多看文档
https://developers.weixin.qq.com/miniprogram/dev/framework/
安装
这里直接下一步就行
界面
打开后允许通过防火墙并扫码登陆 整体界面如下
二、新建项目和项目整体配置
开发者工具界面
打开微信开发者工具先扫码登陆
由于我们开发的是小程序 这里选择小程序 点击 + 号创建一个小程序项目
接着选择项目保存路径,尽量放在自己能找到的地方 采用英文名
Appleid 这里需要上线的话选择注册,会跳转到注册连链接(需要认证费用) 如果不是打算上线的话选择测试号即可,这里我们选择测试
后端服务选择 不使用云服务
模板选择 javascript-基础模板
然后点击确定就创建项目完成了
微信开发者工具结构配置介绍
文件结构
一个小程序包含一个描述整体程序的 app 和多个描述各个页面的 pages。 一个小程序主体由三个文件组成,必须放在项目的根目录下:
app.js:小程序逻辑文件
- app.json: 小程序公共设置(主体配置文件)
- app.wxss:小程序公共样式表(全局样式) (可以删除)
- 一个小小程序 page 页面由四个文件组成,分别是:
- js:页面逻辑
- wxml:页面结构
- wxss:页面样式(可以删除)
- json:页面配置(可以删除)
- utils:工具目录(可以删除),实现了模块化工具类,工具函数。
- sitemap.json:小程序搜索所用。
- project.config.json:编辑器配置。
小程序配置 app.json
app.json 文件用来对小程序进行全局配置,决定小程序的页面数量、窗口表现、设置网络超时时间、设置底部或顶部菜单等。
app.json 中不能添加任何注释,key 和 value 字符串必须用双引号引起来,数组或对象最后一位不能有逗号。
app.json 配置项列表
pages
"pages": [
"pages/index/index", // 数组第一项,小程序的初始页面。
"pages/logs/logs"
],
pages 指定由哪些页面组成,每一项代表对应页面【路径+目录名+文件名】。
pages 数组的第一项代表小程序的初始页面(首页/展示页)。
小程序中新增/减少页面,都需要对 pages 数组进行修改 。
window
用于设置小程序的状态栏、导航条、标题、窗口背景色。
"window":{
"enablePullDownRefresh": true, //是否开启开启全局的下拉刷新 true 开启,false 关闭。
"backgroundColor":" #ccc", //窗口的背景色
"backgroundTextStyle":"light", //下拉 loading 的样式,仅支持 dark 和 light
"navigationBarBackgroundColor": "#fff", //导航栏背景色
"navigationBarTitleText": "Weixin", //导航栏标题文字内容
"navigationBarTextStyle":"black", //导航栏标题颜色,仅支持白色(white)和黑色(black)
"navigationStyle":"custom" //去除导航栏
},
tabBar
设置小程序的底部或顶部菜单栏。
注释:当设置 position 为 top 时,将不会显示 icon 图标。
当 tabBar 中的 list 是一个数组,只能配置最少 2 个、最多 5 个菜单。
"tabBar": {
"color": "#999", //tab上文字默认颜色,仅支持十六进制颜色
"selectedColor": "", //tab 上的文字选中时的颜色,仅支持十六进制颜色
"backgroundColor": "", //tab 的背景色,仅支持十六进制颜色
"position": "bottom", //tabBar 的位置,仅支持 bottom / top
"borderStyle": "black", //tabbar 上边框的颜色, 仅支持 black / white
"list": [
{
"pagePath": "pagePath", //页面路径
"text": "text", //页面文字
"iconPath": "iconPath", //默认图标路径
"selectedIconPath": "selectedIconPath" //选中图标路径
},
{
"pagePath": "pagePath", //页面路径
"text": "text", //页面文字
"iconPath": "iconPath", //默认图标路径
"selectedIconPath": "selectedIconPath" //选中图标路径
}
]
},
networkTimeout
可以设置各种网络请求的超时时间,单位毫秒。
"networkTimeout": {
"request": 10000 //发起HTTP请求的超时时间 10s (一般设置5s:5000毫秒)
},
三、头部 ui 制作
页面标题
我们需要在 app.json 里去除掉标题块 设置 “navigationStyle”: "custom”
头部直接使用image
和text
标签即可
天气板快
使用 css3 的 flex 布局
对于 flex
布局不理解的同学可以去看这篇文章,里面有很详细的讲解 Flex 布局教程:语法篇 - 阮一峰的网络日志 (ruanyifeng.com)
四、MQTT 连接 ui 制作
mqtt 板块
这里主要运用的是 flex 布局的两端对其属性justify-content:space-between
引入 Vant 组件库
什么是组件?为什么要使用组件库?
组件是页面中最基本的元素。按钮、输入框、下拉选择都是组件,组件和组件组合成为更复杂的组件。至于为什么要用组件库,我觉得应该是体验。用户体验和开发者的开发体验,用户在页面上的交互都是通过组件进行的。一个价值高的组件,第一眼就能吸引用户点击,这就是用户体验。更不用说开发经验了,组件是同一类型不同交互的封装。使用组件库可以让我们更专注于业务开发和产品交互。
怎么引入 Vant 组件库?
1、确保安装有 nodejs
2、在项目根目录打开 cmd 输入初始化项目的命令 npm init
一直按回车即可
3、然后通过 npm 指令安装 Vant Weapp npm i @vant/weapp -S --production
4、修改 app.json
将 app.json 中的 “style”: “v2” 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。
5、构建 npm 包
打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,可见官方文档 快速上手 的
步骤四。新版的微信开发者工具中,详情 -> 本地设置中没有【使用 npm 模块】选项,则不用理会, 如果有则需要勾选。
6、使用组件
你只需要在 app.json 或 你需要使用 vant 的页面中的 json 文件进行组件的注册即可使用了
这里涉及到注册组件的两种方式,后面会讲到。下面,以在 app.json 全局注册 button 组件为例:
注册引入组件后,在 wxml 中直接使用组件
mqtt 连接弹出框
弹窗使用的是 Vant Weapp 组件库的 Popup 弹出层 组件
输入框使用的是 Field 输入框
按钮使用的是 button 按钮
<!-- 弹出框 -->
<van-popup
show="{{ mqttShow }}"
round
position="bottom"
custom-style="height: 65%"
bind:close="closeMqttDialog"
>
</van-popup>
<!-- js变量 mqttShow:false -->
<!-- 输入框 -->
<van-cell-group>
<van-field
disabled="{{isConnect}}"
model:value="{{address}}"
clearable
label="address"
placeholder="请输入地址"
bind:click-icon="onClickIcon"
/>
<van-field
disabled="{{isConnect}}"
model:value="{{ port }}"
type="number"
clearable
label="port"
placeholder="请输入端口号"
bind:click-icon="onClickIcon"
/>
<van-field
disabled="{{isConnect}}"
model:value="{{ username }}"
clearable
label="username"
placeholder="请输入用户名"
bind:click-icon="onClickIcon"
/>
<van-field
disabled="{{isConnect}}"
model:value="{{ password }}"
type="password"
label="password"
placeholder="请输入密码"
/>
</van-cell-group>
<!-- js变量 address: "", post: "", username: "", password: "", -->
mqtt 连接框逻辑编写
定义变量是否连接,默认值为 false isConnect=false
给这一块的代码加上点击事件 bindtap=”事件名”
/* 展示mqtt连接弹窗 */
事件名() {
this.setData({ mqttShow: true });
},
根据是否连接切换颜色
<view style="color: {{isConnect?'RGB(0,176,80)':'RGB(192,0,0)'}};">
{{isConnect?'已连接':'未连接'}}
</view>
根据连接弹窗是否打开切换箭头上下 需引入icon
<van-icon wx:if="{{!mqttShow}}" name="arrow-down" />
<van-icon name="arrow-up" wx:if="{{mqttShow}}" />
连接/断开按钮编写和变色逻辑
<van-button
size="small"
disabled="{{isConnect}}"
color="{{isConnect?'#d9d9d9':'#97baff'}}"
bindtap="mqttConnect"
>连接</van-button
>
<van-button
size="small"
disabled="{{!isConnect}}"
color="{{!isConnect?'#d9d9d9':'#97baff'}}"
bindtap="mqttBreak"
>断开</van-button
>
五、传感器设备和其他设备 ui 制作
具体使用 flex 布局,采用一行两个板块
/* 父元素设置 */
display: flex;
flex-wrap: wrap;
width: 100%;
/* 子元素为父元素/2的值 即可一行两个 超过换行 */
width: 315rpx;
通过数据驱动视图来循环出多个数据板块 默认值给 0
sensorList: [
//传感器列表
//图 名字 参数 值 单位 序号
{
img: "/images/P1.png",
name: "DHT22",
parameter: "温度",
value: 0,
unit: "°C",
idx: 0,
},
{
img: "/images/P2.png",
name: "DHT22",
parameter: "湿度",
value: 0,
unit: "%rh",
// isPass: true,
idx: 1,
},
{
img: "/images/P3.png",
name: "TEMT6000",
parameter: "光强",
value: 0,
unit: "lx",
idx: 2,
},
{
img: "/images/P4.png",
name: "MQ2",
parameter: "烟雾",
value: 0,
unit: "ppm",
idx: 3,
},
],
其他设备 ui 制作
同理,使用 flex 布局
开关组件使用 指定数据并循环展示
otherSensorList: [
{ img: "/images/deng.png", name: "灯", isOpen: false },
{ img: "/images/fengshan.png", name: "风扇", isOpen: false },
{
img: "/images/chuanglian.png",
name: "窗帘",
schedule: 0,//进度条
isOpen: false,
},
],
进度条使用的是 vant
的 progress 组件
六、天气 Api 申请和定位显示天气
步骤:
1、申请高德地图 key 我的应用 | 高德控制台 (amap.com)
2、将高德地图 api(https://restapi.amap.com
)增加到小程序 request 合法域名
3、在 app.json 里增加
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序天气接口的效果展示"
}
}
4、获取设置函数wx.getSetting
看是否授权定位
5、获取定位函数wx.getLocation
获取经纬度 : latitude 纬度 longitude 经度
6、通过高德地图逆地理编码接口传入经纬度获取地区编码 接口文档
7、高德地图天气查询接口传入地区编码查询天气并显示天气和位置 接口文档
8、回显位置和天气情况并显示天气图图标
申请高德地图 key
点击创建新应用
选择 web 服务 ip 白名单设置为空
添加到 request 合法域名 https://restapi.amap.com
处于安全性考虑,小程序官方对于数据接口的请求作出了如下两个限制:
- 只能请求 https 类型的接口;
- 必须将接口的域名添加到信任列表中
配置 request 合法域名
假设需要在自己的微信小程序中,希望请求https://www.escook.cn/
域名下的接口
配置步骤:登录微信小程序管理后台 ⇒ 开发 ⇒ 开发设置 ⇒ 服务器域名 ⇒ 修改 request 合法域名
跳过 request 合法域名校验(上线无法使用)
如果仅仅提供了 http 协议的接口,暂时没有提供 https 协议的接口。
此时为了不耽误开发进度,我们可以在微信开发者工具中,临时开启「开发环境不校验请求域名、TLS 版本以及 HTTPS 证书」选项,跳过
request 合法域名校验。
查看是否授权过定位权限
getUserLocation: function () {
let that = this;
wx.getSetting({
success: (res) => {
console.log(res, JSON.stringify(res));
// res.authSetting['scope.userLocation'] == undefined 表示 初始化进入该页面
// res.authSetting['scope.userLocation'] == false 表示 非初始化进入该页面,且未授权
// res.authSetting['scope.userLocation'] == true 表示 地理位置授权
if (
res.authSetting["scope.userLocation"] != undefined &&
res.authSetting["scope.userLocation"] != true
) {
wx.showModal({
title: "请求授权当前位置",
content: "需要获取您的地理位置,请确认授权",
success: function (res) {
if (res.cancel) {
wx.showToast({
title: "拒绝授权",
icon: "none",
duration: 1000,
});
} else if (res.confirm) {
wx.openSetting({
success: function (dataAu) {
if (dataAu.authSetting["scope.userLocation"] == true) {
wx.showToast({
title: "授权成功",
icon: "success",
duration: 1000,
});
//再次授权,调用wx.getLocation的API
that.getLocation();
} else {
wx.showToast({
title: "授权失败",
icon: "none",
duration: 1000,
});
}
},
});
}
},
});
} else if (res.authSetting["scope.userLocation"] == undefined) {
//调用wx.getLocation的API
that.getLocation();
} else {
//res.authSetting['scope.userLocation'] == true
//调用wx.getLocation的API
that.getLocation();
}
},
});
},
获取定位经纬度
getLocation() {
let that = this;
wx.getLocation({
type: "wgs84",
success(res) {
console.log("经纬度", res);
if (res?.errMsg === "getLocation:ok") {
/* ----------------通过经纬度获取地区编码---------------- */
wx.request({
url: "https://restapi.amap.com/v3/geocode/regeo?parameters",
data: {
key: KEY, //填入自己申请到的Key
location: res.longitude + "," + res.latitude, //传入经纬度
},
header: {
"content-type": "application/json",
},
success: function (res) {
console.log("坐标转换和查询天气", res.data);
wx.setStorageSync(
"city",
res.data.regeocode.addressComponent.adcode //地区编码
);
that.setData({
location:
res.data.regeocode.addressComponent.city +
" " +
res.data.regeocode.addressComponent.district,
});
wx.request({
url: "https://restapi.amap.com/v3/weather/weatherInfo",
data: {
key: KEY, //填入自己申请到的Key
city: res.data.regeocode.addressComponent.adcode, //传入地区编码
},
header: {
"content-type": "application/json",
},
success: function (weather) {
console.log("天气", weather.data);
that.setData({
weatherNum: weather.data.lives[0].temperature,//温度
weatherText: weather.data.lives[0].weather, //天气描述 晴天 下雨天...
welcome: "欢迎欢迎!今天的天气是 " + weather.data.lives[0].weather, //欢迎语
});
},
});
},
});
}
},
fail(err){
console.log('获取经纬度错误信息',err);
}
});
},
回显位置和天气情况并显示天气图图标
上述变量中绑定的变量温度为 weatherNum
天气描述为 weatherText
显示 :
<view class="welcome">{{welcome}}</view>
<view class="weather-num" wx:if="{{weatherNum}}">{{weatherNum}}℃</view>
天气图标显示 :
<wxs src="../../wxs/tools.wxs" module="tools"></wxs>
<view>
<image
wx:if="{{weatherText === '晴'}}"
class="weather"
src="/images/weather/qing.png"
mode="widthFix"
/>
<image
wx:if="{{tools.strIndexOf(weatherText,'雨')}}"
class="weather"
src="/images/weather/yu.png"
mode="widthFix"
/>
<image
wx:if="{{tools.strIndexOf(weatherText,'雪')}}"
class="weather"
src="/images/weather/xue.png"
mode="widthFix"
/>
<image
wx:if="{{weatherText === '多云'}}"
class="weather"
src="/images/weather/duoyun.png"
mode="widthFix"
/>
<image
wx:if="{{weatherText === '阴'}}"
class="weather"
src="/images/weather/duoyun.png"
mode="widthFix"
/>
<image
wx:if="{{weatherText === '雷'}}"
class="weather"
src="/images/weather/lei.png"
mode="widthFix"
/>
<image
wx:if="{{weatherText === '冰雹'}}"
class="weather"
src="/images/weather/bingbao.png"
mode="widthFix"
/>
</view>
考虑到天气可能是大雪/小雪、大雨/小雨等 和我们定义的雨、雪不一样 所以要引入一个函数 根据包含了 雨、雪的状态来显示相应的图
function strIndexOf(weatherText, text) {
//str.indexOf("")的值为-1时表示不包含
if (weatherText) {
// console.log(weatherText, text);
if (weatherText.indexOf(text) !== -1) {
return true;
} else {
return false;
}
}
}
module.exports = {
strIndexOf: strIndexOf
};
//使用方法
strIndexOf(weatherText, "雨"); //weatherText中包含了雨则显示 雨/大雨/小雨/雷阵雨
strIndexOf(weatherText, "雪"); //weatherText中包含了雪则显示 雪/大雪/小雪
参考资料
有时候传入参数不当会导致接口报错 这里可以根据报错状态码查询问题
返回的天气文字描述对照表 列举了天气功能中所能返回的天气现象
七、MQTT 下载引入和配置连接
mqtt 官网和官方教程
MQTT 协议入门与进阶 | EMQ (emqx.com)
MQTT.js 入门教程 | EMQ (emqx.com)
mqtt 下载
进入https://unpkg.com/mqtt@4.1.0/dist/mqtt.min.js 右击另存为到桌面
导入和使用
将下载到本地的 mqtt.min.js 拷贝到项目的 utils 目录下,如下图所示
const app = getApp();
import mqtt from "../../utils/mqtt.min";
const MQTTADDRESS = "你的mqtt服务器地址"; //mqtt服务器地址
let client = null; //mqtt服务
创建连接
connectMqtt(){
let that = this;
const options = {
connectTimeout: 4000,
address: this.data.address,//输入的地址
port: this.data.port, //输入的端口号
username: this.data.username,//输入的用户名
password: this.data.password,//输入的密码
};
console.log("address是:", options.address);
client = mqtt.connect(MQTTADDRESS, options); //连接
client.on("connect", (e) => {
console.log('连接成功');
})
client.on("reconnect", (error) => {
console.log("正在重连:", error);
wx.showToast({
icon: "none",
title: "正在重连",
});
});
client.on("error", (error) => {
console.log("连接失败:", error);
wx.showToast({
icon: "none",
title: "mqtt连接失败",
});
});
}
八、MQTT 订阅/发布和数据显示
添加订阅/添加发布(连接成功状态)
//订阅一个主题
client.subscribe("主题名字", { qos: 0 }, function (err) {
if (!err) {
console.log("成功");
wx.showToast({
icon: "none",
title: "添加成功"
});
}
});
取消订阅/发布(连接成功状态)
client.unsubscribe(this.data.subscribeTopic, function (err) {
if (!err) {
wx.showToast({
icon: "none",
title: "取消成功"
});
}
});
发布消息(连接成功状态)
根据判断是哪个设备的开关 然后发送消息
发送的所有可能为:{LIGHT: “ON”} { LIGHT: “OFF” } { FAN: “ON” } { FAN: “OFF” } { CURTAIN: “ON” } { CURTAIN: “OFF” }
let msg = "发出的消息";
client.subscribe("发布地址", { qos: 0 }, function (err) {
if (!err) {
// 发布消息
console.log("发出的", msg);
client.publish("地址", JSON.stringify(msg)); //转换json格式
}
});
收到消息和数据回显
client.on("message", (topic, message) => {
/* 温度、湿度、光强、烟雾
TEMPERATURE
HUMIDITY
LIGHT_INTENSITY
SMOKE
*/
console.log("收到消息:", message.toString());
// wx.showToast({
// icon: "none",
// title: message.toString(),
// });
let getMessageObj = {}; //收到的消息
getMessageObj = JSON.parse(message); //收到的消息转换成json对象
if (getMessageObj.hasOwnProperty("TEMPERATURE"))
this.setData({
/*这里的sensorList为上面定义的传感器设备数据的数组 如果包含某项数据将更新那条数据 会自动刷新视图和百分比*/
"sensorList[0].value": getMessageObj.TEMPERATURE ? Number(getMessageObj.TEMPERATURE) : 0
});
if (getMessageObj.hasOwnProperty("HUMIDITY"))
this.setData({
"sensorList[1].value": Number(getMessageObj.HUMIDITY)
});
if (getMessageObj.hasOwnProperty("LIGHT_INTENSITY"))
this.setData({
"sensorList[2].value": Number(getMessageObj.LIGHT_INTENSITY)
});
if (getMessageObj.hasOwnProperty("SMOKE"))
this.setData({
"sensorList[3].value": Number(getMessageObj.SMOKE)
});
if (getMessageObj.hasOwnProperty("CURATIN_PROGRESS"))
this.setData({
"otherSensorList[2].schedule": Number(getMessageObj.CURATIN_PROGRESS)
});
});
九、设置传感器阈值和数据保存本地缓存
点击传感器出来弹窗
在循环的(wx:if)传感器块上面增加点击事件 bindtap="thresholdSetting”
和 data-item="{{item}}”
//缓存数据
data:{
thresholdDialog: false, //阈值
thresholdObj: {
top: "",
bottom: "",
},
tempParameter: "", //某个参数的阈值
tempIdx: "",
tempTop: "",
tempBottom: "",
},
//点击传感器事件
thresholdSetting(e) {
console.log(e.currentTarget.dataset.item);
this.setData({
tempTop: e.currentTarget.dataset.item.top
? e.currentTarget.dataset.item.top
: e.currentTarget.dataset.item.top == 0
? e.currentTarget.dataset.item.top
: "",
tempBottom: e.currentTarget.dataset.item.bottom
? e.currentTarget.dataset.item.bottom
: e.currentTarget.dataset.item.bottom == 0
? e.currentTarget.dataset.item.bottom
: "",
thresholdDialog: true,//打开弹窗
tempParameter: e.currentTarget.dataset.item.parameter, //某个参数的阈值
tempIdx: e.currentTarget.dataset.item.idx,
"thresholdObj.top": e.currentTarget.dataset.item.top
? e.currentTarget.dataset.item.top
: e.currentTarget.dataset.item.top == 0
? e.currentTarget.dataset.item.top
: "",
"thresholdObj.bottom": e.currentTarget.dataset.item.bottom
? e.currentTarget.dataset.item.bottom
: e.currentTarget.dataset.item.bottom == 0
? e.currentTarget.dataset.item.bottom
: "",
});
},
wxml 部分 输入事件绑定函数 bindinput
<van-dialog
use-slot
title="设置安全阈值"
show="{{ thresholdDialog }}"
show-cancel-button
bind:confirm="thresholdClose"
>
<van-field
value="{{thresholdObj.top}}"
bindinput="getTop"
type="number"
clearable
label="上限"
placeholder="请输入上限"
bind:click-icon="onClickIcon"
/>
<van-field
value="{{ thresholdObj.bottom }}"
bindinput="getBottom"
type="number"
clearable
label="下限"
placeholder="请输入下限"
bind:click-icon="onClickIcon"
/>
</van-dialog>
输入上限和下限事件
getTop(e) {
this.setData({ tempTop: e.detail });
},
getBottom(e) {
this.setData({ tempBottom: e.detail });
},
弹窗点击确定事件
thresholdClose() {
console.log(
this.data.tempParameter,
this.data.tempIdx,
this.data.tempTop,
this.data.tempBottom
);
let index = this.data.tempIdx;
this.setData({
["sensorList[" + index + "].top"]: Number(this.data.tempTop),
["sensorList[" + index + "].bottom"]: Number(this.data.tempBottom),
});
console.log(this.data.sensorList);
},
连接数据保存本地缓存
这里要用到的三个 api:
wx.getStorageSync("key名");
读取本地缓存中的某个字段
wx.setStorageSync("key名", key值);
将某个字段存入本地缓存
wx.removeStorageSync(”key名");
将本地缓存中某个字段删除
我们需要的场景是连接成功的状态下次进来自动连接
//在mtqq连接成功后 我们把连接状态存到本地缓存 以及地址端口号账号密码存到本地缓存 用于在下次进来时候自动带入输入框 并且调用连接函数
let { address, post, username, password } = this.data;
let mqttInfo = { address, post, username, password };
wx.setStorageSync("mqttInfo", mqttInfo);
this.setData({ isConnect: true });
wx.setStorageSync("isConnect", true);
//点击断开连接后 删除本地缓存中是否有地址和登陆信息 有的话删除
this.setData({ isConnect: false, isPush: false, isSubscribe: false });
wx.setStorageSync("isConnect", false);
wx.getStorageSync("subscribeTopic") && wx.removeStorageSync("subscribeTopic");
wx.getStorageSync("pushTopic") && wx.removeStorageSync("pushTopic");
wx.getStorageSync("mqttInfo") && wx.removeStorageSync("mqttInfo");
进入小程序在 onLoad()生命周期中判断本地缓存中是否有数据 有的话带入并连接 mqtt
/*判断本地缓存中的登陆数据和是否连接数据是否存在*/
if (wx.getStorageSync("mqttInfo") && wx.getStorageSync("isConnect")) {
let mqttInfo = wx.getStorageSync("mqttInfo");
console.log(mqttInfo);
this.setData({
address: mqttInfo.address,
post: mqttInfo.post,
username: mqttInfo.username,
password: mqttInfo.password
});
this.connectMqtt(); //连接mqtt
}
if (
wx.getStorageSync("mqttInfo") &&
wx.getStorageSync("isConnect") &&
wx.getStorageSync("subscribeTopic")
) {
this.setData({ subscribeTopic: wx.getStorageSync("subscribeTopic") });
this.toSubscribe(); //添加订阅地址
}
if (
wx.getStorageSync("mqttInfo") &&
wx.getStorageSync("isConnect") &&
wx.getStorageSync("pushTopic")
) {
this.setData({ pushTopic: wx.getStorageSync("pushTopic") });
this.toPush(); //添加发布地址
}
十、数据记录功能(本地缓存实现)
展示图表官网
需要使用 echarts
图表的折线图功能,官网 ,折线图配置链接
对体积有要求的话使用定制图表 (可能导致图表出不来)
在微信小程序使用
echarts-for-weixin项目提供了一个小程序组件,用这种方式可以方便地使用
ECharts。
使用方式
- 下载该项目
- 如有必要,将
ec-canvas
目录下的echarts.js
替换为最新版的
ECharts。如果希望减小包体积大小,可以使用自定义构建生成并替换echarts.js
-
pages
目录下是使用的示例文件,可以作为参考,或者删除不需要的页面。
更详细的说明请参见echarts-for-weixin项目。
注意事项
最新版的 ECharts 微信小程序支持微信 Canvas 2d,当用户的基础库版本 >= 2.9.0 且没有设置force-use-old-canvas="true"
的情况下,使用新的 Canvas 2d(默认)。
使用新的 Canvas 2d 可以提升渲染性能,解决非同层渲染问题,强烈建议开启。
更详细的说明请参见Canvas 2d 版本要求。
数据读取和放本地缓存
将数据存在本地缓存和 app.js 里全局变量里,来达到每个页面都能增删改查
// 在app.js下面四个变量,分别为温度,湿度,光强,烟雾
globalData: {
chart_wd: wx.getStorageSync("chart_wd")
? JSON.parse(wx.getStorageSync("chart_wd"))
: [],
chart_sd: wx.getStorageSync("chart_sd")
? JSON.parse(wx.getStorageSync("chart_sd"))
: [],
chart_gq: wx.getStorageSync("chart_gq")
? JSON.parse(wx.getStorageSync("chart_gq"))
: [],
chart_yw: wx.getStorageSync("chart_yw")
? JSON.parse(wx.getStorageSync("chart_yw"))
: [],
},
// 全局隐藏时候存入到本地缓存
onHide() {
wx.setStorageSync("chart_wd", JSON.stringify(this.globalData.chart_wd));
wx.setStorageSync("chart_sd", JSON.stringify(this.globalData.chart_sd));
wx.setStorageSync("chart_gq", JSON.stringify(this.globalData.chart_gq));
wx.setStorageSync("chart_yw", JSON.stringify(this.globalData.chart_yw));
},
数据存入到全局
// index.js
import { formatTime } from "../../utils/util"; // 引入时间处理方法
// 在接收到的数据里转换成一条为[时间:数据值]的数据并存入到app.globalData
app.globalData[变量名].push([
formatTime(new Date()).slice(5), // 时间切割 /月/日 时分秒
值
]);
在图表页获取数据和显示出来
// 定义的初始折线图数据
const option = {
xAxis: {
type: "category",
data: ["none"],
},
yAxis: {
type: "value",
},
series: [
{
data: [0],
type: "line",
},
],
};
// 在onload里动态改变标题和读取缓存里的数据
onLoad(options) {
let data = JSON.parse(options.param);
wx.setNavigationBarTitle({ title: data.parameter + "记录", });
let canshu =
data.parameter === "温度" ? "wd" : data.parameter === "湿度" ? "sd" :
data.parameter === "光强" ? "gq" : "yw";
console.log("参数名", "chart_" + canshu);
console.log(app.globalData["chart_" + canshu]);
this.setData({ chartData: app.globalData["chart_" + canshu] });
},
// data里增加变量
chartData : []
// onReady里拿到数据并渲染出来
onReady: function () {
// 获取组件
this.ecComponent = this.selectComponent("#mychart-dom-bar");
this.init();
setTimeout(() => {
console.log(this.data.chartData);
this.data.chartData.length &&
this.chart.setOption({
xAxis: {
type: "category",
data: this.data.chartData.map((item) => item[0]),
},
yAxis: {
type: "value",
},
series: [
{
data: this.data.chartData.map((item) => item[1]),
type: "line",
},
],
});
}, 500);
},
十一、小程序配置发布
点击上传(注意 测试号无法上传)
完成上传后,需要登录到微信公众平台,在左侧“管理”–“版本管理”中,找到使用微信开发者工具上传的小程序版本,提交审核。提交代码至微信团队审核,审核通过后即可发布成为线上版本(公测期间不能发布)。
十二、MySQL 的安装和常用语句的使用
MySQL安装包
需要安装包的小伙伴可以进群自取
MySQL 安装步骤
!!!这里输入管理员密码 一定要谨记
MySQL 常用语句
MySQL 数据库中常用的一些 SQL 语句包括但不限于以下内容:
注意:在 MySQL 操作中所有的语句后面都必须带分号 → ;
-
连接 MySQL 服务器:
mysql -u username -p
这将提示输入密码,然后连接到本地 MySQL 服务器。
username
是你的 MySQL 用户名,一般为 root或直接打开 MySQL 命令行工具 输入密码即可连接
-
创建数据库:
CREATE DATABASE 数据库名;
-
选择(使用)数据库:
USE 数据库名;
-
显示所有数据库:
SHOW DATABASES;
-
创建表:
CREATE TABLE 表名 ( 列名1 数据类型 PRIMARY KEY, 列名2 数据类型, ... );
-
插入数据:
INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...);
-
查询数据:
SELECT * FROM 表名;
-
更新数据:
UPDATE 表名 SET 列名 = 新值 WHERE 条件;
-
删除数据:
DELETE FROM 表名 WHERE 条件;
-
删除表:
DROP TABLE 表名;
-
查看表结构:
DESC 表名;
十三、MySQL 可视化工具 Navicat 的安装和使用
Navicat 介绍
Navicat 是一款由 PremiumSoft CyberTech Ltd 开发的流行且功能强大的数据库管理和开发工具,它提供了一个直观、易于使用的图形用户界面(GUI)来简化数据库管理任务,并支持多种主流关系型数据库系统。通过
Navicat,用户可以在 Windows、macOS 及 Linux 平台上高效地设计数据库结构、执行 SQL 查询和脚本、管理数据表内容、创建图表及报告、进行数据迁移、同步以及备份与恢复操作。
Navicat 支持的主要数据库类型包括:
- MySQL
- MariaDB
- PostgreSQL
- Oracle
- SQL Server
- SQLite
- Amazon RDS
- Amazon Aurora
- Microsoft Azure
- Google Cloud 等云端数据库服务
其主要功能特点如下:
- 多连接管理:允许在同一程序中同时连接到多个数据库服务器。
- 数据库设计:使用可视化设计器创建、修改和管理数据库对象,如表、视图、存储过程、触发器等。
- 数据传输:在不同数据库之间进行数据迁移或同步,支持自定义字段映射和过滤条件。
- 数据备份与恢复:可安排自动备份计划,并能够将备份文件恢复到数据库中。
- 查询构建与运行:具有高级 SQL 编辑器,支持语法高亮、代码完成、实时错误检查和调试等功能。
- 模型比较与同步:可以比较数据库之间的结构差异,并生成同步脚本来更新目标数据库。
- 安全管理:管理用户权限和访问控制,实现对数据库安全性的有效监控。
- 批处理作业调度:设定定时任务执行一系列数据库操作,例如运行 SQL 脚本、导出数据等。
- 云数据库支持:无缝连接到各大云服务商托管的数据库实例。
总之,Navicat 旨在为数据库管理员、开发人员以及需要高效管理和开发数据库的用户提供一个全面、稳定且高效的解决方案。
Navicat 下载地址
https://navicat.com.cn/download/navicat-premium
连接本地数据库
注意主机为 localhost 端口为 3306
新建数据库
注意字符集一定要根据图中的来 不然不支持中文
新建表
在需要建表的数据库双击并找到表,右击新建表
输入表字段信息后点击保存,接着输入表名即可
十四、NodeJs 增删改查数据库示例
Express 介绍
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建
Web 服务器的。
创建所需要的库和表
建立库 esp32_records
CREATE DATABASE esp32_records
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_general_ci;
建立表 esp32_data
create table `esp32_data`(
id int(8) primary key auto_increment comment '主键',
sensor_name varchar(30) not null comment '传感器名',
sensor_value int(8) not null comment '值',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP comment '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP comment '修改时间'
);
安装 Express 脚手架和创建 Express 项目
全局安装Express脚手架
npm install -g express-generator
创建项目
express 项目名
设置淘宝镜像
npm config set registry https://registry.npmmirror.com
安装依赖
npm install
启动项目
npm start
全局安装 nodemon - 作用:监控 node.js 源代码的任何变化和自动重启你的服务器,不用每次改完代码手动重启
npm install -g nodemon
在 package.json 文件中更改"start": "node ./bin/www"
为"start": "nodemon ./bin/www"
安装 mysql 依赖
npm install mysql
代码编辑器
下载编辑器
vscode 官网
腾讯管家 vscode 下载界面 速度快,选择立即下载-直接下载
安装一些必备插件(在 vscode 插件 tab 页下载)
Chinese (Simplified) (简体中文)
ES7+ React/Redux/React-Native snippets
Prettier - Code formatter
Material Icon Theme
操作数据库
MySQL 配置文件
// options/mysqlOption.js
const mysql = require("mysql");
let options = {
host: "localhost",
//port:"3306",// 可选,默认3306
user: "root", // 这里改成你自己的数据库账号
password: "admin", // 这里改成你自己的数据库密码
database: "esp32_records" // 这里改成你自己的数据库的名字 不是表名
};
//创建与数据库进行连接的连接对象
const connection = mysql.createConnection(options);
module.exports = connection;
连接 MySQL
connection.connect(err => {
if (!err) {
// 数据库连接成功
console.log("数据库连接成功");
}
});
测试用的 sql 语句
SELECT * FROM esp32_data
INSERT INTO esp32_data (sensor_name,sensor_value) VALUES ('SMOKE', 20);
UPDATE esp32_data SET sensor_value = 60 WHERE id = 1;
DELETE FROM esp32_data WHERE id = 1
执行 sql 语句方法
注:方便教学,这里采用简单的直接拼接方式,但是可能会有 sql
注入的风险,有兴趣的小伙伴可以去了解一下用**[Knex.js](https://www.knexjs.cn/)
**
connection.query("sql语句", (err, result, fields) => {
if (!err) {
// 执行成功
return;
}
// 执行失败
});
格式化时间工具函数
/* ISO 8601 时间格式转换为其他自定义格式 */
function handleData(isoString) {
const date = new Date(isoString);
const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
const formattedTime = `${date.getHours().toString().padStart(2, "0")}:${date
.getMinutes()
.toString()
.padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
return `${formattedDate} ${formattedTime}`;
}
module.exports = handleData;
十五、NodeJs 实现小程序历史数据接口
两种 HTTP 请求方法:GET 和 POST 介绍
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
- GET- 从指定的资源请求数据。
- POST- 向指定的资源提交要被处理的数据。
GET 提交参数一般显示在 URL 上,POST 通过表单提交不会显示在 URL 上,POST 更具隐蔽性:
思路和获取接口参数
从前端传参获取需要的数据 -> 再拼接 SQL 语句来执行 → 再返回结果给前端
获取 get 的参数 let { 参数名 , 参数名 } = req.query
获取 post 的参数 let { 参数名 , 参数名 } = req.body;
这里的参数名一定要传过来 不然获取的值将是 undefined
数据记录增删改查路由实现
增加数据记录接口
/* 新增 */
router.post("/api/add_record", function (req, res, next) {
// 从请求体中获取 sensor_name 和 sensor_value 的值
let { sensor_name, sensor_value } = req.body;
// 拼接 sql 语句
let sqlStr = `INSERT INTO esp32_data (sensor_name,sensor_value) VALUES ('${sensor_name}', ${sensor_value});`;
// 在控制台打印需要执行的 sql 语句
console.log(sqlStr);
// 执行sql语句 通过connection.query方法
connection.query(sqlStr, (err, result, fields) => {
if (!err) {
// 如果没有错误,返回添加成功的信息
res.json({ code: 200, msg: "添加成功" });
return;
}
// 如果有错误,返回添加失败的信息
res.json({ code: 404, msg: "添加失败" });
});
});
删除数据记录接口
/* 删除 */
router.post("/api/del_record", function (req, res, next) {
let { id } = req.body;
let sqlStr = `DELETE FROM esp32_data WHERE id = ${id}`;
console.log(sqlStr);
connection.query(sqlStr, (err, result, fields) => {
if (!err) {
res.json({ code: 200, msg: "删除成功" });
return;
}
res.json({ code: 404, msg: "删除失败" });
});
});
修改数据记录接口
/* 修改 */
router.post("/api/update_record", function (req, res, next) {
let { id, sensor_value } = req.body;
let sqlStr = `UPDATE esp32_data SET sensor_value = ${sensor_value} WHERE id = ${id};`;
console.log(sqlStr);
connection.query(sqlStr, (err, result, fields) => {
if (!err) {
res.json({ code: 200, msg: "修改成功" });
return;
}
res.json({ code: 404, msg: "修改失败" });
});
});
查询数据记录接口
/* 查询 */
router.get("/api/get_records", function (req, res, next) {
let { sensor_name, create_time } = req.query;
let sqlStr = `SELECT * FROM esp32_data WHERE sensor_name = '${sensor_name}' AND DATE(create_time) = '${create_time}'`;
console.log(sqlStr);
// http://localhost:3000/api/get_records?sensor_name=SMOKE&create_time=2024-02-28
connection.query(sqlStr, (err, result, fields) => {
if (!err) {
console.log("查询结果:" + result.length + "条");
// 对查询结果进行处理
let resultData = result.map(item => {
return {
...item,
create_time: handleData(item.create_time), // 调用工具函数处理时间
update_time: handleData(item.update_time)
};
});
res.json({ code: 200, msg: "查询成功", data: resultData });
return;
}
res.json({ code: 404, msg: "查询失败" });
});
});
十六、接口调试工具 Apifox 的安装和使用
官方给出的介绍:
Apifox 是接口管理、开发、测试全流程集成工具,定位 Postman + Swagger + Mock +
JMeter。通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好接口文档,接口调试、数据
Mock、接口测试就可以直接使用,无需再次定义;接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档定义完全一致。高效、及时、准确!
下载 Apifox
Apifox 官网链接
新建项目
我们给项目起名为 esp32
点击新增环境 新增名为“本地环境 3000 端口”的环境
新建接口
选择get
或者post
类型,输入接口命名,编辑请求参数,然后点击保存,这样我们就新增了一个接口
运行接口测试
点击运行按钮
填写参数后点击发送可调用接口进行测试
最后
把我们需要用到的四个接口(增删改查)都添加并测试有没有问题,没有问题说明接口已经创建并测试完毕了
十七、小程序结合后端实现增删改查历史数据
下载 tabbar 图标并设置 tabbar
下载图标阿里巴巴矢量图标库
命名方式和路径如下图所示
app.json 的 pages 里加入 "pages/record_crud/record_crud"
页面
在 app.json 里加入
"tabBar": {
"borderStyle": "white",
"selectedColor": "#15A1EA",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/tabbar/tab1.png",
"selectedIconPath": "images/tabbar/tab1-c.png"
},
{
"pagePath": "pages/record_crud/record_crud",
"text": "记录",
"iconPath": "images/tabbar/tab2.png",
"selectedIconPath": "images/tabbar/tab2-c.png"
}
]
},
集中管理接口调用函数
新建api
目录并新建index.js
文件
// api/index.js
const BASE_URL = "http://localhost:3000/api";
const app = getApp();
// 查询
export function get_records(sensor_name, create_time) {
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE_URL}/get_records`,
data: {
sensor_name,
create_time: `${create_time}`
},
success(response) {
resolve(response);
},
fail(e) {
reject(e);
}
});
});
}
// 删除
export function del_record(id) {
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE_URL}/del_record`,
method: "POST",
data: {
id
},
success(response) {
wx.showToast({ title: "删除成功", icon: "none" });
resolve();
},
fail(e) {
wx.showToast({ title: "删除失败", icon: "none" });
reject(e);
}
});
});
}
// 新增
export function add_record(sensor_name, sensor_value) {
wx.request({
url: `${BASE_URL}/add_record`,
method: "POST",
data: {
sensor_name,
sensor_value
},
success(response) {},
fail(e) {}
});
}
// 修改
export function update_record(id, sensor_value) {
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE_URL}/update_record`,
method: "POST",
data: {
id,
sensor_value
},
success(response) {
resolve();
},
fail(e) {}
});
});
}
更改数据记录逻辑
将之前编写的数据记录存入本地缓存的逻辑删除
将首页数据存入本地缓存方式改用存入数据库接口
import { add_record } from "../../api/index";
add_record("TEMPERATURE", Number(getMessageObj.TEMPERATURE));
add_record("HUMIDITY", Number(getMessageObj.HUMIDITY));
add_record("LIGHT_INTENSITY", Number(getMessageObj.LIGHT_INTENSITY));
add_record("SMOKE", Number(getMessageObj.SMOKE));
抽离图表组件供多个页面复用
新建components
目录并新建record_chart
组件
record_chart.json
里的usingComponents
字段增加 "ec-canvas": "../../ec-canvas/ec-canvas”
导入 echarts 和封装的调用接口函数
import * as echarts from "../../ec-canvas/echarts.min";
import { get_records } from "../../api/index";
获取全局实例和定于中文字段和传感器的对应关系
const app = getApp();
const sensorMapObj = {
温度: "TEMPERATURE",
湿度: "HUMIDITY",
光强: "LIGHT_INTENSITY",
烟雾: "SMOKE"
};
定义图表初始值和设置图表值函数
/* 设置chart方法 */
function setOption(chart) {
let option = {
grid: { containLabel: true },
tooltip: { show: true, trigger: "axis" },
xAxis: { type: "category", data: ["none"] },
yAxis: { type: "value" },
series: [{ data: [0], type: "line" }],
dataZoom: [
{ type: "slider", start: 0, end: 100 },
{ type: "inside", start: 0, end: 100 }
]
};
chart.setOption(option);
}
定义组件属性,用于在外面传入
properties: {
sensorZhName: { type: String, value: "" }, // 传感器中文名字
searchData: { type: String, value: "" }, //查询日期
isNeedGetData: { type: Boolean, value: false }, // 是否需要接口获取数据
chartData: { type: Array, value: () => [] } // 图表数据
// myProperty2: String // 简化的定义方式
},
定义组件 data 数据
ec: { lazyLoad: true }, // 将 lazyLoad 设为 true 后,需要手动初始化图表
isLoaded: false,
isDisposed: false,
chartData: []
定义组件生命周期函数,用于处理数据
lifetimes: {
attached() {
if (this.properties.isNeedGetData) {
// console.log("从接口获取数据");
get_records(
sensorMapObj[this.properties.sensorZhName],
this.properties.searchData
).then((response) => {
this.setData({ chartData: response.data.data });
});
} else {
this.setData({ chartData: this.properties.chartData });
}
},
ready() {
// 获取组件节点
this.ecComponent = this.selectComponent("#mychart-dom-bar");
this.init();
setTimeout(() => {
console.log("图表数据", this.data.chartData);
this.data.chartData.length &&
this.chart?.setOption({
xAxis: {
type: "category",
data: this.data.chartData.map((item) =>
item.create_time.slice(-5)
),
},
series: [
{
data: this.data.chartData.map((item) => item.sensor_value),
type: "line",
},
],
});
}, 1000);
},
},
定义组件方法列表
// 点击按钮后初始化图表
init() {
this.ecComponent.init((canvas, width, height, dpr) => {
// 获取组件的 canvas、width、height 后的回调函数
// 在这里初始化图表
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr, // new
});
setOption(chart);
// 将图表实例绑定到 this 上,可以在其他成员函数(如 dispose)中访问
this.chart = chart;
this.setData({ isLoaded: true, isDisposed: false });
// 注意这里一定要返回 chart 实例,否则会影响事件处理等
return chart;
});
},
dispose() {
this.chart && this.chart.dispose();
this.setData({ isDisposed: true });
},
编写组件的页面结构
<ec-canvas
wx:if="{{!isDisposed}}"
id="mychart-dom-bar"
canvas-id="mychart-bar"
ec="{{ ec }}"
></ec-canvas>
编写组件的样式
ec-canvas {
width: 100%;
height: 90%;
}
至此我们的图表组件就定义完成了!可以在多个页面使用了
制作数据记录页面
拷贝首页的顶部图表和标题过来
抽离主页顶部样式为全局样式
编写头部下拉选择日期和传感器名盒子
编写数据列表展示的样式
引入图表组件并编写切换图表和列表展示按钮来切换
选择日期、传感器名、展示模式样式编写
<view class="select-box">
<view class="title"
>历史数据查询
<van-icon style="margin-left: 40rpx" bind:tap="selectData" name="replay" />
</view>
<picker
mode="date"
value="{{timeValue}}"
start="2024-01-01"
end="2034-01-01"
bindchange="bindDateChange"
>
<view class="picker-box">
<view> 查询日期: </view>
<view class="picker-font-color">{{timeValue}}</view>
</view>
</picker>
<picker bindchange="bindPickerChange" value="{{index}}" range="{{sensorNameList}}">
<view class="picker-box">
<view> 传感器名:</view>
<view class="picker-font-color">{{sensorNameList[sensorNameIndex]}} </view>
</view>
</picker>
<picker bindchange="bindrevealTypeChange" value="{{revealTypeIndex}}" range="{{revealList}}">
<view class="picker-box">
<view> 展示模式:</view>
<view class="picker-font-color">{{revealList[revealTypeIndex]}} </view>
</view>
</picker>
</view>
在 util.js 写入工具函数来获取当前年月日 YYYY-MM-DD
function getCurrYearMonthDate() {
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = (currentDate.getMonth() + 1).toString().padStart(2, "0");
const day = currentDate.getDate().toString().padStart(2, "0");
const formattedDate = `${year}-${month}-${day}`;
// console.log(formattedDate); // 输出当前日期,例如 "2024-01-14"
return formattedDate;
}
// 并导出
在记录页面导入获取时间函数和接口
import {getCurrYearMonthDate} from '../../utils/util’
import { get_records, del_record, update_record } from "../../api/index";
定义 data 的一些参数并赋予默认值
revealList: ["列表", "图表"], // 数据展示模式
revealTypeIndex: 0, // 连接方式的类型值 0列表 1图表
showChart: false, // 控制图表显示隐藏
timeValue: getCurrYearMonthDate(), //时间选择框的值
sensorNameList: ["温度", "湿度", "光强", "烟雾"], //传感器下拉数据源
sensorEnList: ["TEMPERATURE", "HUMIDITY", "LIGHT_INTENSITY", "SMOKE"], //传感器下拉数据源英文
sensorNameIndex: "0", //传感器下拉值的index
sensorNameEnValue: "TEMPERATURE",
sensorMap: {
TEMPERATURE: "°C",
HUMIDITY: "%rh",
LIGHT_INTENSITY: "lx",
SMOKE: "ppm",
}, //传感器单位
sersorInfoList: [], // 数据列表
在页面首次进入函数里判断如果有值就查询数据
onLoad() {
if (this.data.timeValue && this.data.sensorNameEnValue) this.selectData();
},
定义选择器改变事件
// 日期改变事件
bindDateChange(e) {
this.setData({ timeValue: e.detail.value });
this.selectData();
},
// 传感器改变事件
bindPickerChange(e) {
this.setData({
sensorNameIndex: e.detail.value,
sensorNameEnValue: this.data.sensorEnList[Number(e.detail.value)],
});
this.selectData();
},
// 切换展示模式
bindrevealTypeChange(e) {
this.setData({ revealTypeIndex: Number(e.detail.value) });
if (e.detail.value === '1') {
this.setData({ showChart: true });
}
},
在页面的 json 文件里引入组件
"record_chart":"../../components/record_chart/record_chart”
在 app.json 引入
"van-cell": "@vant/weapp/cell/index", "van-cell-group": "@vant/weapp/cell-group/index"
编写页面增删改查方法
// 获取数据记录
selectData() {
if (this.data.revealTypeIndex === 1) this.setData({ showChart: false });
get_records(this.data.sensorNameEnValue, `${this.data.timeValue}`).then(
(response) => {
this.setData({ sersorInfoList: response.data.data });
if (this.data.revealTypeIndex === 1) this.setData({ showChart: true });
}
);
},
// 删除记录
delRecord(e) {
let that = this;
wx.showModal({
title: "系统提示",
content: "是否确认删除本条消息",
complete: (res) => {
if (res.confirm) {
del_record(e.currentTarget.dataset.id).then(() => {
that.selectData();
});
}
},
});
},
// 修改记录
updateRecord(e) {
wx.showModal({
title: "修改记录",
editable: true,
placeholderText: "请输入需要修改的值",
confirmText: "确认修改",
complete: (res) => {
if (res.confirm) {
if (res.content && !isNaN(Number(res.content))) {
// console.log(Number(res.content));
update_record(e.currentTarget.dataset.id, Number(res.content)).then(
() => {
wx.showToast({ title: "修改成功", icon: "none" });
this.selectData();
}
);
} else {
wx.showToast({ title: "输入有误", icon: "none" });
}
}
},
});
},
编写页面展示列表和图表
<!-- 列表模式 -->
<view wx:if="{{revealTypeIndex===0}}" class="list-mode">
<scroll-view scroll-y="true" style="height: calc(100vh - 620rpx)">
<van-cell-group inset border="{{false}}">
<van-cell
wx:for="{{sersorInfoList}}"
title="{{item.sensor_value + sensorMap[sensorNameEnValue]}}"
wx:key="item"
data-id="{{item.id}}"
data-value="{{item.sersor_value}}"
bind:longpress="delRecord"
bind:tap="updateRecord"
value="{{item.create_time}}"
/>
</van-cell-group>
</scroll-view>
</view>
<!-- 图表模式 -->
<view
wx:if="{{revealTypeIndex === 1 && showChart}}"
style="height: calc(100vh - 530rpx); margin: 0 2rpx 4rpx"
>
<record_chart chart-data="{{sersorInfoList}}" />
</view>
最后附上页面样式文件
.select-box {
margin: 38rpx 32rpx 0;
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.3);
border-radius: 32rpx;
padding: 20rpx;
background-color: #ffffff;
}
.title {
text-align: center;
font-size: 44rpx;
margin-bottom: 20rpx;
}
.picker-box {
display: flex;
margin-bottom: 10rpx;
}
.list-mode {
background-color: #ffffff;
margin: 38rpx 32rpx 0;
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.3);
border-radius: 32rpx;
}
.picker-font-color {
color: #969799;
}
改写之前的图表页面逻辑
将 chart 页面删除,新建 record 页面,改成调用我们写的图表组件
// record.js
import { getCurrYearMonthDate } from "../../utils/util";
const app = getApp();
Page({
data: {
showChart: false, // 控制图表显示隐藏
sensorZhName: "", // 传感器英文名
timeValue: getCurrYearMonthDate() //时间选择框的值
},
onLoad({ data }) {
// console.log(JSON.parse(data));
let { name, parameter, unit } = JSON.parse(data) || {};
wx.setNavigationBarTitle({ title: `${parameter}记录(单位${unit})` });
this.setData({ sensorZhName: parameter, showChart: true });
},
bindDateChange(e) {
this.setData({ showChart: false, timeValue: e.detail.value });
setTimeout(() => {
this.setData({ showChart: true });
}, 500);
}
});
<!-- record.wxml -->
<view class="page-container">
<picker
mode="date"
value="{{timeValue}}"
start="2024-01-01"
end="2034-01-01"
bindchange="bindDateChange"
>
<view class="picker-box">
<view> 查询日期: </view>
<view class="picker-font-color">{{timeValue}}</view>
</view>
</picker>
<record_chart
is-need-get-data
sensor-zh-name="{{sensorZhName}}"
search-data="{{timeValue}}"
wx:if="{{showChart}}"
/>
</view>
/* record.wxss */
.page-container {
width: 100vw;
height: 92vh;
}
.picker-box {
display: flex;
justify-content: space-between;
margin: 10rpx 72rpx;
}
{
"usingComponents": {
"record_chart": "../../components/record_chart/record_chart"
},
"navigationBarTitleText": "记录",
"navigationStyle": "default"
}
更改跳转 record 页面逻辑文章来源:https://www.toymoban.com/news/detail-854452.html
// wxml增加
data-param="{{item}}"
let data = e.currentTarget.dataset.item;
wx.navigateTo({
url: "/pages/record/record?data=" + JSON.stringify(data)
});
至此本篇教程已经到此完毕!如果大家感兴趣的话后续将会出更多教程文章来源地址https://www.toymoban.com/news/detail-854452.html
到了这里,关于基于ESP32+微信小程序的物联网应用(小程序部分)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!