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

前言

三维交互可视化平台(智慧海上牧场平台)学习开发之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 random

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pymysql
import threading

from util.ecodings import class_to_dict, Decimal_and_DateEncoder
from util.settings import DevelopmentConfig

pymysql.install_as_MySQLdb()

app = Flask(__name__)

app.config.from_object(DevelopmentConfig)

db = SQLAlchemy(app)

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)
    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)
    timer.start()

def create():

    db.create_all()

def drop():

    db.drop_all()

def query():

    db.session.commit()

    new = db.session.query(MonitorDatum).order_by(MonitorDatum.id.desc()).limit(1).all()

    result = class_to_dict(new)

    result[0] = json.loads(json.dumps(result[0], cls=Decimal_and_DateEncoder))

    result[0]['light'] = int(result[0]['light'])
    return result[0]

if __name__ == '__main__':

    t1 = threading.Timer(5, function=insert)
    t1.start()

python db_operate.py

app_db_data.py

import sys

sys.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 CORS

app = 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 = 0
    while True:
        socketio.sleep(5)
        res = query()

        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 thread
    with 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)

python app_db_data.py

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

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

二、前端编写

1.总体架构

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

三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql+websocket 实战(四)
本demo只写主界面中的基地总览和水质检测两部分,前端设计的还很烂,慢慢完善吧。

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

import Vue from 'vue'
import App from './App'
import router from './router'
import $ from 'jquery'

Vue.prototype.$ = $

import 'element-ui/lib/theme-chalk/index.css'

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,

    connection: socketIO.connect('ws://127.0.0.1:5000/test', {
      autoConnect:false
    })
}))
import echarts from "echarts";
Vue.prototype.$echarts = echarts;
Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  components: { App },
  template: ''
})

index.js: 路由跳转

import Vue from 'vue'
import Router from 'vue-router'
import Test from "../views/Test";
Vue.use(Router)

export default new Router({

  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">

  <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>

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

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){
          // 开始连接socket
          this.$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.主界面完成

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

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

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

先设计一下,如图所示:

三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql+websocket 实战(四)
然后页面设计:学到一个新知识点空格占位符3个, ; &ensp; &emsp;
三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql+websocket 实战(四)
注入逻辑部分:
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">

  <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">Phel-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] = false
              this.isPlain[1] = true
              this.isPlain[2] = true
              this.isPlain[3] = true
              this.isPlain[4] = true
            }else if (index === 1){
              this.isPlain[0] = true
              this.isPlain[1] = false
              this.isPlain[2] = true
              this.isPlain[3] = true
              this.isPlain[4] = true
              。。。设定}
              // 查询按钮
              date_query(start,end){
            // 获取start和end之间差几天,for循环 for (i=1,i {
                  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('执行')

        date = request.get_data()
        string = date.decode('utf-8', 'ignore')
        dic = json.loads(string)

        get_data = query_three("temperature", dic['start'], dic['day'])

        print(get_data[0])
        res = {"data": get_data}
        return res
    return "返回值来咯!"

结语

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

Original: https://blog.csdn.net/weixin_44165950/article/details/127375552
Author: 寒暄的大企鹅
Title: 三维交互可视化平台(智慧海上牧场平台)学习开发Flask+Vue+Echarts+Mysql+websocket 实战(四)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/745905/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球