node-red 可视化探索

  • node-red的基础使用不讲了,百度有较多资料,跟着官网也可以写个简单的自定义组件
  • 这里主要讲的是如何将复杂的组件输出成一个 独立的html页面并于vue框架结合形成一个友好的频繁交互页面
  • 第一:创建一个”config“类型的配置项,从而创建一个websocket长连接,进行频繁的数据交换
  • 第二:创建一个html文件,引入vue,写一个漂亮一些的可视化页面
  • 第三:创建一个组件,提供一些node服务,并关联上你写的websocket服务,启动你的html页面

配置socket

选择配置

流程

产生url

  • ui文件夹是组件加入后需要公开的文件
  • mycom 是自定义组件
  • socket 是长链接配置
  • 下面上代码

my-com.js


module.exports = function (RED) {
    "use strict";
    const path = require('path');
    const express = require('express');
    const { networkInterfaces } = RED.require('os');

    function myCom(config) {
        RED.nodes.createNode(this, config);
        const node = this;
        const socket = RED.nodes.getNode(config.socket);
        // 提供一些node能力 比如检测IPv4网口
        function getWorks() {
            const nif = networkInterfaces();
            const works = [];
            Object.keys(nif).forEach(i => {
                nif[i].forEach(j => {
                    j.family == 'IPv4' && works.push({ ...j, name: `${i} ( ${j.address} )` })
                })
            })
            return works;
        }
        node.getWorks = getWorks;
        node.on('input', ({ topic }) => {
            const order = `http://127.0.0.1:1880/ui.html?id=${config.id}&port=${socket.port}`; // 发布之后的url
            if (socket.statusText == 'start') { // socket已启动
                const app = RED.httpNode;
                app.use(express.static(path.join(__dirname, 'ui'))); // 发布html
                node.send({ topic, payload: order }); // 打印发布之后的url
            } else {
                node.send({ topic, payload: socket.statusText + order }); // 打印socket的状态
            }
        })
    }
    RED.nodes.registerType("my-com", myCom);
}

mycom.html

<!-- 注册 -->
<script type="text/javascript">
    RED.nodes.registerType('my-com', {
        category: 'my', // 分类
        color: '#a6bbcf', // 背景颜色
        defaults: {
            name: { value: "我的组件" },
            socket: { value: '', type: "my-socket" }
        },
        inputs: 1, // 上游:输入 0 或者 1
        outputs: 1, // 输出至下游 0 或者 more
        icon: 'fa fa-anchor', // 标签
        label: function () {
            return this.name;
        }
    }); 
</script>
<!-- 配置窗口 -->
<script type="text/html" data-template-name="my-com">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name"/>
    </div>
    <div class="form-row">
        <label for="node-input-socket"><i class="fa fa-globe"></i> <span>socket服务</span></label>
        <input type="text" id="node-input-socket">
    </div>
</script>
<!-- hover提示 -->
<script type="text/html" data-help-name="my-com">
    <p>码代码的小公举</p>
</script>

 socket.html

<!-- 注册节点 -->
<script type="text/javascript">
    RED.nodes.registerType('my-socket', {
        category: 'config', // 节点类型:配置,设置类
        defaults: {
            name: { value: "my-socket" },
            port: { value: '1881', required: true }
        },
        label: function () {
            return this.name;
        }
    });
</script>
<!-- 展示内容 -->
<script type="text/html" data-template-name="my-socket">
    <div class="form-row">
        <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-config-input-name" placeholder="Name" />
    </div>
    <div class="form-row">
        <label for="node-config-input-port"><i class="fa fa-tag"></i> Port</label>
        <input type="text" id="node-config-input-port" placeholder="Port"/>
    </div>
</script>

socket.js

module.exports = function (RED) {
    "use strict";
    const webServer = require('ws').Server;
    function mySocket(n) {
        RED.nodes.createNode(this, n);
        const node = this;
        node.port = n.port;
        node.statusText = 'init';
        const wss = new webServer({ port: node.port }, () => {
            console.log('start')
            node.statusText = 'start';
        });
        wss.on('connection', (ws) => {
            console.log('connected')
            node.statusText = 'connected';
            node.ws = ws;
            ws.send('connected');
            node.start = false;
            let timmer;
            ws.on('message', (data) => {
                try {
                    const { code, id } = JSON.parse(data.toString());
                    const targetNode = RED.nodes.getNode(id);
                    console.log(code, node.start, targetNode.getWorks)
                    if (code == 'start') {
                        // 持续检测
                        if (node.start) {
                            return;
                        }
                        node.start = true;
                        const loop = () => {
                            if (node.start) {
                                const works = targetNode.getWorks();
                                ws.send(JSON.stringify({ code: 'start', payload: works }))
                                timmer = setTimeout(loop, 100);
                            } else {
                                clearTimeout(timmer);
                                timmer = null;
                                
                            }
                        }
                        loop();
                    } else if (code == 'stop') {
                        node.start = false;
                        ws.send(JSON.stringify({ code: 'stop', payload: 'stoped' }))
                    }
                } catch (err) {

                }
            });
            ws.on('close', () => {
                node.statusText = '浏览器断开连接';
                console.log('closed')
            })
        });
        wss.on('close', () => {
            node.statusText = 'closed';
            console.log('closed')
        })
        wss.on('error', (error) => {
            if (error.toString().indexOf('already') > -1 ) {
                console.log('已启动')
                node.statusText = 'start';
                return;
            }
            console.log(error)
            node.statusText = 'error';
        })
    }
    RED.nodes.registerType("my-socket", mySocket);
}

ui.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>码代码的小公举</title>
    <script src="./vue.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #app {
            margin: 40px;
            height: 200px;
            line-height: 26px;
        }

        .items {
            margin: 10px;
        }

        th,td {
            width: 300px;
        }

        button {
            width: 100%;
            height: 40px;
            background: #3872e0;
            border-radius: 4px;
            color: #fff;
            border: 1px solid #3872e0;
            font-size: 16px;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div id='app'>
        <div class='box'>
            <div style="padding: 0 10px; margin-bottom: 10px;">
                <button v-show="!loading" @click="handlerCheck">开始检测</button>
                <button v-show="loading" @click="handlerStopCheck">停止检测</button>
            </div>
            <div class="items">
                <table border="1">
                    <tr>
                        <th>name</th>
                        <th>mac</th>
                        <th>family</th>
                        <th>netmask</th>
                        <th>address</th>
                    </tr>
                    <tr v-for="w of works" class="item">
                        <td>{{w.name}}</td>
                        <td>{{w.mac}}</td>
                        <td>{{w.family}}</td>
                        <td>{{w.netmask}}</td>
                        <td>{{w.address}}</td>
                    </tr>
                </table>
            </div>
        </div>
    </div>

    <script>
        const params = {};
        try {
            const arr = location.search.split('?')[1].split('&');
            arr.forEach(i => {
                const obj = i.split('=');
                params[obj[0]] = obj[1];
            })
        } catch (err) {

        }
        const id = params.id;
        const port = params.port;
        console.log(id, port)
        const { createApp } = Vue
        const HelloVueApp = {
            data() {
                return {
                    loading: false, // 检测按钮
                    works: [],
                }
            },
            mounted() {
                const ws = new WebSocket(`ws://localhost:${port}`);
                ws.addEventListener('open', (event) => {
                    console.log('WebSocket connected!');
                    this.connected = true; // 连接成功
                });
                ws.addEventListener('message', ({ data }) => {
                    try {
                        const { code, payload } = JSON.parse(data);
                        console.log(payload)
                        if (code == 'start') {
                            this.works = payload;
                        } else if (code == 'stop') {
                            // this.works = [];
                        }
                    } catch (err) { }
                });
                ws.addEventListener('close', (event) => {
                    console.log('WebSocket disconnected!');
                    this.connected = false;
                });
                this.ws = ws;
            },
            methods: {
                handlerCheck() {
                    this.loading = true;
                    this.ws.send(JSON.stringify({ code: 'start', id }))
                },
                handlerStopCheck() {
                    this.loading = false;
                    this.ws.send(JSON.stringify({ code: 'stop', id }))
                }
            }
        }

        Vue.createApp(HelloVueApp).mount('#app')
    </script>
</body>

</html>
  • port是配置的所以需要传递过去给html
  • id是获取node能力所需要的
  • html可以自由扩展发挥,项目化也是可以的,node-red作为配置以及服务器使用
  • vue.js 是vue 3 官网下载的
  • 写的毕竟粗糙
  • 发布之后的打开url:http://127.0.0.1:1880/ui.html?id=xxx&prot=xxx 查看
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值