前言

三维交互可视化平台(智慧海上牧场平台)学习开发之Vue(一)
三维交互可视化平台(智慧海上牧场平台)学习开发之Flask+Vue+Mysql(二)
三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql实战(三)
之前已经写了三章了,之前方向本来是三维交互的,现在发现要学习的路程还是比较长,三维交互先放放吧,当前目标就是把基于物联网的设备调试好,然后把整个平台部署到服务器上。基于前几篇的基础上,本篇主要介绍的是websocket双工通讯,以及海康监控摄像头的部署调用。涉及的问题难点就在于当点击左侧选项时路由跳转过去,当回到总览界面,数据显示便丢失了,不仅如此,监控视频画面也是这样,现在就是要解决这个问题,接下来一边编写界面一边找解决办法。
大致框架搭建好了,现在开始填充内容,经过看视频,查资料,要解决数据问题,就是在没关掉路由(例如/overview)之前,overview的界面以及数据一直是保存在后台和前端的,什么意思呢,别着急,请往下看。(备注)
以下可能会缺失一些代码,大家可以去我之前的websocket文章中找思路。
Flask+echarts+mysql+websocket+vue实现前后端分离数据可视化刷新
先展示成果:

智慧渔业养殖系统视频演示

一、后台编写

思路:本质是数据的展示,因此只是调用数据库查询方法给前端使用即可。由于物联网水质监测仪目前还未调试好,因此模拟数据采集到数据库,做一个定时器做数据插入的功能(预计本周可以调试完毕,到时候传感器会定时采集数据传入服务器数据库,和此效果相同,先预留读取接口),然后websocket保持对服务器的读取,查询到数据返回给前端。

db_operate.py

import sys
sys.path.append(r"E:\pycharm2020\projects\platform1.0")
import json
import time
import randomfrom flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pymysql
import threadingfrom util.ecodings import class_to_dict, Decimal_and_DateEncoder
from util.settings import DevelopmentConfigpymysql.install_as_MySQLdb()app = Flask(__name__)
# 读取配置,包含数据库配置
app.config.from_object(DevelopmentConfig)# 创建数据库sqlalchemy工具对象
db = SQLAlchemy(app)# Flask从数据库已有表自动生成
# model flask-sqlacodegen "mysql+pymysql://root:root@120.78.94.58:3306/monitor_sys_data" --tables
# monitor_data --outfile "model.py" --flask
class MonitorDatum(db.Model):__tablename__ = 'monitor_data'id = db.Column(db.Integer, primary_key=True)date_time = db.Column(db.DateTime)water_type = db.Column(db.Float(asdecimal=True))device_id = db.Column(db.Float(asdecimal=True))temperature = db.Column(db.Float(asdecimal=True))ph = db.Column(db.Float(asdecimal=True))solinity = db.Column(db.Float(asdecimal=True))dissolved_oxygen = db.Column(db.Float(asdecimal=True))light = db.Column(db.String(6))velocity = db.Column(db.String(6))data_type = db.Column(db.Float(asdecimal=True))def insert():print("定时器启动了")print(threading.current_thread())  # 查看当前线程record_t = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())temp = round(random.uniform(15, 25), 2)  # 产生一个0-40之间的数字,保留两位小数ph = round(random.uniform(7, 8), 3)sol = round(random.uniform(100, 150), 1)dod = round(random.uniform(5, 8), 2)# 预留光照,如果是传入为字符串,需要将小数转为字符串,再插入数据库ins = MonitorDatum(date_time=record_t, temperature=temp, ph=ph, solinity=sol, dissolved_oxygen=dod, light='30')db.session.add(ins)db.session.commit()print('插入成功!')timer = threading.Timer(5, insert)  # 在insert函数结束之前我再开启一个定时器timer.start()def create():# 创建所有表db.create_all()def drop():# 删除所有表db.drop_all()def query():# 清空缓存db.session.commit()# 查询最近一条数据# 只有最后加.all()才能读到实例,order_by和limit是条件查询new = db.session.query(MonitorDatum).order_by(MonitorDatum.id.desc()).limit(1).all()# [{'temperature': 23.18, 'id': 5, 'record_t': datetime.datetime(2022, 10, 8, 10, 41, 35)}]  listresult = class_to_dict(new)# 取的时间json.dumps无法对字典中的datetime时间格式数据进行转化。因此需要添加特殊日期格式转化result[0] = json.loads(json.dumps(result[0], cls=Decimal_and_DateEncoder))# print(result[0])  # {'temperature': 23.18, 'id': 5, 'record_t': '"2022-10-08 10:41:35"'}# 由于数据库中光照的类型只能为字符型"light": '30',在这里需要把光照转为int型,才能传给前端显示result[0]['light'] = int(result[0]['light'])return result[0]# {"temperature": 23.72, "id": 16, "water_type": null, "solinity": 102.1, "light": 30,# "data_type": null, "device_id": null, "date_time": "2022-10-17 00:12:37", "ph": 7.731,# "dissolved_oxygen": 6.97, "velocity": null}  返回是一个字典if __name__ == '__main__':# res = query()# print(res)# # print(type(res))# print(res['date_time'])# print(type(res['date_time']))# print(res['temperature'])# print(type(res['temperature']))# 创建一个定时器,在程序运行在之后我开启一个insert函数t1 = threading.Timer(5, function=insert)  # 第一个参数是时间,例:过5s之后我执行后面的一个函数,开启一个线程t1.start()
# 控制台运行,5s定时向数据库插入
python db_operate.py

app_db_data.py

import syssys.path.append(r"E:\pycharm2020\projects\platform1.0")
from db_manage.db_operate import query
from threading import Lock
from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_cors import CORSapp = Flask(__name__)
CORS(app)  # 跨域问题
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO()
socketio.init_app(app, async_mode=None, cors_allowed_origins='*')
thread = None
thread_lock = Lock()# 后台线程 产生数据,即刻推送至前端
def background_thread():count = 0while True:socketio.sleep(5)res = query()# 1个时间,5个水质参数record_t = res['date_time']temperature = res['temperature']ph = res['ph']sol = res['solinity']dod = res['dissolved_oxygen']light = res['light']socketio.emit('server_response',{'data': [record_t, temperature, ph, sol, dod, light]},namespace='/test')@socketio.on('conn', namespace='/test')
def test_connect():print('触发')global threadwith thread_lock:if thread is None:thread = socketio.start_background_task(target=background_thread)if __name__ == '__main__':socketio.run(app, host='127.0.0.1', port='5000', debug=True)
# 控制台运行,服务器此时会不断地向前端推送数据(1个时间,5个水质参数)
python app_db_data.py

这是程序定时插入的数据库数据,后面开始光照强度是固定“30”,其余的都不需要。

二、前端编写

1.总体架构

vue就不介绍了,之前的文章都有写过,这里挑重要点写:
首先创建好vue项目后,登录啥的我也不写了,直接写Home.vue(主页),包含header(顶部栏)、sidebar(侧边栏)和content(主要内容)3个组件,顶部栏和侧边栏都是固定的,变化的是content,项目结构如下:

本demo只写主界面中的基地总览和水质检测两部分,前端设计的还很烂,慢慢完善吧。

main.js: 引入jquery、ElementUI、socketio以及echarts

import Vue from 'vue'
import App from './App'
import router from './router'
import $ from 'jquery'Vue.prototype.$ = $// 使用element-ui
// 1 引入element-ui样式
import 'element-ui/lib/theme-chalk/index.css'
// 1 引入element-ui所有组件
import ElementUI from 'element-ui'
Vue.use(ElementUI)import VueSocketIO from 'vue-socket.io'
import socketIO from 'socket.io-client'Vue.use(new VueSocketIO({debug: true,// "ws://域名:端口号/namespace"connection: socketIO.connect('ws://127.0.0.1:5000/test', {autoConnect:false})
}))
import echarts from "echarts";
Vue.prototype.$echarts = echarts;
Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({el: '#app',router,components: { App },template: '<App/>'
})

index.js: 路由跳转

import Vue from 'vue'
import Router from 'vue-router'
import Test from "../views/Test";
Vue.use(Router)export default new Router({// component引入有以下两种方式,其中直接写组件名称的需要在最上方引入。routes: [{path: '/test',name: 'Test',component: Test},{path: '/home',name: 'Home',component: () => import ( "../views/Home.vue"),children: [{path: '/overview',name: 'Overview',component: () => import ( "../views/Overview.vue"),},{path: '/monitor',name: 'Monitor',component: () => import ( "../views/Monitor.vue"),},]}]
})

在vue项目目录下安装less样式 npm install -g less
注意在app.vue中也需要引入全局样式global.less:

@import url('此处为iconfont的引用');
* {margin: 0;padding: 0;
}html,
body,
#app,
.wrapper {width: 100%;height: 100%;overflow: hidden;}

现在下面这个是分栏:
内容部分,elementIU布局把一行为24份
上部分:分左(监控占比5)右(介绍占比1)。
下部分:flex 5 份作为echarts图表。

上部分:gutter为分栏之间间隔
<el-row :gutter="20"><!-- 5:1 改成 18:6我感觉更舒服,所以这里就改了--><el-col :span="18"><div class="grid-content bg-purple"></div></el-col><el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
</el-row>
下部分:挤满,五等份,每份应该是4.8,但不支持小数,因此写5
<el-row type="flex" class="row-bg" justify="space-around"><el-col :span="5"><div class="grid-content bg-purple"></div><div>图一</div></el-col><el-col :span="5"><div class="grid-content bg-purple"></div><div>图二</div></el-col><el-col :span="5"><div class="grid-content bg-purple"></div><div>图三</div></el-col><el-col :span="5"><div class="grid-content bg-purple"></div><div>图四</div></el-col><el-col :span="5"><div class="grid-content bg-purple"></div><div>图五</div></el-col>
</el-row>
</el-row>

2. 4G海康摄像头调用

购买了摄像头之后,我是注册了萤石云,直接走API调用的接口,目前还不清楚如何采用websocket去操控监控的开关,免费版的是有并发上限的,一旦以下函数调用超过3次,就会超过权限:

ezopenInit(value1){if(value1===true){var domain = "https://open.ys7.com";var EZOPENDemo;window.EZOPENDemo = EZOPENDemo;EZOPENDemo = new EZUIKit.EZUIKitPlayer({id: 'playWind',width: 1300,height: 600,template: "pcLive",url: "ezopen://CHVOUB@open.ys7.com/产品编号/1.live",accessToken: "自行申请"});}else {// 关闭视频,断开视频连接}

3.echarts图表

简单说一下,代码可能看不太明白,我封装了几个画图的方法,然后在vue采用websocket读取到数据以后传输到封装好的方法里,就可以。
draw.js:

import echarts from "echarts";
export function myChart(elementID) {// 拿到一个实例return echarts.init(document.getElementById(elementID))
}
export function drawNone(elementID, x1, y1, y2, y3, unit, clazz) {//自己写吧,代码太多了放不下}
export function drawGet(elementID,x1, y1, y2, y3) {//自己写吧,思路就这样}

Overview.vue:

methods:
socketSendmsg(value2) {if(value2 === true){// 开始连接socketthis.$socket.open();console.log('现在开始发送消息')// conn 是与后端约定好的名称this.$socket.emit('conn');console.log('发送过去了')var record_t = ["","","","","","","","","",""],tem = [0,0,0,0,0,0,0,0,0,0],ph = [0,0,0,0,0,0,0,0,0,0],sol = [0,0,0,0,0,0,0,0,0,0],dod = [0,0,0,0,0,0,0,0,0,0],light = [0,0,0,0,0,0,0,0,0,0]drawNone('chart1',record_t,tem,tem,tem,'℃','温度')drawNone('chart2',record_t,ph,ph,ph,' ','PH')drawNone('chart3',record_t,sol,sol,sol,'%','盐度')drawNone('chart4',record_t,dod,dod,dod,'mg/L','溶解氧')drawNone('chart5',record_t,light,light,light,'Lux','光照强度')// server_response 是前端传过来的信息说明this.sockets.subscribe('server_response', (res) => {// 测试打印console.log(res.data[0]);console.log(res.data[1]);console.log(res.data[2]);console.log(res.data[3]);console.log(res.data[4]);console.log(res.data[5]);// 每次获取到最新数据之后应该是做出对图标的插入操作record_t.push(res.data[0]);tem.push(parseFloat(res.data[1]));ph.push(parseFloat(res.data[2]));sol.push(parseFloat(res.data[3]));dod.push(parseFloat(res.data[4]));light.push(parseFloat(res.data[5]));if(record_t.length >= 10){record_t.shift();tem.shift();ph.shift();sol.shift();dod.shift();light.shift();}drawGet('chart1',record_t,tem,tem,tem)drawGet('chart2',record_t,ph,ph,ph)drawGet('chart3',record_t,sol,sol,sol)drawGet('chart4',record_t,dod,dod,dod)drawGet('chart5',record_t,light,light,light)})}else {this.$socket.disconnect();}

4.主界面完成

往里面添加内容,这里我只运行了查询的后台代码,没有运行插入,因此线都是平的。

5.水质监测部分(侧边栏第二个)

先设计一下,如图所示:

然后页面设计:学到一个新知识点空格占位符3个,&nbsp; &ensp; &emsp;

注入逻辑部分:
1.以温度为例,显示默认获取过去7天的数据,每一日的最高值、最低值和平均值都需要计算,我打算这三个值都从数据库语句中计算好拿出来,between的区间就由前端传输给后端,获取的最值平均值再传回给前端,sql语句如下:
– 筛选出2022-10-18日当天的ph最高值、平均值和最低值
最高值:SELECT max(ph) ph FROM monitor_data WHERE date_time between ‘2022-10-18 00:00:00’ and ‘2022-10-18 23:59:59’
在这里我是用的flask-SQLAlchemy
查询得到最大值例子:Max = db.session.query(func.max(temp)).filter(MonitorDatum.date_time.like(f’{start}%')).all()
平均值:SELECT avg(ph) ph FROM monitor_data WHERE date_time between ‘2022-10-18 00:00:00’ and ‘2022-10-18 23:59:59’ ; (这个查询出来是多小数位的,需要后处理为保留小数点两位)
最低值:SELECT min(ph) ph FROM monitor_data WHERE date_time between ‘2022-10-18 00:00:00’ and ‘2022-10-18 23:59:59’
2.读取历史数据查询选择框:Monitor.vue

// template<el-row default-active="1" class="button_choose"><!--如果点击温度,传一个公共参数=1,点击ph,公共参数=2,--><el-button type="primary" :plain="isPlain[0]" @click="setFlag(0)" v-model="timeAndTem">温度</el-button><el-button type="primary" :plain="isPlain[1]" @click="setFlag(1)" v-model="timeAndPh">Ph</el-button><el-button type="primary" :plain="isPlain[2]" @click="setFlag(2)" v-model="timeAndSol">盐度</el-button><el-button type="primary" :plain="isPlain[3]" @click="setFlag(3)" v-model="timeAndDod">溶氧量</el-button><el-button type="primary" :plain="isPlain[4]" @click="setFlag(4)" v-model="timeAndLight">光照强度</el-button>
</el-row>// js
methods:{// 按钮组setFlag(index){this.isPlain = [true,true,true,true,true]if(index === 0){//当index=1,数据是温度,需要调整到温度,缺少这部分代码//设置温度为非朴素按钮,其他为朴素按钮this.isPlain[0] = falsethis.isPlain[1] = truethis.isPlain[2] = truethis.isPlain[3] = truethis.isPlain[4] = true}else if (index === 1){this.isPlain[0] = truethis.isPlain[1] = falsethis.isPlain[2] = truethis.isPlain[3] = truethis.isPlain[4] = true。。。设定}// 查询按钮date_query(start,end){// 获取start和end之间差几天,for循环 for (i=1,i<difDay,i++){从start开始查询}// 封装好的difference方法,计算两个日期的时间差let day = difference(start,end)console.log(day)let start1 = new Date(start)let dateTemp = start1.setDate(start1.getDate() + 1 )   //增加1天let time2 = dateFormat(new Date(dateTemp),'yyyy-MM-dd')console.log(time2)//想办法日期做减法,这里得到day=null,这种方法不行,想过!!!!var date = {"start":start,"end":end,"day":day}//当点击查询按钮时,if (this.isPlain[0] === false) {// console.log('温度测试') //成功const path = '/api/tem_query';console.log(path)axios.post(path,date).then((res) => {console.log(res.data)let date = res.data["data"][0]let max = res.data["data"][1]let min = res.data["data"][2]let avg = res.data["data"][3]draw_date("history_chart",date,max,min,avg,"℃",'温度历史值统计')})//这里只写一个if,其他同理

3.后台部分处理前端发送来的post请求,查询到数据中的值返回过去,参考1.的数据库查询逻辑:

@app.route("/tem_query", methods=['POST', 'GET'])
def tem():if request.method == 'POST':print('执行')# 获取vue中传递的值date = request.get_data()  # byte类型string = date.decode('utf-8', 'ignore')  # 转为Strdic = json.loads(string)  # 转为dic# print(dic)# dic = {'start': '2022-10-18', 'end': ' 2022-10-24','day':1}# 现在可以写数据库查询逻辑, dic['day']get_data = query_three("temperature", dic['start'], dic['day'])# (['2022-10-20', '2022-10-21'], [24.95, 24.97], [15.39, 15.62], [19.23, 21.1])print(get_data[0])res = {"data": get_data}return resreturn "返回值来咯!"

结语

还有一些功能写不下了,下一章再更新,如果觉得思路对你有帮助,请点个赞!

三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql+websocket 实战(四)相关推荐

  1. 三维交互可视化平台(智慧海上牧场平台)学习开发之Vue(一)

    前言 最近找导师重新更换了研究方向,学的东西还是蛮杂的,本来就是一个代码菜鸟,捣鼓一大堆,全栈开发.各种语言.区块链开发等等,之前总是想要学会一项,完成一样功能才记录.目前我要完成的项目是一个智慧海洋 ...

  2. Flask结合ECharts实现在线可视化效果,超级详细!

    Flask结合ECharts实现在线可视化效果,超级详细! 相信大家都知道这个吧Flask!--它是微型Python框架 一.Flask基本介绍 Flask 是一个微型的 Python 开发的 Web ...

  3. Sovit3D数字孪生智慧海上风电场3D可视化管理平台

    建设背景 双碳目标背景下,大力发展新能源产业已成为各界共识.风能凭借其资源总量丰富.环保.运行管理自动化程度高.度电成本持续降低等突出的优势,目前已成为开发和应用最为广泛的可再生能源之一,是全球可再生 ...

  4. 现代化智慧园区管理平台的建设方案详解

    伴随着大家对生活.居住.工作环境的要求愈来愈高,园区的个性化要求愈来愈重要.园区按其功能性与需求性可可分为:物流园区.高新科技园区.工业园区.软件园区.文化园区.创业园区.综合园区等等.智慧园区是融合 ...

  5. 网吧智慧消防监控平台

    一.行业背景   1.近年来,部分社会力量探索运用了智慧用电技术,在单位(场所)增设电气相关频发的现状推出的消防物联NB烟感.WiFi烟感,各消防主机远程传输模块,4G远程控制模块,无线智能NB压力表 ...

  6. vue-threeJS数据驱动的三维图形可视化

    数据驱动的三维图形可视化 在信息暴涨的2010-2016年间,冷暴力的扁平化确实有效降低用户的信息焦虑感,使有限的精力更高效处理过多的信息流.二维平面化扁平化在苹果等大头引领下,成为大众用户机器交流默 ...

  7. 智慧油田-三维管网可视化信息平台

    智慧油田-三维管网可视化信息平台 随着社会经济的快速发展,各种管线纵横交错,在油田内有从油井到集输站的管线,在油田外有从选油站到炼化厂的管线,在国际还有越境跨国的输油管线等.更多石油管线.天燃气管线和 ...

  8. 智慧城市/园区三维GIS可视化平台

    以三维GIS平台为载体构建的智慧城市三维GIS可视化平台,包括城市总体规划.区域规划.区域资源.智慧管理功能. 充分发挥三维GIS平台的数据叠加能力.可视化展示能力.三维空间分析能力,将业务管理.物联 ...

  9. 图扑软件搭建海上风电智慧安全调度平台

    海上风电场的建设和运维受海洋气候条件的影响较大,海上风电场存在可到达性差.海上交通安全风险高以及有效作业时间短的问题.合理利用有效窗口期完成运维任务,保证风电机组设备的安全以及减少非计划停运是风电场安 ...

最新文章

  1. python--通过xpath相对节点位置查找元素(续)
  2. windows和linux运算结果不同,从Windows和Linux读取文件会产生不同的结果(字符编码?)...
  3. android socket 服务,android 创建socket 通信型service
  4. 会linux基本命令是脚本语言吗,如何理解Linux Shell和基本Shell脚本语言?
  5. [教程指导]索尼官方4.0.3系统一键root方法! [复制链接]
  6. Linux C 函数练习
  7. 前端学习(2507):初始化多个实例化对象
  8. redis的zset使用(java)——存取List< Object>
  9. html调用文章标题,HTML中文章标题标签的详细介绍
  10. GitHub上传文件不能超过100M的解决办法
  11. access mysql 同步,SQLServer2008 同步Access数据库
  12. 【原创】ObjectARX中的代理对象
  13. SpringBoot配置Druid数据源,持久层分别 mybatis,jdbc
  14. 3D数学基础:图形与游戏开发
  15. 面试题:什么叫2B树
  16. 【canvas】linejoin——miterLimit
  17. 20.MySQL 常用命令
  18. HDU 2883 kebab(最大流)
  19. Java8 的一些新特性总结
  20. 2021 年“认证杯”数学中国数学建模网络挑战赛 B题解题思路

热门文章

  1. PMBOK6 核对单和核查表的区别
  2. Python安装火狐浏览器驱动
  3. win10重置进度条不动了_Windows10系统重置时卡死的处理方法
  4. 上报下单行为 腾讯广告 微信 公众号 PHP
  5. 【真人手势动画制作软件】万彩手影大师教程 | 如何让2个对象同时播放
  6. numeric mysql_MySQL数据类型-decimal与numeric的区别
  7. php 打开模态框,模态框打开页面
  8. Android新浪微博授权提示:文件不存在 C8998 的终极解决方法
  9. Linux基础、vim、find命令等
  10. 优秀的汽车后市场门店应该具备的数字化能力